aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts4
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx4
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts3
-rw-r--r--src/client/documents/Documents.ts168
-rw-r--r--src/client/northstar/dash-fields/HistogramField.ts5
-rw-r--r--src/client/util/DictationManager.ts7
-rw-r--r--src/client/util/DocumentManager.ts35
-rw-r--r--src/client/util/DragManager.ts11
-rw-r--r--src/client/util/DropConverter.ts31
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx8
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts12
-rw-r--r--src/client/util/InteractionUtils.tsx (renamed from src/client/util/InteractionUtils.ts)72
-rw-r--r--src/client/util/ProseMirrorEditorView.tsx74
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts2
-rw-r--r--src/client/util/RichTextMenu.tsx113
-rw-r--r--src/client/util/RichTextRules.ts122
-rw-r--r--src/client/util/RichTextSchema.tsx234
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/util/SettingsManager.scss136
-rw-r--r--src/client/util/SettingsManager.tsx131
-rw-r--r--src/client/util/TooltipLinkingMenu.tsx68
-rw-r--r--src/client/util/TooltipTextMenu.scss372
-rw-r--r--src/client/util/TooltipTextMenu.tsx1042
-rw-r--r--src/client/util/request-image-size.js2
-rw-r--r--src/client/util/type_decls.d1
-rw-r--r--src/client/views/ContextMenu.tsx20
-rw-r--r--src/client/views/DocComponent.tsx18
-rw-r--r--src/client/views/DocumentButtonBar.tsx132
-rw-r--r--src/client/views/DocumentDecorations.tsx167
-rw-r--r--src/client/views/EditableView.tsx38
-rw-r--r--src/client/views/GestureOverlay.scss20
-rw-r--r--src/client/views/GestureOverlay.tsx536
-rw-r--r--src/client/views/InkSelectDecorations.scss5
-rw-r--r--src/client/views/InkSelectDecorations.tsx55
-rw-r--r--src/client/views/InkingControl.tsx82
-rw-r--r--src/client/views/InkingStroke.tsx53
-rw-r--r--src/client/views/MainView.scss27
-rw-r--r--src/client/views/MainView.tsx48
-rw-r--r--src/client/views/MetadataEntryMenu.scss4
-rw-r--r--src/client/views/MetadataEntryMenu.tsx7
-rw-r--r--src/client/views/OverlayView.tsx3
-rw-r--r--src/client/views/Palette.scss21
-rw-r--r--src/client/views/Palette.tsx80
-rw-r--r--src/client/views/PreviewCursor.tsx14
-rw-r--r--src/client/views/TemplateMenu.scss13
-rw-r--r--src/client/views/TemplateMenu.tsx120
-rw-r--r--src/client/views/Templates.tsx2
-rw-r--r--src/client/views/Touchable.tsx139
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss40
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx79
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx133
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx51
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx4
-rw-r--r--src/client/views/collections/CollectionPivotView.scss57
-rw-r--r--src/client/views/collections/CollectionPivotView.tsx118
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx5
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss20
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx21
-rw-r--r--src/client/views/collections/CollectionStackingView.scss3
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx75
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx82
-rw-r--r--src/client/views/collections/CollectionStaffView.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx94
-rw-r--r--src/client/views/collections/CollectionTimeView.scss126
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx331
-rw-r--r--src/client/views/collections/CollectionTreeView.scss6
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx156
-rw-r--r--src/client/views/collections/CollectionView.tsx85
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss38
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx156
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss2
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx33
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx354
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx660
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx89
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss5
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx107
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss35
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx269
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx24
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx12
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx56
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx114
-rw-r--r--src/client/views/globalCssVariables.scss2
-rw-r--r--src/client/views/linking/LinkEditor.tsx2
-rw-r--r--src/client/views/linking/LinkFollowBox.tsx48
-rw-r--r--src/client/views/linking/LinkMenu.tsx2
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx2
-rw-r--r--src/client/views/nodes/AudioBox.tsx103
-rw-r--r--src/client/views/nodes/ButtonBox.tsx7
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx77
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.scss2
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx23
-rw-r--r--src/client/views/nodes/DocuLinkBox.tsx8
-rw-r--r--src/client/views/nodes/DocumentBox.tsx1
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx18
-rw-r--r--src/client/views/nodes/DocumentView.tsx463
-rw-r--r--src/client/views/nodes/FieldView.tsx15
-rw-r--r--src/client/views/nodes/FontIconBox.tsx19
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx173
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx3
-rw-r--r--src/client/views/nodes/IconBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.scss29
-rw-r--r--src/client/views/nodes/ImageBox.tsx213
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx10
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx3
-rw-r--r--src/client/views/nodes/PDFBox.tsx26
-rw-r--r--src/client/views/nodes/PresBox.scss21
-rw-r--r--src/client/views/nodes/PresBox.tsx166
-rw-r--r--src/client/views/nodes/RadialMenu.scss83
-rw-r--r--src/client/views/nodes/RadialMenu.tsx224
-rw-r--r--src/client/views/nodes/RadialMenuItem.tsx117
-rw-r--r--src/client/views/nodes/VideoBox.tsx35
-rw-r--r--src/client/views/nodes/WebBox.tsx22
-rw-r--r--src/client/views/pdf/Annotation.tsx10
-rw-r--r--src/client/views/pdf/PDFViewer.tsx72
-rw-r--r--src/client/views/presentationview/PresElementBox.scss35
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx110
-rw-r--r--src/client/views/search/SearchBox.scss1
-rw-r--r--src/client/views/search/SearchBox.tsx19
-rw-r--r--src/client/views/search/SearchItem.tsx12
124 files changed, 5751 insertions, 4143 deletions
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 966d8053a..7e5d5fe1b 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -136,7 +136,7 @@ export namespace GooglePhotos {
document.contentSize = upload.contentSize;
return document;
});
- const options = { width: 500, height: 500 };
+ const options = { _width: 500, _height: 500 };
return constructor(children, options);
};
@@ -340,7 +340,7 @@ export namespace GooglePhotos {
const url = data.url.href;
const target = Doc.MakeAlias(source);
const description = parseDescription(target, descriptionKey);
- await DocumentView.makeCustomViewClicked(target, undefined);
+ await DocumentView.makeCustomViewClicked(target, undefined, Docs.Create.FreeformDocument);
media.push({ url, description });
}
if (media.length) {
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index fd3d9e2f1..c940bba43 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -46,7 +46,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
* When component mounts, last search's results are laoded in based on the back up stored
* in the document of the props.
*/
- async componentWillMount() {
+ async componentDidMount() {
//DocServer.getYoutubeChannels();
const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc);
const awaitedBackUp = await castedSearchBackUp;
@@ -337,7 +337,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
const newVideoX = NumCast(this.props.Document.x);
const newVideoY = NumCast(this.props.Document.y) + NumCast(this.props.Document.height);
- addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, width: 400, height: 315, x: newVideoX, y: newVideoY }));
+ addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY }));
this.videoClicked = true;
}
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 57296c961..9e2ceac62 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -185,8 +185,9 @@ export namespace CognitiveServices {
results.recognitionUnits && (results = results.recognitionUnits);
target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
const recognizedText = results.map((item: any) => item.recognizedText);
+ const recognizedObjects = results.map((item: any) => item.recognizedObject);
const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
- target[keys[1]] = individualWords.join(" ");
+ target[keys[1]] = individualWords.length ? individualWords.join(" ") : recognizedObjects.join(", ");
}
batch.end();
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index aaf3d3d11..ba0f69846 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -29,7 +29,7 @@ import { listSpec } from "../../new_fields/Schema";
import { DocServer } from "../DocServer";
import { dropActionType } from "../util/DragManager";
import { DateField } from "../../new_fields/DateField";
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager, undoBatch } from "../util/UndoManager";
import { YoutubeBox } from "../apis/youtube/YoutubeBox";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { LinkManager } from "../util/LinkManager";
@@ -54,42 +54,53 @@ import { DocumentBox } from "../views/nodes/DocumentBox";
import { InkingStroke } from "../views/InkingStroke";
import { InkField } from "../../new_fields/InkField";
import { InkingControl } from "../views/InkingControl";
+import { RichTextField } from "../../new_fields/RichTextField";
+import { Networking } from "../Network";
const requestImageSize = require('../util/request-image-size');
const path = require('path');
export interface DocumentOptions {
+ _autoHeight?: boolean;
+ _panX?: number;
+ _panY?: number;
+ _width?: number;
+ _height?: number;
+ _nativeWidth?: number;
+ _nativeHeight?: number;
+ _fitWidth?: boolean;
+ _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
+ _LODdisable?: boolean;
+ dropAction?: dropActionType;
+ _chromeStatus?: string;
+ _viewType?: number;
+ _gridGap?: number; // gap between items in masonry view
+ _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
+ _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
+ _textTemplate?: RichTextField; // template used by a formattedTextBox to create a text box to render
+ _itemIndex?: number; // which item index the carousel viewer is showing
+ _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts
x?: number;
y?: number;
z?: number;
+ layoutKey?: string;
type?: string;
- width?: number;
- height?: number;
- nativeWidth?: number;
- nativeHeight?: number;
title?: string;
- panX?: number;
- panY?: number;
page?: number;
scale?: number;
- fitWidth?: boolean;
- fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
forceActive?: boolean;
preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
layout?: string | Doc;
hideHeadings?: boolean; // whether stacking view column headings should be hidden
- isTemplateField?: boolean;
+ isTemplateForField?: string; // the field key for which the containing document is a rendering template
isTemplateDoc?: boolean;
templates?: List<string>;
- viewType?: number;
- backgroundColor?: string;
+ backgroundColor?: string | ScriptField;
ignoreClick?: boolean;
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
opacity?: number;
defaultBackgroundColor?: string;
- dropAction?: dropActionType;
- chromeStatus?: string;
columnWidth?: number;
fontSize?: number;
curPage?: number;
@@ -98,22 +109,23 @@ export interface DocumentOptions {
documentText?: string;
borderRounding?: string;
boxShadow?: string;
+ showTitle?: string;
sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views
schemaColumns?: List<SchemaHeaderField>;
dockingConfig?: string;
- autoHeight?: boolean;
annotationOn?: Doc;
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
dbDoc?: Doc;
ischecked?: ScriptField; // returns whether a font icon box is checked
activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
onClick?: ScriptField;
+ onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
+ onPointerDown?: ScriptField;
+ onPointerUp?: ScriptField;
dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
+ clipboard?: Doc; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
icon?: string;
- gridGap?: number; // gap between items in masonry view
- xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
- yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
@@ -121,9 +133,14 @@ export interface DocumentOptions {
color?: string;
treeViewHideTitle?: boolean; // whether to hide the title of a tree view
treeViewOpen?: boolean; // whether this document is expanded in a tree view
+ treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
isFacetFilter?: boolean; // whether document functions as a facet filter in a tree view
limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
// [key: string]: Opt<Field>;
+ pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
+ isExpanded?: boolean; // is linear view expanded
+ textTransform?: string; // is linear view expanded
+ letterSpacing?: string; // is linear view expanded
}
class EmptyBox {
@@ -151,19 +168,19 @@ export namespace Docs {
const TemplateMap: TemplateMap = new Map([
[DocumentType.TEXT, {
layout: { view: FormattedTextBox, dataField: data },
- options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
+ options: { _height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
}],
[DocumentType.HIST, {
layout: { view: HistogramBox, dataField: data },
- options: { height: 300, backgroundColor: "black" }
+ options: { _height: 300, backgroundColor: "black" }
}],
[DocumentType.QUERY, {
layout: { view: QueryBox, dataField: data },
- options: { width: 400 }
+ options: { _width: 400 }
}],
[DocumentType.COLOR, {
layout: { view: ColorBox, dataField: data },
- options: { nativeWidth: 220, nativeHeight: 300 }
+ options: { _nativeWidth: 220, _nativeHeight: 300 }
}],
[DocumentType.IMG, {
layout: { view: ImageBox, dataField: data },
@@ -171,19 +188,19 @@ export namespace Docs {
}],
[DocumentType.WEB, {
layout: { view: WebBox, dataField: data },
- options: { height: 300 }
+ options: { _height: 300 }
}],
[DocumentType.COL, {
layout: { view: CollectionView, dataField: data },
- options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 }
+ options: { _panX: 0, _panY: 0, scale: 1 } // , _width: 500, _height: 500 }
}],
[DocumentType.KVP, {
layout: { view: KeyValueBox, dataField: data },
- options: { height: 150 }
+ options: { _height: 150 }
}],
[DocumentType.DOCUMENT, {
layout: { view: DocumentBox, dataField: data },
- options: { height: 250 }
+ options: { _height: 250 }
}],
[DocumentType.VID, {
layout: { view: VideoBox, dataField: data },
@@ -191,7 +208,7 @@ export namespace Docs {
}],
[DocumentType.AUDIO, {
layout: { view: AudioBox, dataField: data },
- options: { height: 35, backgroundColor: "lightGray" }
+ options: { _height: 35, backgroundColor: "lightGray" }
}],
[DocumentType.PDF, {
layout: { view: PDFBox, dataField: data },
@@ -199,11 +216,11 @@ export namespace Docs {
}],
[DocumentType.ICON, {
layout: { view: IconBox, dataField: data },
- options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) },
+ options: { _width: Number(MINIMIZED_ICON_SIZE), _height: Number(MINIMIZED_ICON_SIZE) },
}],
[DocumentType.IMPORT, {
layout: { view: DirectoryImportBox, dataField: data },
- options: { height: 150 }
+ options: { _height: 150 }
}],
[DocumentType.LINKDOC, {
data: new List<Doc>(),
@@ -221,7 +238,7 @@ export namespace Docs {
}],
[DocumentType.FONTICON, {
layout: { view: FontIconBox, dataField: data },
- options: { width: 40, height: 40, borderRounding: "100%" },
+ options: { _width: 40, _height: 40, borderRounding: "100%" },
}],
[DocumentType.LINKFOLLOW, {
layout: { view: LinkFollowBox, dataField: data }
@@ -239,7 +256,7 @@ export namespace Docs {
]);
// All document prototypes are initialized with at least these values
- const defaultOptions: DocumentOptions = { x: 0, y: 0, width: 300 };
+ const defaultOptions: DocumentOptions = { x: 0, y: 0, _width: 300 }; // bcz: do we really want to set anything here? could also try to set in render() methods for types that need a default
const suffix = "Proto";
/**
@@ -317,7 +334,8 @@ export namespace Docs {
// whatever options pertain to this specific prototype
const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
options.layout = layout.view.LayoutString(layout.dataField);
- return Doc.assign(new Doc(prototypeId, true), { ...options });
+ const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
+ return doc;
}
}
@@ -328,7 +346,14 @@ export namespace Docs {
*/
export namespace Create {
- const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "annotationOn", "forceActive", "fitWidth"];
+ export async function Buxton() {
+ console.log(await Networking.FetchFromServer("/newBuxton"));
+ }
+
+ Scripting.addGlobal(Buxton);
+
+ const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "_annotationOn",
+ "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "showTitle"];
/**
* This function receives the relevant document prototype and uses
@@ -398,11 +423,11 @@ export namespace Docs {
requestImageSize(target)
.then((size: any) => {
const aspect = size.height / size.width;
- if (!inst.nativeWidth) {
- inst.nativeWidth = size.width;
+ if (!inst._nativeWidth) {
+ inst._nativeWidth = size.width;
}
- inst.nativeHeight = NumCast(inst.nativeWidth) * aspect;
- inst.height = NumCast(inst.width) * aspect;
+ inst._nativeHeight = NumCast(inst._nativeWidth) * aspect;
+ inst._height = NumCast(inst._width) * aspect;
})
.catch((err: any) => console.log(err));
// }
@@ -440,8 +465,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COLOR), "", options);
}
- export function TextDocument(options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
+ export function TextDocument(text: string, options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options);
}
export function InkDocument(color: string, tool: number, strokeWidth: number, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
@@ -465,7 +490,7 @@ export namespace Docs {
const ctlog = await Gateway.Instance.GetSchema(url, schemaName);
if (ctlog && ctlog.schemas) {
const schema = ctlog.schemas[0];
- const schemaDoc = Docs.Create.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
+ const schemaDoc = Docs.Create.TreeDocument([], { ...options, _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: schema.displayName! });
const schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
if (!schemaDocuments) {
return;
@@ -482,13 +507,13 @@ export namespace Docs {
new AttributeTransformationModel(atmod, AggregateFunction.None),
new AttributeTransformationModel(atmod, AggregateFunction.Count),
new AttributeTransformationModel(atmod, AggregateFunction.Count));
- docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! }));
+ docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, _width: 200, _height: 200, title: attr.displayName! }));
}
}));
});
return schemaDoc;
}
- return Docs.Create.TreeDocument([], { width: 50, height: 100, title: schemaName });
+ return Docs.Create.TreeDocument([], { _width: 50, _height: 100, title: schemaName });
}
export function WebDocument(url: string, options: DocumentOptions = {}) {
@@ -508,27 +533,35 @@ export namespace Docs {
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id);
}
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Linear }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
+ }
+
+ export function CarouselDocument(documents: Array<Doc>, options: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Carousel });
}
export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, _viewType: CollectionViewType.Schema });
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Tree });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Tree });
}
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Stacking });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Stacking });
+ }
+
+ export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multicolumn });
}
export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Masonry });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry });
}
export function ButtonDocument(options?: DocumentOptions) {
@@ -549,7 +582,7 @@ export namespace Docs {
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
- const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+ const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
Doc.GetProto(inst).data = new List<Doc>(documents);
return inst;
}
@@ -671,37 +704,38 @@ export namespace Docs {
const field = target[fieldKey];
const resolved = options || {};
if (field instanceof ImageField) {
- created = Docs.Create.ImageDocument((field as ImageField).url.href, resolved);
+ created = Docs.Create.ImageDocument((field).url.href, resolved);
layout = ImageBox.LayoutString;
} else if (field instanceof Doc) {
created = field;
} else if (field instanceof VideoField) {
- created = Docs.Create.VideoDocument((field as VideoField).url.href, resolved);
+ created = Docs.Create.VideoDocument((field).url.href, resolved);
layout = VideoBox.LayoutString;
} else if (field instanceof PdfField) {
- created = Docs.Create.PdfDocument((field as PdfField).url.href, resolved);
+ created = Docs.Create.PdfDocument((field).url.href, resolved);
layout = PDFBox.LayoutString;
} else if (field instanceof IconField) {
- created = Docs.Create.IconDocument((field as IconField).icon, resolved);
+ created = Docs.Create.IconDocument((field).icon, resolved);
layout = IconBox.LayoutString;
} else if (field instanceof AudioField) {
- created = Docs.Create.AudioDocument((field as AudioField).url.href, resolved);
+ created = Docs.Create.AudioDocument((field).url.href, resolved);
layout = AudioBox.LayoutString;
} else if (field instanceof HistogramField) {
- created = Docs.Create.HistogramDocument((field as HistogramField).HistoOp, resolved);
+ created = Docs.Create.HistogramDocument((field).HistoOp, resolved);
layout = HistogramBox.LayoutString;
} else if (field instanceof InkField) {
const { selectedColor, selectedWidth, selectedTool } = InkingControl.Instance;
- created = Docs.Create.InkDocument(selectedColor, selectedTool, Number(selectedWidth), (field as InkField).inkData, resolved);
+ created = Docs.Create.InkDocument(selectedColor, selectedTool, Number(selectedWidth), (field).inkData, resolved);
layout = InkingStroke.LayoutString;
} else if (field instanceof List && field[0] instanceof Doc) {
created = Docs.Create.StackingDocument(DocListCast(field), resolved);
layout = CollectionView.LayoutString;
} else {
- created = Docs.Create.TextDocument({ ...{ width: 200, height: 25, autoHeight: true }, ...resolved });
+ created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
layout = FormattedTextBox.LayoutString;
}
created.layout = layout?.(fieldKey);
+ created.title = fieldKey;
proto && (created.proto = Doc.GetProto(proto));
return created;
}
@@ -710,20 +744,20 @@ export namespace Docs {
let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
if (type.indexOf("image") !== -1) {
ctor = Docs.Create.ImageDocument;
- if (!options.width) options.width = 300;
+ if (!options._width) options._width = 300;
}
if (type.indexOf("video") !== -1) {
ctor = Docs.Create.VideoDocument;
- if (!options.width) options.width = 600;
- if (!options.height) options.height = options.width * 2 / 3;
+ if (!options._width) options._width = 600;
+ if (!options._height) options._height = options._width * 2 / 3;
}
if (type.indexOf("audio") !== -1) {
ctor = Docs.Create.AudioDocument;
}
if (type.indexOf("pdf") !== -1) {
ctor = Docs.Create.PdfDocument;
- if (!options.width) options.width = 400;
- if (!options.height) options.height = options.width * 1200 / 927;
+ if (!options._width) options._width = 400;
+ if (!options._height) options._height = options._width * 1200 / 927;
}
if (type.indexOf("excel") !== -1) {
ctor = Docs.Create.DBDocument;
@@ -738,15 +772,15 @@ export namespace Docs {
const alias = Doc.MakeAlias(field);
alias.x = options.x || 0;
alias.y = options.y || 0;
- alias.width = options.width || 300;
- alias.height = options.height || options.width || 300;
+ alias._width = options._width || 300;
+ alias._height = options._height || options._width || 300;
return alias;
}
return undefined;
});
}
ctor = Docs.Create.WebDocument;
- options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+ options = { _height: options._width, ...options, title: path, _nativeWidth: undefined };
}
return ctor ? ctor(path, options) : undefined;
}
@@ -807,8 +841,8 @@ export namespace DocUtils {
linkDocProto.anchor2Groups = new List<Doc>([]);
linkDocProto.anchor1Timecode = source.doc.currentTimecode;
linkDocProto.anchor2Timecode = target.doc.currentTimecode;
- linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1");
- linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2");
+ linkDocProto.layout_key1 = DocuLinkBox.LayoutString("anchor1");
+ linkDocProto.layout_key2 = DocuLinkBox.LayoutString("anchor2");
linkDocProto.width = linkDocProto.height = 0;
linkDocProto.isBackground = true;
linkDocProto.isButton = true;
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index f3365e73d..076516977 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -7,7 +7,7 @@ import { ObjectField } from "../../../new_fields/ObjectField";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { OmitKeys } from "../../../Utils";
import { Deserializable } from "../../util/SerializationHelper";
-import { Copy, ToScriptString } from "../../../new_fields/FieldSymbols";
+import { Copy, ToScriptString, ToString } from "../../../new_fields/FieldSymbols";
function serialize(field: HistogramField) {
const obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
@@ -60,4 +60,7 @@ export class HistogramField extends ObjectField {
[ToScriptString]() {
return this.toString();
}
+ [ToString]() {
+ return this.toString();
+ }
} \ No newline at end of file
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 3d8f2d234..3394cb93d 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -325,15 +325,14 @@ export namespace DictationManager {
["open fields", {
action: (target: DocumentView) => {
- const kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+ const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
target.props.addDocTab(kvp, target.props.DataDoc, "onRight");
}
}],
["new outline", {
action: (target: DocumentView) => {
- const newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
- newBox.autoHeight = true;
+ const newBox = Docs.Create.TextDocument("", { _width: 400, _height: 200, title: "My Outline", _autoHeight: true });
const proto = newBox.proto!;
const prompt = "Press alt + r to start dictating here...";
const head = 3;
@@ -379,7 +378,7 @@ export namespace DictationManager {
expression: /view as (freeform|stacking|masonry|schema|tree)/g,
action: (target: DocumentView, matches: RegExpExecArray) => {
const mode = CollectionViewType.valueOf(matches[1]);
- mode && (target.props.Document.viewType = mode);
+ mode && (target.props.Document._viewType = mode);
},
restrictTo: [DocumentType.COL]
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb4c2155a..60bb25272 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -93,9 +93,9 @@ export class DocumentManager {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.map(view =>
- view.props.Document === toFind && toReturn.push(view));
+ view.props.Document.presBox === undefined && view.props.Document === toFind && toReturn.push(view));
DocumentManager.Instance.DocumentViews.map(view =>
- view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+ view.props.Document.presBox === undefined && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
return toReturn;
}
@@ -140,7 +140,7 @@ export class DocumentManager {
if (first) annotatedDoc = first.props.Document;
}
if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
- docView.props.focus(docView.props.Document, false);
+ docView.props.focus(docView.props.Document, willZoom);
highlight();
} else {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
@@ -221,34 +221,5 @@ export class DocumentManager {
return 1;
}
}
-
- @action
- animateBetweenPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
- expandedDocs && expandedDocs.map(expDoc => {
- if (expDoc.isMinimized || expDoc.isAnimating === "min") { // MAXIMIZE DOC
- if (expDoc.isMinimized) { // docs are never actaully at the minimized location. so when we unminimize one, we have to set our overrides to make it look like it was at the minimize location
- expDoc.isMinimized = false;
- expDoc.animateToPos = new List<number>([...scrpt, 0]);
- expDoc.animateToDimensions = new List<number>([0, 0]);
- }
- setTimeout(() => {
- expDoc.isAnimating = "max";
- expDoc.animateToPos = new List<number>([0, 0, 1]);
- expDoc.animateToDimensions = new List<number>([NumCast(expDoc.width), NumCast(expDoc.height)]);
- setTimeout(() => expDoc.isAnimating === "max" && (expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined), 600);
- }, 0);
- } else { // MINIMIZE DOC
- expDoc.isAnimating = "min";
- expDoc.animateToPos = new List<number>([...scrpt, 0]);
- expDoc.animateToDimensions = new List<number>([0, 0]);
- setTimeout(() => {
- if (expDoc.isAnimating === "min") {
- expDoc.isMinimized = true;
- expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined;
- }
- }, 600);
- }
- });
- }
}
Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index df2f5fe3c..e572f0fcb 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -94,6 +94,7 @@ export namespace DragManager {
readonly x: number,
readonly y: number,
readonly complete: DragCompleteEvent,
+ readonly shiftKey: boolean,
readonly altKey: boolean,
readonly metaKey: boolean,
readonly ctrlKey: boolean
@@ -196,7 +197,10 @@ export namespace DragManager {
dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
);
e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
- Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined));
+ Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => {
+ drop[prop] = undefined;
+ })
+ );
};
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
StartDrag(eles, dragData, downX, downY, options, finishDrag);
@@ -205,7 +209,7 @@ export namespace DragManager {
// drag a button template and drop a new button
export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
- const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
+ const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: title });
bd.onClick = ScriptField.MakeScript(script);
params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
initialize && initialize(bd);
@@ -340,7 +344,7 @@ export namespace DragManager {
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey ? "alias" : undefined;
}
- if (e.shiftKey && CollectionDockingView.Instance) {
+ if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
AbortDrag();
finishDrag?.(new DragCompleteEvent(true, dragData));
CollectionDockingView.Instance.StartOtherDrag({
@@ -409,6 +413,7 @@ export namespace DragManager {
x: e.x,
y: e.y,
complete: complete,
+ shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
ctrlKey: e.ctrlKey
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index dc66bceee..d0f1d86cb 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,27 +1,31 @@
import { DragManager } from "./DragManager";
-import { CollectionViewType } from "../views/collections/CollectionView";
import { Doc, DocListCast } from "../../new_fields/Doc";
import { DocumentType } from "../documents/DocumentTypes";
import { ObjectField } from "../../new_fields/ObjectField";
import { StrCast } from "../../new_fields/Types";
import { Docs } from "../documents/Documents";
-import { ScriptField } from "../../new_fields/ScriptField";
+import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
+import { RichTextField } from "../../new_fields/RichTextField";
+import { ImageField } from "../../new_fields/URLField";
-
-function makeTemplate(doc: Doc): boolean {
- const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+export function makeTemplate(doc: Doc): boolean {
+ const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0];
const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, "");
const docs = DocListCast(layoutDoc[fieldKey]);
let any = false;
docs.forEach(d => {
if (!StrCast(d.title).startsWith("-")) {
- any = true;
- Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc));
- } else if (d.type === DocumentType.COL) {
+ any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
+ } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
any = makeTemplate(d) || any;
}
});
+ if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
+ if (!StrCast(layoutDoc.title).startsWith("-")) {
+ any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc));
+ }
+ }
return any;
}
export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
@@ -29,13 +33,14 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
if (!doc.onDragStart && !doc.onClick && !doc.isButtonBar) {
- const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
- if (layoutDoc.type === DocumentType.COL) {
- layoutDoc.isTemplateDoc = makeTemplate(layoutDoc);
+ const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
+ if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) {
+ makeTemplate(layoutDoc);
} else {
- layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && !data.userDropAction;
+ (layoutDoc.layout instanceof Doc) && !data.userDropAction;
}
- dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
+ layoutDoc.isTemplateDoc = true;
+ dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
dbox.dragFactory = layoutDoc;
dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 5b5bffd8c..071015193 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -116,13 +116,13 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
formData.append(Utils.GenerateGuid(), file);
});
- collector.push(...(await Networking.PostFormDataToServer("/upload", formData)));
+ collector.push(...(await Networking.PostFormDataToServer("/uploadFormData", formData)));
runInAction(() => this.completed += batch.length);
});
await Promise.all(uploads.map(async ({ name, type, clientAccessPath, exifData }) => {
const path = Utils.prepend(clientAccessPath);
- const document = await Docs.Get.DocumentFromType(type, path, { width: 300, title: name });
+ const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
const { data, error } = exifData;
if (document) {
Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data);
@@ -145,8 +145,8 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
const offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
const options: DocumentOptions = {
title: `Import of ${directory}`,
- width: 1105,
- height: 500,
+ _width: 1105,
+ _height: 500,
x: NumCast(doc.x),
y: NumCast(doc.y) + offset
};
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 6a9486f83..ff909cc6b 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -14,9 +14,17 @@ export namespace ImageUtils {
return false;
}
const source = field.url.href;
- const response = await Networking.PostToServer("/inspectImage", { source });
- const { error, data } = response.exifData;
+ const {
+ contentSize,
+ nativeWidth,
+ nativeHeight,
+ exifData: { error, data }
+ } = await Networking.PostToServer("/inspectImage", { source });
document.exif = error || Docs.Get.DocumentHierarchyFromJson(data);
+ const proto = Doc.GetProto(document);
+ proto["data-nativeWidth"] = nativeWidth;
+ proto["data-nativeHeight"] = nativeHeight;
+ proto.contentSize = contentSize;
return data !== undefined;
};
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.tsx
index 2e4e8c7ca..7194feb2e 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.tsx
@@ -8,14 +8,74 @@ export namespace InteractionUtils {
const REACT_POINTER_PEN_BUTTON = 0;
const ERASER_BUTTON = 5;
- export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>): React.Touch[] {
+ export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) {
+ const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
+ return (
+ <polyline
+ points={pts}
+ style={{
+ fill: "none",
+ stroke: color,
+ strokeWidth: width
+ }}
+ />
+ );
+ }
+
+ export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> {
+ constructor(
+ readonly fingers: number,
+ // readonly points: T extends React.TouchEvent ? React.TouchList : TouchList,
+ readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
+ readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[],
+ readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
+ readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent
+ ) { }
+ }
+
+ export interface MultiTouchEventDisposer { (): void; }
+
+ export function MakeMultiTouchTarget(
+ element: HTMLElement,
+ startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void,
+ ): MultiTouchEventDisposer {
+ const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
+ // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
+ // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
+ element.addEventListener("dashOnTouchStart", onMultiTouchStartHandler);
+ // if (onMultiTouchMoveHandler) {
+ // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
+ // }
+ // if (onMultiTouchEndHandler) {
+ // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler);
+ // }
+ return () => {
+ element.removeEventListener("dashOnTouchStart", onMultiTouchStartHandler);
+ // if (onMultiTouchMoveHandler) {
+ // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
+ // }
+ // if (onMultiTouchEndHandler) {
+ // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler);
+ // }
+ };
+ }
+
+ export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
const myTouches = new Array<React.Touch>();
- for (let i = 0; i < e.targetTouches.length; i++) {
- const pt = e.targetTouches.item(i);
- if (pt && prevPoints.has(pt.identifier)) {
- myTouches.push(pt);
+ for (const pt of mte.touches) {
+ if (!ignorePen || ((pt as any).radiusX > 1 && (pt as any).radiusY > 1)) {
+ for (const tPt of mte.targetTouches) {
+ if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
+ if (pt && prevPoints.has(pt.identifier)) {
+ myTouches.push(pt);
+ }
+ }
+ }
}
}
+ // if (mte.touches.length !== myTouches.length) {
+ // throw Error("opo")
+ // }
return myTouches;
}
@@ -23,7 +83,7 @@ export namespace InteractionUtils {
switch (type) {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
case PENTYPE:
- return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? POINTER_PEN_BUTTON : REACT_POINTER_PEN_BUTTON);
+ return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE:
return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
default:
diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx
deleted file mode 100644
index b42adfbb4..000000000
--- a/src/client/util/ProseMirrorEditorView.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from "react";
-import { EditorView } from "prosemirror-view";
-import { EditorState } from "prosemirror-state";
-
-export interface ProseMirrorEditorViewProps {
- /* EditorState instance to use. */
- editorState: EditorState;
- /* Called when EditorView produces new EditorState. */
- onEditorState: (editorState: EditorState) => any;
-}
-
-/**
- * This wraps ProseMirror's EditorView into React component.
- * This code was found on https://discuss.prosemirror.net/t/using-with-react/904
- */
-export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorViewProps> {
-
- private _editorView?: EditorView;
-
- _createEditorView = (element: HTMLDivElement | null) => {
- if (element !== null) {
- this._editorView = new EditorView(element, {
- state: this.props.editorState,
- dispatchTransaction: this.dispatchTransaction,
- });
- }
- }
-
- dispatchTransaction = (tx: any) => {
- // In case EditorView makes any modification to a state we funnel those
- // modifications up to the parent and apply to the EditorView itself.
- const editorState = this.props.editorState.apply(tx);
- if (this._editorView) {
- this._editorView.updateState(editorState);
- }
- this.props.onEditorState(editorState);
- }
-
- focus() {
- if (this._editorView) {
- this._editorView.focus();
- }
- }
-
- componentWillReceiveProps(nextProps: { editorState: EditorState<any>; }) {
- // In case we receive new EditorState through props — we apply it to the
- // EditorView instance.
- if (this._editorView) {
- if (nextProps.editorState !== this.props.editorState) {
- this._editorView.updateState(nextProps.editorState);
- }
- }
- }
-
- componentWillUnmount() {
- if (this._editorView) {
- this._editorView.destroy();
- }
- }
-
- shouldComponentUpdate() {
- // Note that EditorView manages its DOM itself so we'd ratrher don't mess
- // with it.
- return false;
- }
-
- render() {
- // Render just an empty div which is then used as a container for an
- // EditorView instance.
- return (
- <div ref={this._createEditorView} />
- );
- }
-} \ No newline at end of file
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index c028dbf8b..da3815181 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -5,9 +5,7 @@ import { Schema } from "prosemirror-model";
import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { TooltipTextMenu } from "./TooltipTextMenu";
import { SelectionManager } from "./SelectionManager";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 419d7caf9..e07efe056 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -32,8 +32,9 @@ export default class RichTextMenu extends AntimodeMenu {
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private view?: EditorView;
- private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+ public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+ public _brushMap: Map<string, Set<Mark>> = new Map();
private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
@@ -72,7 +73,7 @@ export default class RichTextMenu extends AntimodeMenu {
this.fontSizeOptions = [
{ mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
{ mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+ { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
{ mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
{ mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
{ mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
@@ -142,6 +143,17 @@ export default class RichTextMenu extends AntimodeMenu {
this.updateFromDash(view, lastState, this.editorProps);
}
+
+ public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
+ if (this.view) {
+ const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
+ this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
+ addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+ return this.view.state.selection.$from.nodeAfter?.text || "";
+ }
+ return "";
+ }
+
@action
public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
if (!view) {
@@ -300,22 +312,22 @@ export default class RichTextMenu extends AntimodeMenu {
}
return (
- <button className={"antimodeMenu-button" + (isActive ? " active" : "")} title={title} onPointerDown={onClick}>
+ <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
<FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
</button>
);
}
- createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+ createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
return label === activeOption ?
- <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
- <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
+ <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+ <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
}
return label === activeOption ?
- <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
- <option value={label} title={title} style={style ? style : {}}>{label}</option>;
+ <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+ <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
});
const self = this;
@@ -328,19 +340,19 @@ export default class RichTextMenu extends AntimodeMenu {
}
});
}
- return <select onChange={onChange}>{items}</select>;
+ return <select onChange={onChange} key={key}>{items}</select>;
}
- createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+ createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
return label === activeOption ?
- <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
- <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
+ <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+ <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
}
return label === activeOption ?
- <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
- <option value={label} title={title} style={style ? style : {}}>{label}</option>;
+ <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+ <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
});
const self = this;
@@ -351,31 +363,15 @@ export default class RichTextMenu extends AntimodeMenu {
}
});
}
- return <select onChange={e => onChange(e.target.value)}>{items}</select>;
+ return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
}
changeFontSize = (mark: Mark, view: EditorView) => {
- const size = mark.attrs.fontSize;
- if (this.editorProps) {
- const ruleProvider = this.editorProps.ruleProvider;
- const heading = NumCast(this.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleSize_" + heading] = size;
- }
- }
- this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
+ this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
}
changeFontFamily = (mark: Mark, view: EditorView) => {
- const fontName = mark.attrs.family;
- if (this.editorProps) {
- const ruleProvider = this.editorProps.ruleProvider;
- const heading = NumCast(this.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleFont_" + heading] = fontName;
- }
- }
- this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
+ this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
}
// TODO: remove doesn't work
@@ -417,6 +413,15 @@ export default class RichTextMenu extends AntimodeMenu {
@action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+ // todo: add brushes to brushMap to save with a style name
+ onBrushNameKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+ this._brushNameRef.current!.style.background = "lightGray";
+ }
+ }
+ _brushNameRef = React.createRef<HTMLInputElement>();
+
createBrushButton() {
const self = this;
function onBrushClick(e: React.PointerEvent) {
@@ -447,11 +452,11 @@ export default class RichTextMenu extends AntimodeMenu {
<div className="dropdown">
<p>{label}</p>
<button onPointerDown={this.clearBrush}>Clear brush</button>
- {/* <input placeholder="Enter URL"></input> */}
+ <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
</div>;
return (
- <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
);
}
@@ -514,21 +519,21 @@ export default class RichTextMenu extends AntimodeMenu {
</button>;
const dropdownContent =
- <div className="dropdown">
+ <div className="dropdown" >
<p>Change font color:</p>
<div className="color-wrapper">
{this.fontColors.map(color => {
if (color) {
return this.activeFontColor === color ?
- <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
- <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+ <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+ <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
}
})}
</div>
</div>;
return (
- <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
);
}
@@ -561,7 +566,7 @@ export default class RichTextMenu extends AntimodeMenu {
}
const button =
- <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
+ <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
<FontAwesomeIcon icon="highlighter" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
</button>;
@@ -573,15 +578,15 @@ export default class RichTextMenu extends AntimodeMenu {
{this.highlightColors.map(color => {
if (color) {
return this.activeHighlightColor === color ?
- <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
- <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+ <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+ <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
}
})}
</div>
</div>;
return (
- <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
);
}
@@ -614,7 +619,7 @@ export default class RichTextMenu extends AntimodeMenu {
</div>;
return (
- <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
+ <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
);
}
@@ -752,7 +757,7 @@ export default class RichTextMenu extends AntimodeMenu {
render() {
- const row1 = <div className="antimodeMenu-row">{[
+ const row1 = <div className="antimodeMenu-row" key="row1">{[
this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
@@ -766,14 +771,14 @@ export default class RichTextMenu extends AntimodeMenu {
this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
]}</div>;
- const row2 = <div className="antimodeMenu-row row-2">
- <div>
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions),
- this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions),
- this.createNodesDropdown(this.activeListType, this.listTypeOptions)]}
+ const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+ <div key="row">
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
+ this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
+ this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
</div>
- <div>
- <button className="antimodeMenu-button" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+ <div key="button">
+ <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
</button>
{this.getDragger()}
@@ -843,12 +848,12 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
</button> :
<>
{this.props.button}
- <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}>
+ <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
</>}
- {this.showDropdown ? this.props.dropdownContent : <></>}
+ {this.showDropdown ? this.props.dropdownContent : (null)}
</div>
);
}
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 29b378299..3b30b5b3f 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -2,14 +2,16 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from
import { schema } from "./RichTextSchema";
import { wrappingInputRule } from "./prosemirrorPatches";
import { NodeSelection, TextSelection } from "prosemirror-state";
-import { NumCast, Cast } from "../../new_fields/Types";
-import { Doc } from "../../new_fields/Doc";
+import { StrCast, Cast, NumCast } from "../../new_fields/Types";
+import { Doc, DataSym } from "../../new_fields/Doc";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { TooltipTextMenuManager } from "../util/TooltipTextMenu";
import { Docs, DocUtils } from "../documents/Documents";
import { Id } from "../../new_fields/FieldSymbols";
import { DocServer } from "../DocServer";
import { returnFalse, Utils } from "../../Utils";
+import RichTextMenu from "./RichTextMenu";
+import { RichTextField } from "../../new_fields/RichTextField";
+import { ComputedField } from "../../new_fields/ScriptField";
export const inpRules = {
rules: [
@@ -64,35 +66,75 @@ export const inpRules = {
// set the font size using #<font-size>
new InputRule(
- new RegExp(/^%([0-9]+)\s$/),
+ new RegExp(/%([0-9]+)\s$/),
(state, match, start, end) => {
const size = Number(match[1]);
- const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
- if (ruleProvider && heading) {
- (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size;
- return state.tr.deleteRange(start, end);
- }
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
- // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode)
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc
new InputRule(
- new RegExp(/@$/),
+ new RegExp(/\[\[([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\]\]$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null;
-
- const value = state.doc.textBetween(start, end);
- if (value) {
- DocServer.GetRefField(value).then(docx => {
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500, }, value);
- DocUtils.Publish(target, value, returnFalse, returnFalse);
- DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", "");
- });
- const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value, targetId: value });
- return state.tr.addMark(start, end, link);
+ const fieldKey = match[1];
+ const docid = match[2]?.substring(1);
+ if (!fieldKey) {
+ if (docid) {
+ DocServer.GetRefField(docid).then(docx => {
+ const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
+ DocUtils.Publish(target, docid, returnFalse, returnFalse);
+ DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", "");
+ });
+ const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
+ }
+ return state.tr;
}
- return state.tr;
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
+ // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document {{<layout>}} => show layout for this doc {{<layout> : Doc}} => show layout for another doc
+ new InputRule(
+ new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\}\}$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1];
+ const docid = match[2]?.substring(1);
+ if (!fieldKey && !docid) return state.tr;
+ docid && DocServer.GetRefField(docid).then(docx => {
+ if (!(docx instanceof Doc && docx)) {
+ const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
+ DocUtils.Publish(docx, docid, returnFalse, returnFalse);
+ }
+ });
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey, float: "right", alias: Utils.GenerateGuid() });
+ const sm = state.storedMarks || undefined;
+ return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ }),
+ new InputRule(
+ new RegExp(/##$/),
+ (state, match, start, end) => {
+ const schemaDoc = Doc.GetDataDoc((schema as any).Document);
+ const textDoc = Doc.GetProto(Cast(schemaDoc[DataSym], Doc, null)!);
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
+ const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+ const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, fontSize: 9, title: "inline comment" });
+ textDocInline.title = inlineFieldKey; // give the annotation its own title
+ textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+ textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+ textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+ textDocInline._textContext = ComputedField.MakeFunction(`copyField(this.${inlineFieldKey})`, { this: Doc.name });
+ textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+ textDoc[inlineFieldKey] = ""; // set a default value for the annotation
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" });
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced;
}),
// stop using active style
new InputRule(
@@ -175,12 +217,6 @@ export const inpRules = {
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleAlign_" + heading] = "center";
- return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }
const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
@@ -191,12 +227,6 @@ export const inpRules = {
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleAlign_" + heading] = "left";
- return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }
const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
@@ -207,29 +237,11 @@ export const inpRules = {
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleAlign_" + heading] = "right";
- return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }
const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
new InputRule(
- new RegExp(/%#$/),
- (state, match, start, end) => {
- const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" });
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docid: target[Id] });
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" });
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end)));
- }),
- new InputRule(
new RegExp(/%\(/),
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
@@ -264,7 +276,7 @@ export const inpRules = {
new RegExp(/%[a-z]+$/),
(state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
- const marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+ const marks = RichTextMenu.Instance._brushMap.get(color);
if (marks) {
const tr = state.tr.deleteRange(start, end);
return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index ef90a7294..c07ebe2ed 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,4 +1,4 @@
-import { reaction, IReactionDisposer } from "mobx";
+import { reaction, IReactionDisposer, observable, runInAction } from "mobx";
import { baseKeymap, toggleMark } from "prosemirror-commands";
import { redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
@@ -8,7 +8,7 @@ import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-s
import { StepMap } from "prosemirror-transform";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
-import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { Doc, WidthSym, HeightSym, DataSym, Field } from "../../new_fields/Doc";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { DocumentView } from "../views/nodes/DocumentView";
@@ -16,8 +16,13 @@ import { DocumentManager } from "./DocumentManager";
import ParagraphNodeSpec from "./ParagraphNodeSpec";
import { Transform } from "./Transform";
import React = require("react");
-import { BoolCast, NumCast, Cast } from "../../new_fields/Types";
+import { BoolCast, NumCast, StrCast } from "../../new_fields/Types";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { ObjectField } from "../../new_fields/ObjectField";
+import { ComputedField } from "../../new_fields/ScriptField";
+import { observer } from "mobx-react";
+import { Id } from "../../new_fields/FieldSymbols";
+import { OnChangeHandler } from "react-color/lib/components/common/ColorWrap";
const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -165,7 +170,23 @@ export const nodes: { [index: string]: NodeSpec } = {
float: { default: "right" },
location: { default: "onRight" },
hidden: { default: false },
+ fieldKey: { default: "" },
docid: { default: "" },
+ alias: { default: "" }
+ },
+ group: "inline",
+ draggable: false,
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+ return ["div", { ...node.attrs, ...attrs }];
+ }
+ },
+
+ dashField: {
+ inline: true,
+ attrs: {
+ fieldKey: { default: "" },
+ docid: { default: "" }
},
group: "inline",
draggable: false,
@@ -306,7 +327,7 @@ export const marks: { [index: string]: MarkSpec } = {
}
}],
toDOM(node: any) {
- return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }];
+ return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
}
},
@@ -669,7 +690,7 @@ export class DashDocCommentView {
if (target) {
const expand = target.hidden;
const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
- view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
+ view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
@@ -678,7 +699,7 @@ export class DashDocCommentView {
e.stopPropagation();
};
this._collapsed.onpointerenter = (e: any) => {
- DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
};
@@ -703,7 +724,7 @@ export class DashDocView {
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
}
- contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1;
+ contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this._textBox = tbox;
@@ -721,12 +742,6 @@ export class DashDocView {
this._dashSpan.style.height = node.attrs.height;
this._dashSpan.style.position = "absolute";
this._dashSpan.style.display = "inline-block";
- const removeDoc = () => {
- const pos = getPos();
- const ns = new NodeSelection(view.state.doc.resolve(pos));
- view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
- return true;
- };
this._dashSpan.onpointerleave = () => {
const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
if (ele) {
@@ -739,47 +754,26 @@ export class DashDocView {
(ele as HTMLDivElement).style.backgroundColor = "orange";
}
};
- DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
- if (dashDoc instanceof Doc) {
- self._dashDoc = dashDoc;
- dashDoc.hideSidebar = true;
- if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") {
- try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
- } catch (e) {
- console.log(e);
+ const removeDoc = () => {
+ const pos = getPos();
+ const ns = new NodeSelection(view.state.doc.resolve(pos));
+ view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+ return true;
+ };
+ const alias = node.attrs.alias;
+
+ const docid = node.attrs.docid || tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
+ DocServer.GetRefField(docid + alias).then(async dashDoc => {
+ if (!(dashDoc instanceof Doc)) {
+ alias && DocServer.GetRefField(docid).then(async dashDocBase => {
+ if (dashDocBase instanceof Doc) {
+ const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
+ aliasedDoc.layoutKey = "layout" + (node.attrs.fieldKey ? "_" + node.attrs.fieldKey : "");
+ self.doRender(aliasedDoc, removeDoc, node, view, getPos);
}
- }
- this._reactionDisposer && this._reactionDisposer();
- this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => {
- this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px";
- this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px";
});
- ReactDOM.render(<DocumentView
- Document={dashDoc}
- LibraryPath={tbox.props.LibraryPath}
- fitToBox={BoolCast(dashDoc.fitToBox)}
- addDocument={returnFalse}
- removeDocument={removeDoc}
- ruleProvider={undefined}
- ScreenToLocalTransform={this.getDocTransform}
- addDocTab={self._textBox.props.addDocTab}
- pinToPres={returnFalse}
- renderDepth={1}
- PanelWidth={self._dashDoc[WidthSym]}
- PanelHeight={self._dashDoc[HeightSym]}
- focus={self.outerFocus}
- backgroundColor={returnEmptyString}
- parentActive={returnFalse}
- whenActiveChanged={returnFalse}
- bringToFront={emptyFunction}
- zoomToScale={emptyFunction}
- getScale={returnOne}
- dontRegisterView={false}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ContentScaling={this.contentScaling}
- />, this._dashSpan);
+ } else {
+ self.doRender(dashDoc, removeDoc, node, view, getPos);
}
});
const self = this;
@@ -795,11 +789,142 @@ export class DashDocView {
this._outer.appendChild(this._dashSpan);
(this as any).dom = this._outer;
}
+ doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
+ this._dashDoc = dashDoc;
+ if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
+ try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ const self = this;
+ const finalLayout = Doc.expandTemplateLayout(dashDoc, !Doc.AreProtosEqual(this._textBox.dataDoc, this._textBox.Document) ? this._textBox.dataDoc : undefined);
+ if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
+ else {
+ const layoutKey = StrCast(finalLayout.layoutKey);
+ const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
+ if (finalLayout !== dashDoc && finalKey) {
+ const finalLayoutField = finalLayout[finalKey];
+ if (finalLayoutField instanceof ObjectField) {
+ finalLayout._textTemplate = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
+ }
+ }
+ this._reactionDisposer?.();
+ this._reactionDisposer = reaction(() => [finalLayout[WidthSym](), finalLayout[HeightSym]()], (dim) => {
+ this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px";
+ this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
+ }, { fireImmediately: true });
+ ReactDOM.render(<DocumentView
+ Document={finalLayout}
+ DataDoc={!node.attrs.docid ? this._textBox.dataDoc : undefined}
+ LibraryPath={this._textBox.props.LibraryPath}
+ fitToBox={BoolCast(dashDoc._fitToBox)}
+ addDocument={returnFalse}
+ removeDocument={removeDoc}
+ ScreenToLocalTransform={this.getDocTransform}
+ addDocTab={this._textBox.props.addDocTab}
+ pinToPres={returnFalse}
+ renderDepth={1}
+ PanelWidth={finalLayout[WidthSym]}
+ PanelHeight={finalLayout[HeightSym]}
+ focus={this.outerFocus}
+ backgroundColor={returnEmptyString}
+ parentActive={returnFalse}
+ whenActiveChanged={returnFalse}
+ bringToFront={emptyFunction}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}
+ dontRegisterView={false}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ContentScaling={this.contentScaling}
+ />, this._dashSpan);
+ }
+ }
destroy() {
this._reactionDisposer && this._reactionDisposer();
}
}
+
+export class DashFieldView {
+ _fieldWrapper: HTMLDivElement;
+ _labelSpan: HTMLSpanElement;
+ _fieldSpan: HTMLDivElement;
+ _reactionDisposer: IReactionDisposer | undefined;
+ _textBoxDoc: Doc;
+ @observable _dashDoc: Doc | undefined;
+ _fieldKey: string;
+
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this._fieldKey = node.attrs.fieldKey;
+ this._textBoxDoc = tbox.props.Document;
+ this._fieldWrapper = document.createElement("div");
+ this._fieldWrapper.style.width = node.attrs.width;
+ this._fieldWrapper.style.height = node.attrs.height;
+ this._fieldWrapper.style.position = "relative";
+ this._fieldWrapper.style.display = "inline-block";
+
+ this._fieldSpan = document.createElement("div");
+ this._fieldSpan.id = Utils.GenerateGuid();
+ this._fieldSpan.contentEditable = "true";
+ this._fieldSpan.style.position = "relative";
+ this._fieldSpan.style.display = "inline-block";
+ this._fieldSpan.style.minWidth = "50px";
+ this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
+ this._fieldSpan.addEventListener("input", this.onchanged);
+ this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+ this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
+ this._fieldSpan.onmousedown = function (e: any) {
+ console.log(e);
+ e.stopPropagation();
+ };
+ const self = this;
+ this._fieldSpan.onkeydown = function (e: any) {
+ e.stopPropagation();
+ if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
+ if (window.getSelection) {
+ var range = document.createRange();
+ range.selectNodeContents(self._fieldSpan);
+ window.getSelection()!.removeAllRanges();
+ window.getSelection()!.addRange(range);
+ }
+ e.preventDefault();
+ }
+ };
+
+ this._labelSpan = document.createElement("span");
+ this._labelSpan.style.position = "relative";
+ this._labelSpan.style.display = "inline";
+ this._labelSpan.style.fontWeight = "bold";
+ this._labelSpan.style.fontSize = "larger";
+ this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `;
+ if (node.attrs.docid) {
+ const self = this;
+ DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => self._dashDoc = dashDoc));
+ } else {
+ this._dashDoc = tbox.props.DataDoc || tbox.dataDoc;
+ }
+ this._reactionDisposer?.();
+ this._reactionDisposer = reaction(() => this._dashDoc?.[node.attrs.fieldKey], fval => this._fieldSpan.innerHTML = Field.toString(fval as Field) || "(null)", { fireImmediately: true });
+
+ this._fieldWrapper.appendChild(this._labelSpan);
+ this._fieldWrapper.appendChild(this._fieldSpan);
+ (this as any).dom = this._fieldWrapper;
+ }
+ onchanged = () => {
+ this._reactionDisposer?.();
+ this._dashDoc![this._fieldKey] = this._fieldSpan.innerText;
+ this._reactionDisposer = reaction(() => this._dashDoc?.[this._fieldKey], fval => this._fieldSpan.innerHTML = Field.toString(fval as Field) || "(null)");
+
+ }
+ destroy() {
+ this._reactionDisposer?.();
+ }
+ selectNode() { }
+}
+
export class OrderedListView {
update(node: any) {
return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
@@ -855,7 +980,8 @@ export class FootnoteView {
}),
new Plugin({
view(newView) {
- return FormattedTextBox.getToolTip(newView);
+ // TODO -- make this work with RichTextMenu
+ // return FormattedTextBox.getToolTip(newView);
}
})
],
@@ -963,7 +1089,7 @@ export class SummaryView {
view.dispatch(view.state.tr.
setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
- setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+ setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
e.preventDefault();
e.stopPropagation();
this._collapsed.className = this.className(visible);
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 86a7a620e..4612f10f4 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,8 +3,6 @@ import { Doc } from "../../new_fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { computedFn } from "mobx-utils";
import { List } from "../../new_fields/List";
-import { DocumentDecorations } from "../views/DocumentDecorations";
-import RichTextMenu from "./RichTextMenu";
export namespace SelectionManager {
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
new file mode 100644
index 000000000..7a0fb0741
--- /dev/null
+++ b/src/client/util/SettingsManager.scss
@@ -0,0 +1,136 @@
+@import "../views/globalCssVariables";
+
+.dialogue-box {
+ background-color: whitesmoke !important;
+ color: grey;
+ width: 450px;
+ height: 300px;
+
+ button {
+ background: $lighter-alt-accent;
+ outline: none;
+ border-radius: 5px;
+ border: 0px;
+ color: #fcfbf7;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ padding: 10px;
+ margin: 10px;
+ transition: transform 0.2s;
+ margin: 2px;
+ }
+}
+
+.settings-interface {
+ display: flex;
+ flex-direction: column;
+
+ button {
+ width: 100%;
+ align-self: center;
+ background: $darker-alt-accent;
+ margin-top: 4px;
+ }
+
+ .delete-button {
+ background: rgb(227, 86, 86);
+ }
+
+ .close-button {
+ position: absolute;
+ right: 1em;
+ top: 1em;
+ }
+
+ .settings-heading {
+ letter-spacing: .5em;
+ }
+
+
+ .settings-body {
+ display: flex;
+ justify-content: space-between;
+
+ .settings-type {
+ display: flex;
+ flex-direction: column;
+ flex-basis: 30%;
+
+ }
+
+ .settings-content {
+ padding-left: 1em;
+ padding-right: 1em;
+ display: flex;
+ flex-direction: column;
+ flex-basis: 70%;
+ justify-content: space-around;
+ text-align: left;
+
+ ::placeholder {
+ color: $intermediate-color;
+ }
+
+ input {
+ border-radius: 5px;
+ border: none;
+ padding: 4px;
+ min-width: 100%;
+ margin: 2px 0;
+ }
+
+ .error-text {
+ color: #C40233;
+ }
+
+ .success-text {
+ color: #009F6B;
+ }
+
+ p {
+ padding: 0 0 .1em .2em;
+ }
+
+ }
+ }
+
+ .focus-span {
+ text-decoration: underline;
+ }
+
+ h1 {
+ color: $dark-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 120%;
+ }
+
+ .container {
+ display: block;
+ position: relative;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 22px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ width: 700px;
+ min-width: 700px;
+ max-width: 700px;
+ text-align: left;
+ font-style: normal;
+ font-size: 15;
+ font-weight: normal;
+ padding: 0;
+
+ .padding {
+ padding: 0 0 0 20px;
+ color: black;
+ }
+
+
+
+ }
+} \ No newline at end of file
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
new file mode 100644
index 000000000..ff0b22381
--- /dev/null
+++ b/src/client/util/SettingsManager.tsx
@@ -0,0 +1,131 @@
+import { observable, runInAction, action } from "mobx";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { observer } from "mobx-react";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import * as fa from '@fortawesome/free-solid-svg-icons';
+import { SelectionManager } from "./SelectionManager";
+import "./SettingsManager.scss";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Networking } from "../Network";
+
+library.add(fa.faWindowClose);
+
+@observer
+export default class SettingsManager extends React.Component<{}> {
+ public static Instance: SettingsManager;
+ @observable private isOpen = false;
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
+ @observable private settingsContent = "password";
+ @observable private errorText = "";
+ @observable private successText = "";
+ private curr_password_ref = React.createRef<HTMLInputElement>();
+ private new_password_ref = React.createRef<HTMLInputElement>();
+ private new_confirm_ref = React.createRef<HTMLInputElement>();
+
+ public open = action(() => {
+ SelectionManager.DeselectAll();
+ this.isOpen = true;
+ });
+
+ public close = action(() => {
+ this.isOpen = false;
+ });
+
+ constructor(props: {}) {
+ super(props);
+ SettingsManager.Instance = this;
+ }
+
+ @action
+ private dispatchRequest = async () => {
+ const curr_pass = this.curr_password_ref.current?.value;
+ const new_pass = this.new_password_ref.current?.value;
+ const new_confirm = this.new_confirm_ref.current?.value;
+
+ if (!(curr_pass && new_pass && new_confirm)) {
+ this.changeAlertText("Hey, we're missing some fields!", "");
+ return;
+ }
+
+ const passwordBundle = {
+ curr_pass,
+ new_pass,
+ new_confirm
+ };
+
+ const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ if (error) {
+ this.changeAlertText("Uh oh! " + error[0].msg + "...", "");
+ return;
+ }
+
+ this.changeAlertText("", "Password successfully updated!");
+ }
+
+ @action
+ private changeAlertText = (errortxt: string, successtxt: string) => {
+ this.errorText = errortxt;
+ this.successText = successtxt;
+ }
+
+ @action
+ onClick = (event: any) => {
+ this.settingsContent = event.currentTarget.value;
+ this.errorText = "";
+ this.successText = "";
+ }
+
+ private get settingsInterface() {
+ return (
+ <div className={"settings-interface"}>
+ <div className="settings-heading">
+ <h1>settings</h1>
+ <div className={"close-button"} onClick={this.close}>
+ <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+ </div>
+ </div>
+ <div className="settings-body">
+ <div className="settings-type">
+ <button onClick={this.onClick} value="password">reset password</button>
+ <button onClick={this.onClick} value="data">reset data</button>
+ </div>
+ {this.settingsContent === "password" ?
+ <div className="settings-content">
+ <input placeholder="current password" ref={this.curr_password_ref} />
+ <input placeholder="new password" ref={this.new_password_ref} />
+ <input placeholder="confirm new password" ref={this.new_confirm_ref} />
+ {this.errorText ? <div className="error-text">{this.errorText}</div> : undefined}
+ {this.successText ? <div className="success-text">{this.successText}</div> : undefined}
+ <button onClick={this.dispatchRequest}>submit</button>
+ <a href="/forgotPassword">forgot password?</a>
+
+ </div>
+ : undefined}
+ {this.settingsContent === "data" ?
+ <div className="settings-content">
+ <p>WARNING: <br />
+ THIS WILL ERASE ALL YOUR CURRENT DOCUMENTS STORED ON DASH. IF YOU WISH TO PROCEED, CLICK THE BUTTON BELOW.</p>
+ <button className="delete-button">DELETE</button>
+ </div>
+ : undefined}
+ </div>
+
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ contents={this.settingsInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/util/TooltipLinkingMenu.tsx b/src/client/util/TooltipLinkingMenu.tsx
deleted file mode 100644
index b46675a04..000000000
--- a/src/client/util/TooltipLinkingMenu.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { EditorState } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import "./TooltipTextMenu.scss";
-
-//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
-export class TooltipLinkingMenu {
-
- private tooltip: HTMLElement;
- private view: EditorView;
- private editorProps: FieldViewProps;
-
- constructor(view: EditorView, editorProps: FieldViewProps) {
- this.view = view;
- this.editorProps = editorProps;
- this.tooltip = document.createElement("div");
- this.tooltip.className = "tooltipMenu linking";
-
- //add the div which is the tooltip
- view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
-
- const target = "https://www.google.com";
-
- const link = document.createElement("a");
- link.href = target;
- link.textContent = target;
- link.target = "_blank";
- link.style.color = "white";
- this.tooltip.appendChild(link);
-
- this.update(view, undefined);
- }
-
- //updates the tooltip menu when the selection changes
- update(view: EditorView, lastState: EditorState | undefined) {
- const state = view.state;
- // Don't do anything if the document/selection didn't change
- if (lastState && lastState.doc.eq(state.doc) &&
- lastState.selection.eq(state.selection)) return;
-
- // Hide the tooltip if the selection is empty
- if (state.selection.empty) {
- this.tooltip.style.display = "none";
- return;
- }
-
- console.log("STORED:");
- console.log(state.doc.content.firstChild!.content);
-
- // Otherwise, reposition it and update its content
- this.tooltip.style.display = "";
- const { from, to } = state.selection;
- const start = view.coordsAtPos(from), end = view.coordsAtPos(to);
- // The box in which the tooltip is positioned, to use as base
- const box = this.tooltip.offsetParent!.getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- const left = Math.max((start.left + end.left) / 2, start.left + 3);
- this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
- const width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
- const mid = Math.min(start.left, end.left) + width;
-
- this.tooltip.style.width = "auto";
- this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
- }
-
- destroy() { this.tooltip.remove(); }
-}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
deleted file mode 100644
index 2a38fe118..000000000
--- a/src/client/util/TooltipTextMenu.scss
+++ /dev/null
@@ -1,372 +0,0 @@
-@import "../views/globalCssVariables";
-.ProseMirror-menu-dropdown-wrap {
- display: inline-block;
- position: relative;
-}
-
-.ProseMirror-menu-dropdown {
- vertical-align: 1px;
- cursor: pointer;
- position: relative;
- padding: 0 15px 0 4px;
- background: white;
- border-radius: 2px;
- text-align: left;
- font-size: 12px;
- white-space: nowrap;
- margin-right: 4px;
-
- &:after {
- content: "";
- border-left: 4px solid transparent;
- border-right: 4px solid transparent;
- border-top: 4px solid currentColor;
- opacity: .6;
- position: absolute;
- right: 4px;
- top: calc(50% - 2px);
- }
-}
-
-.ProseMirror-menu-submenu-wrap {
- position: relative;
- margin-right: -4px;
-}
-
-.ProseMirror-menu-dropdown-menu,
-.ProseMirror-menu-submenu {
- font-size: 12px;
- background: white;
- border: 1px solid rgb(223, 223, 223);
- min-width: 40px;
- z-index: 50000;
- position: absolute;
- box-sizing: content-box;
-
- .ProseMirror-menu-dropdown-item {
- cursor: pointer;
- z-index: 100000;
- text-align: left;
- padding: 3px;
-
- &:hover {
- background-color: $light-color-secondary;
- }
- }
-}
-
-
-.ProseMirror-menu-submenu-label:after {
- content: "";
- border-top: 4px solid transparent;
- border-bottom: 4px solid transparent;
- border-left: 4px solid currentColor;
- opacity: .6;
- position: absolute;
- right: 4px;
- top: calc(50% - 4px);
-}
-
- .ProseMirror-icon {
- display: inline-block;
- // line-height: .8;
- // vertical-align: -2px; /* Compensate for padding */
- // padding: 2px 8px;
- cursor: pointer;
-
- &.ProseMirror-menu-disabled {
- cursor: default;
- }
-
- svg {
- fill:white;
- height: 1em;
- }
-
- span {
- vertical-align: text-top;
- }
- }
-
-.wrapper {
- position: absolute;
- pointer-events: all;
- display: flex;
- align-items: center;
- transform: translateY(-85px);
-
- height: 35px;
- background: #323232;
- border-radius: 6px;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-}
-
-.tooltipMenu, .basic-tools {
- z-index: 20000;
- pointer-events: all;
- padding: 3px;
- padding-bottom: 5px;
- display: flex;
- align-items: center;
-
- .ProseMirror-example-setup-style hr {
- padding: 2px 10px;
- border: none;
- margin: 1em 0;
- }
-
- .ProseMirror-example-setup-style hr:after {
- content: "";
- display: block;
- height: 1px;
- background-color: silver;
- line-height: 2px;
- }
-}
-
-.menuicon {
- width: 25px;
- height: 25px;
- cursor: pointer;
- text-align: center;
- line-height: 25px;
- margin: 0 2px;
- border-radius: 3px;
-
- &:hover {
- background-color: black;
-
- #link-drag {
- background-color: black;
- }
- }
-
- &> * {
- margin-top: 50%;
- margin-left: 50%;
- transform: translate(-50%, -50%);
- }
-
- svg {
- fill: inherit;
- width: 18px;
- height: 18px;
- }
-}
-
-.menuicon-active {
- width: 25px;
- height: 25px;
- cursor: pointer;
- text-align: center;
- line-height: 25px;
- margin: 0 2px;
- border-radius: 3px;
-
- &:hover {
- background-color: black;
- }
-
- &> * {
- margin-top: 50%;
- margin-left: 50%;
- transform: translate(-50%, -50%);
- }
-
- svg {
- fill: greenyellow;
- width: 18px;
- height: 18px;
- }
-}
-
-.colorPicker {
- position: relative;
-
- svg {
- width: 18px;
- height: 18px;
- // margin-top: 11px;
- }
-
- .buttonColor {
- position: absolute;
- top: 24px;
- left: 1px;
- width: 24px;
- height: 4px;
- margin-top: 0;
- }
-}
-
-#link-drag {
- background-color: #323232;
-}
-
-.underline svg {
- margin-top: 13px;
-}
-
- .font-size-indicator {
- font-size: 12px;
- padding-right: 0px;
- }
- .summarize{
- color: white;
- height: 20px;
- text-align: center;
- }
-
-
-.brush{
- display: inline-block;
- width: 1em;
- height: 1em;
- stroke-width: 0;
- stroke: currentColor;
- fill: currentColor;
- margin-right: 15px;
-}
-
-.brush-active{
- display: inline-block;
- width: 1em;
- height: 1em;
- stroke-width: 3;
- fill: greenyellow;
- margin-right: 15px;
-}
-
-.dragger-wrapper {
- color: #eee;
- height: 22px;
- padding: 0 5px;
- box-sizing: content-box;
- cursor: grab;
-
- .dragger {
- width: 18px;
- height: 100%;
- display: flex;
- justify-content: space-evenly;
- }
-
- .dragger-line {
- width: 2px;
- height: 100%;
- background-color: black;
- }
-}
-
-.button-dropdown-wrapper {
- display: flex;
- align-content: center;
-
- &:hover {
- background-color: black;
- }
-}
-
-.buttonSettings-dropdown {
-
- &.ProseMirror-menu-dropdown {
- width: 10px;
- height: 25px;
- margin: 0;
- padding: 0 2px;
- background-color: #323232;
- text-align: center;
-
- &:after {
- border-top: 4px solid white;
- right: 2px;
- }
-
- &:hover {
- background-color: black;
- }
- }
-
- &.ProseMirror-menu-dropdown-menu {
- min-width: 150px;
- left: -27px;
- top: 31px;
- background-color: #323232;
- border: 1px solid #4d4d4d;
- color: $light-color-secondary;
- // border: none;
- // border: 1px solid $light-color-secondary;
- border-radius: 0 6px 6px 6px;
- padding: 3px;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
- .ProseMirror-menu-dropdown-item{
- cursor: default;
-
- &:last-child {
- border-bottom: none;
- }
-
- &:hover {
- background-color: #323232;
- }
-
- .button-setting, .button-setting-disabled {
- padding: 2px;
- border-radius: 2px;
- }
-
- .button-setting:hover {
- cursor: pointer;
- background-color: black;
- }
-
- .separated-button {
- border-top: 1px solid $light-color-secondary;
- padding-top: 6px;
- }
-
- input {
- color: black;
- border: none;
- border-radius: 1px;
- padding: 3px;
- }
-
- button {
- padding: 6px;
- background-color: #323232;
- border: 1px solid black;
- border-radius: 1px;
-
- &:hover {
- background-color: black;
- }
- }
- }
-
-
- }
-}
-
-.colorPicker-wrapper {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- margin-top: 3px;
- margin-left: -3px;
- width: calc(100% + 6px);
-}
-
-button.colorPicker {
- width: 20px;
- height: 20px;
- border-radius: 15px !important;
- margin: 3px;
- border: none !important;
-
- &.active {
- border: 2px solid white !important;
- }
-} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
deleted file mode 100644
index 1c15dca7f..000000000
--- a/src/client/util/TooltipTextMenu.tsx
+++ /dev/null
@@ -1,1042 +0,0 @@
-import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc, Field, Opt } from "../../new_fields/Doc";
-import { Utils } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { LinkManager } from "./LinkManager";
-import { schema } from "./RichTextSchema";
-import "./TooltipTextMenu.scss";
-import { Cast, NumCast, StrCast } from '../../new_fields/Types';
-import { updateBullets } from './ProsemirrorExampleTransfer';
-import { DocumentDecorations } from '../views/DocumentDecorations';
-import { SelectionManager } from './SelectionManager';
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-const { toggleMark } = require("prosemirror-commands");
-
-//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
-export class TooltipTextMenu {
- public static Toolbar: HTMLDivElement | undefined;
-
- // editor state properties
- private view: EditorView;
- private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
-
- private fontStyles: Mark[] = [];
- private fontSizes: Mark[] = [];
- private _marksToDoms: Map<MarkType, HTMLSpanElement> = new Map();
- private _collapsed: boolean = false;
-
- // editor doms
- public tooltip: HTMLElement = document.createElement("div");
- private wrapper: HTMLDivElement = document.createElement("div");
-
- // editor button doms
- private colorDom?: Node;
- private colorDropdownDom?: Node;
- private linkDom?: Node;
- private highighterDom?: Node;
- private highlighterDropdownDom?: Node;
- private linkDropdownDom?: Node;
- private _brushdom?: Node;
- private _brushDropdownDom?: Node;
- private fontSizeDom?: Node;
- private fontStyleDom?: Node;
- private basicTools?: HTMLElement;
-
- static createDiv(className: string) { const div = document.createElement("div"); div.className = className; return div; }
- static createSpan(className: string) { const div = document.createElement("span"); div.className = className; return div; }
- constructor(view: EditorView) {
- this.view = view;
-
- // initialize the tooltip -- sets this.tooltip
- this.initTooltip(view);
-
- // initialize the wrapper
- this.wrapper = TooltipTextMenu.createDiv("wrapper");
- this.wrapper.appendChild(this.tooltip);
-
- TooltipTextMenu.Toolbar = this.wrapper;
- }
-
- private async initTooltip(view: EditorView) {
- const self = this;
- this.tooltip = TooltipTextMenu.createDiv("tooltipMenu");
- this.basicTools = TooltipTextMenu.createDiv("basic-tools");
-
- const svgIcon = (name: string, title: string = name, dpath: string) => {
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("viewBox", "-100 -100 650 650");
- const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
- path.setAttributeNS(null, "d", dpath);
- svg.appendChild(path);
-
- const span = TooltipTextMenu.createSpan(name + " menuicon");
- span.title = title;
- span.appendChild(svg);
-
- return span;
- };
-
- const basicItems = [ // init basicItems in minimized toolbar -- paths to svgs are obtained from fontawesome
- { mark: schema.marks.strong, dom: svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
- { mark: schema.marks.em, dom: svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
- { mark: schema.marks.underline, dom: svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
- ];
- const items = [ // init items in full size toolbar
- { mark: schema.marks.strikethrough, dom: svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
- { mark: schema.marks.superscript, dom: svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
- { mark: schema.marks.subscript, dom: svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
- ];
-
- basicItems.map(({ dom, mark }) => this.basicTools ?.appendChild(dom.cloneNode(true)));
- basicItems.concat(items).forEach(({ dom, mark }) => {
- this.tooltip.appendChild(dom);
- this._marksToDoms.set(mark, dom);
-
- //pointer down handler to activate button effects
- dom.addEventListener("pointerdown", e => {
- this.view.focus();
- if (dom.contains(e.target as Node)) {
- e.preventDefault();
- e.stopPropagation();
- toggleMark(mark)(this.view.state, this.view.dispatch, this.view);
- this.updateHighlightStateOfButtons();
- }
- });
- });
-
- // summarize menu
- this.highighterDom = this.createHighlightTool().render(this.view).dom;
- this.highlighterDropdownDom = this.createHighlightDropdown().render(this.view).dom;
- this.tooltip.appendChild(this.highighterDom);
- this.tooltip.appendChild(this.highlighterDropdownDom);
-
- // color menu
- this.colorDom = this.createColorTool().render(this.view).dom;
- this.colorDropdownDom = this.createColorDropdown().render(this.view).dom;
- this.tooltip.appendChild(this.colorDom);
- this.tooltip.appendChild(this.colorDropdownDom);
-
- // link menu
- this.linkDom = this.createLinkTool().render(this.view).dom;
- this.linkDropdownDom = this.createLinkDropdown("").render(this.view).dom;
- this.tooltip.appendChild(this.linkDom);
- this.tooltip.appendChild(this.linkDropdownDom);
-
- // list of font styles
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 7 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 8 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 9 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 16 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 18 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 20 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 24 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 }));
- this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 }));
-
- // font sizes
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
-
-
- // init brush tool
- this._brushdom = this.createBrushTool().render(this.view).dom;
- this.tooltip.appendChild(this._brushdom);
- this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
- this.tooltip.appendChild(this._brushDropdownDom);
-
- // summarizer tool
- const summarizer = new MenuItem({
- title: "Summarize",
- label: "Summarize",
- icon: icons.join,
- css: "fill:white;",
- class: "menuicon",
- execEvent: "",
- run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
- });
- this.tooltip.appendChild(summarizer.render(this.view).dom);
-
- // list types dropdown
- const listDropdownTypes = [{ mapStyle: "bullet", label: ":" }, { mapStyle: "decimal", label: "1.1" }, { mapStyle: "multi", label: "A.1" }, { label: "X" }];
- const listTypes = new Dropdown(listDropdownTypes.map(({ mapStyle, label }) =>
- new MenuItem({
- title: "Set Bullet Style",
- label: label,
- execEvent: "",
- class: "dropdown-item",
- css: "color: black; width: 40px;",
- enable() { return true; },
- run() {
- const marks = self.view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
- if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
- const tx3 = updateBullets(tx2, schema, mapStyle);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
-
- view.dispatch(tx2);
- })) {
- const tx2 = view.state.tr;
- const tx3 = updateBullets(tx2, schema, mapStyle);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
-
- view.dispatch(tx3);
- }
- }
- })), { label: ":", css: "color:black; width: 40px;" });
- this.tooltip.appendChild(listTypes.render(this.view).dom);
-
- await this.updateFromDash(view, undefined, undefined);
-
- const draggerWrapper = TooltipTextMenu.createDiv("dragger-wrapper");
- const dragger = TooltipTextMenu.createDiv("dragger");
- dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
- dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
- dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
- draggerWrapper.appendChild(dragger);
- this.wrapper.appendChild(draggerWrapper);
- this.setupDragElementInteractions(draggerWrapper);
- }
-
- setupDragElementInteractions(elmnt: HTMLElement) {
- var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
- if (elmnt) {
- // if present, the header is where you move the DIV from:
- elmnt.onpointerdown = dragPointerDown;
- elmnt.ondblclick = onClick;
- }
- const self = this;
-
- function dragPointerDown(e: PointerEvent) {
- e = e || window.event;
- e.preventDefault();
- // get the mouse cursor position at startup:
- pos3 = e.clientX;
- pos4 = e.clientY;
- document.onpointerup = closeDragElement;
- // call a function whenever the cursor moves:
- document.onpointermove = elementDrag;
- }
-
- function onClick(e: MouseEvent) {
- self._collapsed = !self._collapsed;
- const children = self.wrapper.childNodes;
- if (self._collapsed && children.length > 0) {
- self.wrapper.removeChild(self.tooltip);
- self.basicTools && self.wrapper.prepend(self.basicTools);
- }
- else {
- self.wrapper.prepend(self.tooltip);
- self.basicTools && self.wrapper.removeChild(self.basicTools);
- }
- }
-
- function elementDrag(e: PointerEvent) {
- e = e || window.event;
- //e.preventDefault();
- // calculate the new cursor position:
- pos1 = pos3 - e.clientX;
- pos2 = pos4 - e.clientY;
- pos3 = e.clientX;
- pos4 = e.clientY;
- // set the element's new position:
- // elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
- // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
-
- self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px";
- self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px";
- }
-
- function closeDragElement() {
- // stop moving when mouse button is released:
- document.onpointerup = null;
- document.onpointermove = null;
- }
- }
-
- //label of dropdown will change to given label
- updateFontSizeDropdown(label: string) {
- //font SIZES
- const fontSizeBtns: MenuItem[] = [];
- const self = this;
- this.fontSizes.forEach(mark =>
- fontSizeBtns.push(new MenuItem({
- title: "Set Font Size",
- label: String(mark.attrs.fontSize),
- execEvent: "",
- class: "dropdown-item",
- css: "color: black; width: 50px;",
- enable() { return true; },
- run() {
- const size = mark.attrs.fontSize;
- if (size) { self.updateFontSizeDropdown(String(size) + " pt"); }
- if (self.editorProps) {
- const ruleProvider = self.editorProps.ruleProvider;
- const heading = NumCast(self.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleSize_" + heading] = size;
- }
- }
- TooltipTextMenu.setMark(self.view.state.schema.marks.pFontSize.create({ fontSize: size }), self.view.state, self.view.dispatch);
- }
- })));
-
- const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom;
- if (this.fontSizeDom) {
- this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom);
- }
- else {
- this.tooltip.appendChild(newfontSizeDom);
- }
- this.fontSizeDom = newfontSizeDom;
- }
-
- //label of dropdown will change to given label
- updateFontStyleDropdown(label: string) {
- //font STYLES
- const fontBtns: MenuItem[] = [];
- const self = this;
- this.fontStyles.forEach(mark =>
- fontBtns.push(new MenuItem({
- title: "Set Font Family",
- label: mark.attrs.family,
- execEvent: "",
- class: "dropdown-item",
- css: "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;",
- enable() { return true; },
- run() {
- const fontName = mark.attrs.family;
- if (fontName) { self.updateFontStyleDropdown(fontName); }
- if (self.editorProps) {
- const ruleProvider = self.editorProps.ruleProvider;
- const heading = NumCast(self.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleFont_" + heading] = fontName;
- }
- }
- TooltipTextMenu.setMark(self.view.state.schema.marks.pFontFamily.create({ family: fontName }), self.view.state, self.view.dispatch);
- }
- })));
-
- const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom;
- if (this.fontStyleDom) {
- this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom);
- }
- else {
- this.tooltip.appendChild(newfontStyleDom);
- }
- this.fontStyleDom = newfontStyleDom;
- }
- async getTextLinkTargetTitle() {
- const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type.name === "link");
- if (link) {
- const href = link.attrs.href;
- if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- if (linkclicked) {
- const linkDoc = await DocServer.GetRefField(linkclicked);
- if (linkDoc instanceof Doc) {
- const anchor1 = await Cast(linkDoc.anchor1, Doc);
- const anchor2 = await Cast(linkDoc.anchor2, Doc);
- const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
- if (currentDoc && anchor1 && anchor2) {
- if (Doc.AreProtosEqual(currentDoc, anchor1)) {
- return StrCast(anchor2.title);
- }
- if (Doc.AreProtosEqual(currentDoc, anchor2)) {
- return StrCast(anchor1.title);
- }
- }
- }
- }
- } else {
- return href;
- }
- } else {
- return link.attrs.title;
- }
- }
- }
-
- // LINK TOOL
- createLinkTool(active: boolean = false) {
- return new MenuItem({
- title: "Link tool",
- label: "Link tool",
- icon: icons.link,
- css: "fill:white;",
- class: active ? "menuicon-active" : "menuicon",
- execEvent: "",
- run: async (state, dispatch) => { },
- active: (state) => true
- });
- }
-
- createLinkDropdown(targetTitle: string) {
- const input = document.createElement("input");
-
- // menu item for input for hyperlink url
- // TODO: integrate search to allow users to search for a doc to link to
- const linkInfo = new MenuItem({
- title: "",
- execEvent: "",
- class: "button-setting-disabled",
- css: "",
- render() {
- const p = document.createElement("p");
- p.textContent = "Linked to:";
-
- input.type = "text";
- input.placeholder = "Enter URL";
- if (targetTitle) input.value = targetTitle;
- input.onclick = (e: MouseEvent) => {
- input.select();
- input.focus();
- };
-
- const div = document.createElement("div");
- div.appendChild(p);
- div.appendChild(input);
- return div;
- },
- enable() { return false; },
- run(p1, p2, p3, event) { event.stopPropagation(); }
- });
-
- // menu item to update/apply the hyperlink to the selected text
- const linkApply = new MenuItem({
- title: "",
- execEvent: "",
- class: "",
- css: "",
- render() {
- const button = document.createElement("button");
- button.className = "link-url-button";
- button.textContent = "Apply hyperlink";
- return button;
- },
- enable() { return false; },
- run: async (state, dispatch, view, event) => {
- event.stopPropagation();
- let node = this.view.state.selection.$from.nodeAfter;
- let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: input.value, location: "onRight" });
- this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
- this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
- node = this.view.state.selection.$from.nodeAfter;
- link = node && node.marks.find(m => m.type.name === "link");
-
- // update link menu
- const linkDom = self.createLinkTool(true).render(self.view).dom;
- const linkDropdownDom = self.createLinkDropdown(await self.getTextLinkTargetTitle()).render(self.view).dom;
- self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
- self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
- self.linkDom = linkDom;
- self.linkDropdownDom = linkDropdownDom;
- }
- });
-
- // menu item to remove the link
- // TODO: allow this to be undoable
- const self = this;
- const deleteLink = new MenuItem({
- title: "Delete link",
- execEvent: "",
- class: "separated-button",
- css: "",
- render() {
- const button = document.createElement("button");
- button.textContent = "Remove link";
-
- const wrapper = document.createElement("div");
- wrapper.appendChild(button);
- return wrapper;
- },
- enable() { return true; },
- async run() {
- // delete the link
- const node = self.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type === self.view.state.schema.marks.link);
- const href = link!.attrs.href;
- if (href ?.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- linkclicked && DocServer.GetRefField(linkclicked).then(async linkDoc => {
- if (linkDoc instanceof Doc) {
- LinkManager.Instance.deleteLink(linkDoc);
- self.view.dispatch(self.view.state.tr.removeMark(self.view.state.selection.from, self.view.state.selection.to, self.view.state.schema.marks.link));
- }
- });
- }
- // update link menu
- const linkDom = self.createLinkTool(false).render(self.view).dom;
- const linkDropdownDom = self.createLinkDropdown("").render(self.view).dom;
- self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
- self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
- self.linkDom = linkDom;
- self.linkDropdownDom = linkDropdownDom;
- }
- });
-
- return new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
- }
-
- public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
- const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
- this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
- addMark(this.view.state.selection.from, this.view.state.selection.to, link));
- return this.view.state.selection.$from.nodeAfter ?.text || "";
- }
-
- // SUMMARIZER TOOL
- static insertSummarizer(state: EditorState<any>, dispatch: any) {
- if (!state.selection.empty) {
- const mark = state.schema.marks.summarize.create();
- const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
- const content = tr.selection.content();
- const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
- dispatch ?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
- }
- }
-
- // HIGHLIGHTER TOOL
- createHighlightTool() {
- return new MenuItem({
- title: "Highlight",
- css: "fill:white;",
- class: "menuicon",
- execEvent: "",
- render() {
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("viewBox", "-100 -100 650 650");
- const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
- path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z");
- svg.appendChild(path);
-
- const color = TooltipTextMenu.createDiv("buttonColor");
- color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
-
- const wrapper = TooltipTextMenu.createDiv("colorPicker");
- wrapper.appendChild(svg);
- wrapper.appendChild(color);
- return wrapper;
- },
- run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch)
- });
- }
-
- static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
- if (!state.selection.empty) {
- toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
- }
- }
-
- createHighlightDropdown() {
- // menu item for color picker
- const self = this;
- const colors = new MenuItem({
- title: "",
- execEvent: "",
- class: "button-setting-disabled",
- css: "",
- render() {
- const p = document.createElement("p");
- p.textContent = "Change highlight:";
-
- const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
-
- const colors = [
- PastelSchemaPalette.get("pink2"),
- PastelSchemaPalette.get("purple4"),
- PastelSchemaPalette.get("bluegreen1"),
- PastelSchemaPalette.get("yellow4"),
- PastelSchemaPalette.get("red2"),
- PastelSchemaPalette.get("bluegreen7"),
- PastelSchemaPalette.get("bluegreen5"),
- PastelSchemaPalette.get("orange1"),
- "white",
- "transparent"
- ];
-
- colors.forEach(color => {
- const button = document.createElement("button");
- button.className = color === TooltipTextMenuManager.Instance.highlighter ? "colorPicker active" : "colorPicker";
- if (color) {
- button.style.backgroundColor = color;
- button.textContent = color === "transparent" ? "X" : "";
- button.onclick = e => {
- TooltipTextMenuManager.Instance.highlighter = color;
-
- TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, self.view.state, self.view.dispatch);
-
- // update color menu
- const highlightDom = self.createHighlightTool().render(self.view).dom;
- const highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom;
- self.highighterDom && self.tooltip.replaceChild(highlightDom, self.highighterDom);
- self.highlighterDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlighterDropdownDom);
- self.highighterDom = highlightDom;
- self.highlighterDropdownDom = highlightDropdownDom;
- };
- }
- colorsWrapper.appendChild(button);
- });
-
- const div = document.createElement("div");
- div.appendChild(p);
- div.appendChild(colorsWrapper);
- return div;
- },
- enable() { return false; },
- run(p1, p2, p3, event) {
- event.stopPropagation();
- }
- });
-
- return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
- }
-
- // COLOR TOOL
- createColorTool() {
- return new MenuItem({
- title: "Color",
- css: "fill:white;",
- class: "menuicon",
- execEvent: "",
- render() {
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("viewBox", "-100 -100 650 650");
- const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
- path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z");
- svg.appendChild(path);
-
- const color = TooltipTextMenu.createDiv("buttonColor");
- color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString();
-
- const wrapper = TooltipTextMenu.createDiv("colorPicker");
- wrapper.appendChild(svg);
- wrapper.appendChild(color);
- return wrapper;
- },
- run: (state, dispatch) => TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
- });
- }
-
- static insertColor(color: String, state: EditorState<any>, dispatch: any) {
- const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
- if (state.selection.empty) {
- dispatch(state.tr.addStoredMark(colorMark));
- } else {
- this.setMark(colorMark, state, dispatch);
- }
- }
-
- createColorDropdown() {
- // menu item for color picker
- const self = this;
- const colors = new MenuItem({
- title: "",
- execEvent: "",
- class: "button-setting-disabled",
- css: "",
- render() {
- const p = document.createElement("p");
- p.textContent = "Change color:";
-
- const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
-
- const colors = [
- DarkPastelSchemaPalette.get("pink2"),
- DarkPastelSchemaPalette.get("purple4"),
- DarkPastelSchemaPalette.get("bluegreen1"),
- DarkPastelSchemaPalette.get("yellow4"),
- DarkPastelSchemaPalette.get("red2"),
- DarkPastelSchemaPalette.get("bluegreen7"),
- DarkPastelSchemaPalette.get("bluegreen5"),
- DarkPastelSchemaPalette.get("orange1"),
- "#757472",
- "#000"
- ];
-
- colors.forEach(color => {
- const button = document.createElement("button");
- button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker";
- if (color) {
- button.style.backgroundColor = color;
- button.onclick = e => {
- TooltipTextMenuManager.Instance.color = color;
-
- TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
-
- // update color menu
- const colorDom = self.createColorTool().render(self.view).dom;
- const colorDropdownDom = self.createColorDropdown().render(self.view).dom;
- self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom);
- self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom);
- self.colorDom = colorDom;
- self.colorDropdownDom = colorDropdownDom;
- };
- }
- colorsWrapper.appendChild(button);
- });
-
- const div = document.createElement("div");
- div.appendChild(p);
- div.appendChild(colorsWrapper);
- return div;
- },
- enable() { return false; },
- run(p1, p2, p3, event) { event.stopPropagation(); }
- });
-
- return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
- }
-
- // BRUSH TOOL
- createBrushTool(active: boolean = false) {
- const icon = {
- height: 32, width: 32,
- path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
- };
- const self = this;
- return new MenuItem({
- title: "Brush tool",
- label: "Brush tool",
- icon: icon,
- css: "fill:white;",
- class: active ? "menuicon-active" : "menuicon",
- execEvent: "",
- run: (state, dispatch) => {
- this.brush_function(state, dispatch);
-
- // update dropdown with marks
- const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
- self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
- self._brushDropdownDom = newBrushDropdowndom;
- },
- active: (state) => true
- });
- }
-
- brush_function(state: EditorState<any>, dispatch: any) {
- if (TooltipTextMenuManager.Instance._brushIsEmpty) {
- // get marks in the selection
- const selected_marks = new Set<Mark>();
- const { from, to } = state.selection as TextSelection;
- state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => selected_marks.add(m)));
-
- if (this._brushdom && selected_marks.size >= 0) {
- TooltipTextMenuManager.Instance._brushMarks = selected_marks;
- const newbrush = this.createBrushTool(true).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
- }
- }
- else {
- const { from, to, $from } = this.view.state.selection;
- if (this._brushdom) {
- if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
- if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) {
- this.view.dispatch(this.view.state.tr.removeMark(from, to));
- Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
- TooltipTextMenu.setMark(mark, this.view.state, this.view.dispatch);
- });
- }
- }
- else {
- const newbrush = this.createBrushTool(false).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
- }
- }
- }
- }
-
- createBrushDropdown(active: boolean = false) {
- let label = "Stored marks: ";
- if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) {
- TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => label += mark.type.name + ", ");
- label = label.substring(0, label.length - 2);
- } else {
- label = "No marks are currently stored";
- }
-
- const brushInfo = new MenuItem({
- title: "",
- label: label,
- execEvent: "",
- class: "button-setting-disabled",
- css: "",
- enable() { return false; },
- run(p1, p2, p3, event) { event.stopPropagation(); }
- });
-
- const self = this;
- const input = document.createElement("input");
- const clearBrush = new MenuItem({
- title: "Clear brush",
- execEvent: "",
- class: "separated-button",
- css: "",
- render() {
- const button = document.createElement("button");
- button.textContent = "Clear brush";
-
- input.textContent = "editme";
- input.style.width = "75px";
- input.style.height = "30px";
- input.style.background = "white";
- input.setAttribute("contenteditable", "true");
- input.style.whiteSpace = "nowrap";
- input.type = "text";
- input.placeholder = "Enter URL";
- input.onpointerdown = (e: PointerEvent) => {
- e.stopPropagation();
- e.preventDefault();
- };
- input.onclick = (e: MouseEvent) => {
- input.select();
- input.focus();
- };
- input.onkeypress = (e: KeyboardEvent) => {
- if (e.key === "Enter") {
- TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMap.set(input.value, TooltipTextMenuManager.Instance._brushMarks);
- input.style.background = "lightGray";
- }
- };
-
- const wrapper = document.createElement("div");
- wrapper.appendChild(input);
- wrapper.appendChild(button);
- return wrapper;
- },
- enable() { return true; },
- run() {
- TooltipTextMenuManager.Instance._brushIsEmpty = true;
- TooltipTextMenuManager.Instance._brushMarks = new Set();
-
- // update brush tool
- // TODO: this probably isn't very clean
- const newBrushdom = self.createBrushTool().render(self.view).dom;
- self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom);
- self._brushdom = newBrushdom;
- const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
- self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
- self._brushDropdownDom = newBrushDropdowndom;
- }
- });
-
- const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
- return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
- }
-
- static setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
- if (mark) {
- const node = (state.selection as NodeSelection).node;
- if (node ?.type === schema.nodes.ordered_list) {
- let attrs = node.attrs;
- if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
- if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
- if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
- const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
- dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
- } else {
- toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
- const { from, $from, to, empty } = tx.selection;
- if (!tx.doc.rangeHasMark(from, to, mark.type)) {
- toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
- } else dispatch(tx);
- });
- }
- }
- }
-
- // called by Prosemirror
- update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); }
- //updates the tooltip menu when the selection changes
- public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
- if (!view) {
- console.log("no editor? why?");
- return;
- }
- this.view = view;
- DocumentDecorations.Instance.showTextBar();
- props && (this.editorProps = props);
-
- // Don't do anything if the document/selection didn't change
- if (!lastState || !lastState.doc.eq(view.state.doc) || !lastState.selection.eq(view.state.selection)) {
-
- // UPDATE LINK DROPDOWN
- const linkTarget = await this.getTextLinkTargetTitle();
- const linkDom = this.createLinkTool(linkTarget ? true : false).render(this.view).dom;
- const linkDropdownDom = this.createLinkDropdown(linkTarget).render(this.view).dom;
- this.linkDom && this.tooltip.replaceChild(linkDom, this.linkDom);
- this.linkDropdownDom && this.tooltip.replaceChild(linkDropdownDom, this.linkDropdownDom);
- this.linkDom = linkDom;
- this.linkDropdownDom = linkDropdownDom;
-
- //UPDATE FONT STYLE DROPDOWN
- const activeStyles = this.activeFontFamilyOnSelection();
- this.updateFontStyleDropdown(activeStyles.length === 1 ? activeStyles[0] : activeStyles.length ? "various" : "default");
-
- //UPDATE FONT SIZE DROPDOWN
- const activeSizes = this.activeFontSizeOnSelection();
- this.updateFontSizeDropdown(activeSizes.length === 1 ? String(activeSizes[0]) + " pt" : activeSizes.length ? "various" : "default");
-
- //UPDATE ALL OTHER BUTTONS
- this.updateHighlightStateOfButtons();
- }
- }
-
- updateHighlightStateOfButtons() {
- Array.from(this._marksToDoms.values()).forEach(val => val.style.fill = "white");
- this.activeMarksOnSelection().filter(mark => this._marksToDoms.has(mark)).forEach(mark =>
- this._marksToDoms.get(mark)!.style.fill = "greenyellow");
-
- // keeps brush tool highlighted if active when switching between textboxes
- if (!TooltipTextMenuManager.Instance._brushIsEmpty && this._brushdom) {
- const newbrush = this.createBrushTool(true).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- }
- }
-
- //finds fontSize at start of selection
- activeFontSizeOnSelection() {
- //current selection
- const state = this.view.state;
- const activeSizes: number[] = [];
- const pos = this.view.state.selection.$from;
- const ref_node: ProsNode = this.reference_node(pos);
- if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
- ref_node.marks.forEach(m => m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize));
- }
- return activeSizes;
- }
- //finds fontSize at start of selection
- activeFontFamilyOnSelection() {
- //current selection
- const state = this.view.state;
- const activeFamilies: string[] = [];
- const pos = this.view.state.selection.$from;
- const ref_node: ProsNode = this.reference_node(pos);
- if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
- ref_node.marks.forEach(m => m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family));
- }
- return activeFamilies;
- }
- //finds all active marks on selection in given group
- activeMarksOnSelection() {
- const markGroup = Array.from(this._marksToDoms.keys());
- if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
- //current selection
- const { empty, ranges, $to } = this.view.state.selection as TextSelection;
- const state = this.view.state;
- let activeMarks: MarkType[] = [];
- if (!empty) {
- activeMarks = markGroup.filter(mark => {
- const has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
- }
- return false;
- });
- }
- else {
- const pos = this.view.state.selection.$from;
- const ref_node: ProsNode = this.reference_node(pos);
- if (ref_node !== null && ref_node !== this.view.state.doc) {
- if (ref_node.isText) {
- }
- else {
- return [];
- }
- activeMarks = markGroup.filter(mark_type => {
- if (mark_type === state.schema.marks.pFontSize) {
- return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
- }
- const mark = state.schema.mark(mark_type);
- return ref_node.marks.includes(mark);
- });
- }
- }
- return activeMarks;
- }
-
- reference_node(pos: ResolvedPos<any>): ProsNode {
- let ref_node: ProsNode = this.view.state.doc;
- if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
- ref_node = pos.nodeBefore;
- }
- else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
- ref_node = pos.nodeAfter;
- }
- else if (pos.pos > 0) {
- let skip = false;
- for (let i: number = pos.pos - 1; i > 0; i--) {
- this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
- if (node.isLeaf && !skip) {
- ref_node = node;
- skip = true;
- }
-
- });
- }
- }
- if (!ref_node.isLeaf && ref_node.childCount > 0) {
- ref_node = ref_node.child(0);
- }
- return ref_node;
- }
-
- destroy() {
- // this.wrapper.remove();
- }
-}
-
-
-export class TooltipTextMenuManager {
- private static _instance: TooltipTextMenuManager;
- private _isPinned: boolean = false;
-
- public pinnedX: number = 0;
- public pinnedY: number = 0;
- public unpinnedX: number = 0;
- public unpinnedY: number = 0;
-
- public _brushMarks: Set<Mark> | undefined;
- public _brushMap: Map<string, Set<Mark>> = new Map();
- public _brushIsEmpty: boolean = true;
-
- public color: String = "#000";
- public highlighter: String = "transparent";
-
- public activeMenu: TooltipTextMenu | undefined;
-
- static get Instance() {
- if (!TooltipTextMenuManager._instance) {
- TooltipTextMenuManager._instance = new TooltipTextMenuManager();
- }
- return TooltipTextMenuManager._instance;
- }
-
- public get isPinned() { return this._isPinned; }
-
- public toggleIsPinned() { this._isPinned = !this._isPinned; }
-}
diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js
index 27605d167..beb030635 100644
--- a/src/client/util/request-image-size.js
+++ b/src/client/util/request-image-size.js
@@ -38,7 +38,7 @@ module.exports = function requestImageSize(options) {
return reject(new HttpError(res.statusCode, res.statusMessage));
}
- let buffer = new Buffer([]);
+ let buffer = new Buffer.from([]);
let size;
let imageSizeError;
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 622e10960..127f7b798 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -131,6 +131,7 @@ interface Promise<T> {
declare const Update: unique symbol;
declare const Self: unique symbol;
declare const SelfProxy: unique symbol;
+declare const DataSym: unique symbol;
declare const HandleUpdate: unique symbol;
declare const Id: unique symbol;
declare const OnUpdate: unique symbol;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 937aff0d6..4d04d4e89 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -77,8 +77,13 @@ export class ContextMenu extends React.Component {
@action
clearItems() {
this._items = [];
+ this._defaultPrefix = "";
+ this._defaultItem = undefined;
}
+ _defaultPrefix: string = "";
+ _defaultItem: ((name: string) => void) | undefined;
+
findByDescription = (target: string, toLowerCase = false) => {
return this._items.find(menuItem => {
let reference = menuItem.description;
@@ -93,6 +98,11 @@ export class ContextMenu extends React.Component {
this._items.push(item);
}
}
+ @action
+ setDefaultItem(prefix: string, item: (name: string) => void) {
+ this._defaultPrefix = prefix;
+ this._defaultItem = item;
+ }
getItems() {
return this._items;
@@ -124,13 +134,13 @@ export class ContextMenu extends React.Component {
}
@action
- displayMenu = (x: number, y: number) => {
+ displayMenu = (x: number, y: number, initSearch = "") => {
//maxX and maxY will change if the UI/font size changes, but will work for any amount
//of items added to the menu
this._pageX = x;
this._pageY = y;
- this._searchString = "";
+ this._searchString = initSearch;
this._shouldDisplay = true;
}
@@ -248,7 +258,11 @@ export class ContextMenu extends React.Component {
e.preventDefault();
} else if (e.key === "Enter" || e.key === "Tab") {
const item = this.flatItems[this.selectedIndex];
- item && item.event({ x: this.pageX, y: this.pageY });
+ if (item) {
+ item.event({ x: this.pageX, y: this.pageY });
+ } else if (this._searchString.startsWith(this._defaultPrefix)) {
+ this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
+ }
this.closeMenu();
e.preventDefault();
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 4dbf26956..ce48e1215 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -34,8 +34,7 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@computed get layoutDoc() { return Doc.Layout(this.props.Document); }
- @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
- @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+ @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Cast(this.props.Document.resolvedDataDoc, Doc, null) || Doc.GetProto(this.props.Document)) as Doc; }
active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
}
return Component;
@@ -43,7 +42,7 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
/// DocAnnotatbleComponent return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
-interface DocAnnotatableProps {
+export interface DocAnnotatableProps {
Document: Doc;
DataDoc?: Doc;
fieldKey: string;
@@ -58,15 +57,16 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@computed get layoutDoc() { return Doc.Layout(this.props.Document); }
- @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
- @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
- @computed get extensionDocSync() { return Doc.fieldExtensionDocSync(this.dataDoc, this.props.fieldKey); }
- @computed get annotationsKey() { return "annotations"; }
+ @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Cast(this.props.Document.resolvedDataDoc, Doc, null) || Doc.GetProto(this.props.Document)) as Doc; }
+
+ _annotationKey: string = "annotations";
+ public set annotationKey(val: string) { this._annotationKey = val; }
+ public get annotationKey() { return this._annotationKey; }
@action.bound
removeDocument(doc: Doc): boolean {
Doc.GetProto(doc).annotationOn = undefined;
- const value = this.extensionDoc && Cast(this.extensionDoc[this.annotationsKey], listSpec(Doc), []);
+ const value = Cast(this.dataDoc[this.props.fieldKey + "-" + this._annotationKey], listSpec(Doc), []);
const index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
return index !== -1 && value && value.splice(index, 1) ? true : false;
}
@@ -79,7 +79,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
@action.bound
addDocument(doc: Doc): boolean {
Doc.GetProto(doc).annotationOn = this.props.Document;
- return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this.annotationsKey, doc) ? true : false;
+ return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false;
}
whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 202bfe400..d6029dbe5 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,26 +1,29 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction, computed } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Id } from '../../new_fields/FieldSymbols';
import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast, StrCast, Cast } from "../../new_fields/Types";
+import { NumCast, StrCast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { DragManager } from "../util/DragManager";
+import RichTextMenu from '../util/RichTextMenu';
import { UndoManager } from "../util/UndoManager";
-import './DocumentButtonBar.scss';
+import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
+import { ParentDocSelector } from './collections/ParentDocumentSelector';
import './collections/ParentDocumentSelector.scss';
+import './DocumentButtonBar.scss';
import { LinkMenu } from "./linking/LinkMenu";
-import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
+import { DocumentView } from './nodes/DocumentView';
+import { GoogleRef } from "./nodes/FormattedTextBox";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
-import { DocumentView } from './nodes/DocumentView';
-import { ParentDocSelector } from './collections/ParentDocumentSelector';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { Id } from '../../new_fields/FieldSymbols';
+import { DragManager } from '../util/DragManager';
+import { MetadataEntryMenu } from './MetadataEntryMenu';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -42,6 +45,7 @@ const fetch: IconProp = "sync-alt";
@observer
export class DocumentButtonBar extends React.Component<{ views: (DocumentView | undefined)[], stack?: any }, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
+ private _dragRef = React.createRef<HTMLDivElement>();
private _downX = 0;
private _downY = 0;
private _pullAnimating = false;
@@ -112,14 +116,15 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
const linkDrag = UndoManager.StartBatch("Drag Link");
this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.props.Document, e.pageX, e.pageY, {
dragComplete: dropEv => {
- const linkDoc = dropEv.linkDragData?.linkDocument; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
- if (this.view0 && linkDoc && FormattedTextBox.ToolTipTextMenu) {
+ const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+ if (this.view0 && linkDoc) {
const proto = Doc.GetProto(linkDoc);
proto.sourceContext = this.view0.props.ContainingCollectionDoc;
const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
+ const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
+ const text = RichTextMenu.Instance.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", anchor2Id);
if (linkDoc.anchor2 instanceof Doc) {
- const text = FormattedTextBox.ToolTipTextMenu.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", linkDoc.anchor2[Id]);
proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link
}
}
@@ -193,13 +198,34 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
/>
</div>;
}
+ @computed
+ get pinButton() {
+ const targetDoc = this.view0?.props.Document;
+ const isPinned = targetDoc && CurrentUserUtils.IsDocPinned(targetDoc);
+ return !targetDoc ? (null) : <div className="documentButtonBar-linker"
+ title={CurrentUserUtils.IsDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}
+ style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
+
+ onClick={e => {
+ if (isPinned) {
+ DockedFrameRenderer.UnpinDoc(targetDoc);
+ }
+ else {
+ targetDoc.sourceContext = this.view0?.props.ContainingCollectionDoc; // bcz: !! Shouldn't need this ... use search to lookup contexts dynamically
+ DockedFrameRenderer.PinDoc(targetDoc);
+ }
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
+ />
+ </div>;
+ }
@computed
get linkButton() {
const view0 = this.view0;
const linkCount = view0 && DocListCast(view0.props.Document.links).length;
return !view0 ? (null) : <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
- <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
content={<LinkMenu docView={view0} addDocTab={view0.props.addDocTab} changeFlyout={emptyFunction} />}>
<div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
{linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
@@ -209,6 +235,19 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
}
@computed
+ get metadataButton() {
+ const view0 = this.view0;
+ return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={<MetadataEntryMenu docs={() => this.props.views.filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
+ <div className={"documentButtonBar-linkButton-" + "empty"} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
+ </div>
+ </Flyout>
+ </div>;
+ }
+
+ @computed
get contextButton() {
return !this.view0 ? (null) : <ParentDocSelector Views={this.props.views.filter(v => v).map(v => v as DocumentView)} Document={this.view0.props.Document} addDocTab={(doc, data, where) => {
where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) :
@@ -218,21 +257,81 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
}} />;
}
- render() {
- if (!this.view0) return (null);
+ private _downx = 0;
+ private _downy = 0;
+ onAliasButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onAliasButtonMoved);
+ document.removeEventListener("pointerup", this.onAliasButtonUp);
+ e.stopPropagation();
+ }
+
+ onAliasButtonDown = (e: React.PointerEvent): void => {
+ this._downx = e.clientX;
+ this._downy = e.clientY;
+ e.stopPropagation();
+ e.preventDefault();
+ document.removeEventListener("pointermove", this.onAliasButtonMoved);
+ document.addEventListener("pointermove", this.onAliasButtonMoved);
+ document.removeEventListener("pointerup", this.onAliasButtonUp);
+ document.addEventListener("pointerup", this.onAliasButtonUp);
+ }
+ onAliasButtonMoved = (e: PointerEvent): void => {
+ if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
+ document.removeEventListener("pointermove", this.onAliasButtonMoved);
+ document.removeEventListener("pointerup", this.onAliasButtonUp);
+
+ const dragDocView = this.props.views[0]!;
+ const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
+ const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ dragData.embedDoc = true;
+ dragData.dropAction = "alias";
+ DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
+ offsetX: dragData.offset[0],
+ offsetY: dragData.offset[1],
+ hideSource: false
+ });
+ }
+ e.stopPropagation();
+ }
+
+ @computed
+ get templateButton() {
+ const view0 = this.view0;
const templates: Map<Template, boolean> = new Map();
Array.from(Object.values(Templates.TemplateList)).map(template =>
templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean)));
+ return !view0 ? (null) : <div title="Customize layout" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={<TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+ <div className={"documentButtonBar-linkButton-" + "empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
+ </div>
+ </Flyout>
+ </div>;
+ }
+
+ render() {
+ if (!this.view0) return (null);
const isText = this.view0.props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field. need to add fieldKey to DocumentView
const considerPull = isText && this.considerGoogleDocsPull;
const considerPush = isText && this.considerGoogleDocsPush;
+ Doc.UserDoc().pr
return <div className="documentButtonBar">
<div className="documentButtonBar-button">
{this.linkButton}
</div>
<div className="documentButtonBar-button">
- <TemplateMenu docs={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />
+ {this.templateButton}
+ </div>
+ <div className="documentButtonBar-button">
+ {this.metadataButton}
+ </div>
+ <div className="documentButtonBar-button">
+ {this.contextButton}
+ </div>
+ <div className="documentButtonBar-button">
+ {this.pinButton}
</div>
<div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
{this.considerGoogleDocsPush}
@@ -240,7 +339,6 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
<div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
{this.considerGoogleDocsPull}
</div>
- {this.contextButton}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9496375f4..4ec1659cc 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,33 +1,25 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync } from "../../new_fields/Doc";
+import { Doc } from "../../new_fields/Doc";
import { PositionDocument } from '../../new_fields/documentSchemas';
-import { List } from "../../new_fields/List";
import { ObjectField } from '../../new_fields/ObjectField';
-import { Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { ScriptField } from '../../new_fields/ScriptField';
+import { Cast, StrCast } from "../../new_fields/Types";
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
import { Utils } from "../../Utils";
-import { Docs, DocUtils } from "../documents/Documents";
-import { DocumentManager } from "../util/DocumentManager";
+import { DocUtils } from "../documents/Documents";
+import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
-import { TooltipTextMenu } from '../util/TooltipTextMenu';
import { undoBatch, UndoManager } from "../util/UndoManager";
-import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
-import { CollectionView } from "./collections/CollectionView";
import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { DocumentView } from "./nodes/DocumentView";
-import { FieldView } from "./nodes/FieldView";
import { IconBox } from "./nodes/IconBox";
import React = require("react");
-import { DocumentType } from '../documents/DocumentTypes';
-import { ScriptField } from '../../new_fields/ScriptField';
-import { render } from 'react-dom';
-import RichTextMenu from '../util/RichTextMenu';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -56,17 +48,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _titleHeight = 20;
private _downX = 0;
private _downY = 0;
- private _iconDoc?: Doc = undefined;
private _resizeUndo?: UndoManager.Batch;
private _radiusDown = [0, 0];
@observable private _accumulatedTitle = "";
- @observable private _minimizedX = 0;
- @observable private _minimizedY = 0;
@observable private _titleControlString: string = "#title";
@observable private _edtingTitle = false;
@observable private _hidden = false;
@observable private _opacity = 1;
- @observable private _removeIcon = false;
@observable public Interacting = false;
@observable public pushIcon: IconProp = "arrow-alt-circle-up";
@@ -262,15 +250,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@action
onMinimizeDown = (e: React.PointerEvent): void => {
e.stopPropagation();
- this._iconDoc = undefined;
if (e.button === 0) {
- this._downX = e.pageX;
- this._downY = e.pageY;
- this._removeIcon = false;
- const selDoc = SelectionManager.SelectedDocuments()[0];
- const selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
- this._minimizedX = selDocPos[0] + 12;
- this._minimizedY = selDocPos[1] + 12;
document.removeEventListener("pointermove", this.onMinimizeMove);
document.addEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
@@ -283,20 +263,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
e.stopPropagation();
if (Math.abs(e.pageX - this._downX) > Utils.DRAG_THRESHOLD ||
Math.abs(e.pageY - this._downY) > Utils.DRAG_THRESHOLD) {
- const selDoc = SelectionManager.SelectedDocuments()[0];
- const selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
- const snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20;
- this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX;
- this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY;
- const selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
-
- if (selectedDocs.length > 1) {
- this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString(""));
- this.moveIconDoc(this._iconDoc);
- } else {
- this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon));
- }
- this._removeIcon = snapped;
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
}
}
@undoBatch
@@ -307,59 +275,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
const selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
- if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) {
- selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
- }
- if (!this._removeIcon && selectedDocs.length === 1) { // if you click on the top-left button when just 1 doc is selected, then collapse it. not sure why we don't do it for multiple selections
- this.getIconDoc(selectedDocs[0]).then(async icon => {
- const minimizedDoc = await Cast(selectedDocs[0].props.Document.minimizedDoc, Doc);
- if (minimizedDoc) {
- const scrpt = selectedDocs[0].props.ScreenToLocalTransform().scale(selectedDocs[0].props.ContentScaling()).inverse().transformPoint(
- NumCast(minimizedDoc.x) - NumCast(selectedDocs[0].Document.x), NumCast(minimizedDoc.y) - NumCast(selectedDocs[0].Document.y));
- SelectionManager.DeselectAll();
- DocumentManager.Instance.animateBetweenPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
- }
- });
- }
- this._removeIcon = false;
- }
- runInAction(() => this._minimizedX = this._minimizedY = 0);
- }
-
- @undoBatch
- @action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
- const doc = selected[0].props.Document;
- const iconDoc = Docs.Create.IconDocument(layoutString);
- iconDoc.isButton = true;
-
- IconBox.AutomaticTitle(iconDoc);
- //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
- iconDoc.width = Number(MINIMIZED_ICON_SIZE);
- iconDoc.height = Number(MINIMIZED_ICON_SIZE);
- iconDoc.x = NumCast(doc.x);
- iconDoc.y = NumCast(doc.y) - 24;
- iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document));
- selected.length === 1 && (doc.minimizedDoc = iconDoc);
- selected[0].props.addDocument && selected[0].props.addDocument(iconDoc);
- return iconDoc;
- }
- @action
- public getIconDoc = async (docView: DocumentView): Promise<Doc | undefined> => {
- const doc = docView.props.Document;
- let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
-
- if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) {
- const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView, ""));
- iconDoc = this.createIcon([docView], layout);
+ selectedDocs.map(dv => {
+ const layoutKey = Cast(dv.props.Document.layoutKey, "string", null);
+ const collapse = layoutKey !== "layout_icon";
+ if (collapse) {
+ dv.setCustomView(collapse, "icon");
+ if (layoutKey && layoutKey !== "layout") dv.props.Document.deiconifyLayout = layoutKey.replace("layout_", "");
+ } else {
+ const deiconifyLayout = Cast(dv.props.Document.deiconifyLayout, "string", null);
+ dv.setCustomView(deiconifyLayout ? true : false, deiconifyLayout);
+ dv.props.Document.deiconifyLayout = undefined;
+ }
+ });
}
- return iconDoc;
- }
- moveIconDoc(iconDoc: Doc) {
- const selView = SelectionManager.SelectedDocuments()[0];
- const where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).
- transformPoint(this._minimizedX - 12, this._minimizedY - 12);
- iconDoc.x = where[0] + NumCast(selView.props.Document.x);
- iconDoc.y = where[1] + NumCast(selView.props.Document.y);
+ SelectionManager.DeselectAll();
}
@action
@@ -379,14 +308,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
onRadiusMove = (e: PointerEvent): void => {
let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
dist = dist < 3 ? 0 : dist;
- let usingRule = false;
- SelectionManager.SelectedDocuments().map(dv => {
- const ruleProvider = dv.props.ruleProvider;
- const heading = NumCast(dv.props.Document.heading);
- ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
- usingRule = usingRule || (ruleProvider && heading ? true : false);
- });
- !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
+ SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateForField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
map(d => d.borderRounding = `${Math.min(100, dist)}%`);
e.stopPropagation();
e.preventDefault();
@@ -478,10 +400,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
const doc = PositionDocument(element.props.Document);
const layoutDoc = PositionDocument(Doc.Layout(element.props.Document));
- let nwidth = layoutDoc.nativeWidth || 0;
- let nheight = layoutDoc.nativeHeight || 0;
- const width = (layoutDoc.width || 0);
- const height = (layoutDoc.height || (nheight / nwidth * width));
+ let nwidth = layoutDoc._nativeWidth || 0;
+ let nheight = layoutDoc._nativeHeight || 0;
+ const width = (layoutDoc._width || 0);
+ const height = (layoutDoc._height || (nheight / nwidth * width));
const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
const actualdW = Math.max(width + (dW * scale), 20);
const actualdH = Math.max(height + (dH * scale), 20);
@@ -490,34 +412,34 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
layoutDoc.ignoreAspect = false;
- layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
- layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
+ layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
}
if (fixedAspect && (!nwidth || !nheight)) {
- layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
- layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
+ layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
}
if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
if (Math.abs(dW) > Math.abs(dH)) {
if (!fixedAspect) {
- layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+ layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0);
}
- layoutDoc.width = actualdW;
- if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
- else layoutDoc.height = actualdH;
+ layoutDoc._width = actualdW;
+ if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+ else layoutDoc._height = actualdH;
}
else {
if (!fixedAspect) {
- layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+ layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0);
}
- layoutDoc.height = actualdH;
- if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
- else layoutDoc.width = actualdW;
+ layoutDoc._height = actualdH;
+ if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+ else layoutDoc._width = actualdW;
}
} else {
- dW && (layoutDoc.width = actualdW);
- dH && (layoutDoc.height = actualdH);
- dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
+ dW && (layoutDoc._width = actualdW);
+ dH && (layoutDoc._height = actualdH);
+ dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
}
}
}));
@@ -560,11 +482,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this.TextBar = ele;
}
}
- public showTextBar = () => {
- if (this.TextBar && TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1) {
- this.TextBar.appendChild(TooltipTextMenu.Toolbar);
- }
- }
render() {
const bounds = this.Bounds;
const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -607,8 +524,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}}>
{minimizeIcon}
- {/* <RichTextMenu /> */}
-
{this._edtingTitle ?
<input ref={this._keyinput} className="title" type="text" name="dynbox" value={this._accumulatedTitle} onBlur={e => this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> :
<div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index faf02b946..84c6b0dfd 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,10 +1,12 @@
import React = require('react');
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { observable, action, trace } from 'mobx';
-import "./EditableView.scss";
import * as Autosuggest from 'react-autosuggest';
-import { undoBatch } from '../util/UndoManager';
+import { ObjectField } from '../../new_fields/ObjectField';
import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField';
+import { ContextMenu } from './ContextMenu';
+import { ContextMenuProps } from './ContextMenuItem';
+import "./EditableView.scss";
export interface EditableProps {
/**
@@ -43,6 +45,8 @@ export interface EditableProps {
editing?: boolean;
onClick?: (e: React.MouseEvent) => boolean;
isEditingCallback?: (isEditing: boolean) => void;
+ menuCallback?: (x: number, y: number) => void;
+ showMenuOnLoad?: boolean;
HeadingObject?: SchemaHeaderField | undefined;
HeadingsHack?: number;
toggle?: () => void;
@@ -65,7 +69,7 @@ export class EditableView extends React.Component<EditableProps> {
}
@action
- componentWillReceiveProps(nextProps: EditableProps) {
+ componentDidUpdate(nextProps: EditableProps) {
// this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
// so when the suggestions are passed in, and no editing prop is passed in, it used to set it
// to false. this will no longer do so -syip
@@ -74,6 +78,8 @@ export class EditableView extends React.Component<EditableProps> {
}
}
+ _didShow = false;
+
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Tab") {
@@ -87,21 +93,27 @@ export class EditableView extends React.Component<EditableProps> {
} else if (this.props.OnFillDown) {
this.props.OnFillDown(e.currentTarget.value);
this._editing = false;
- this.props.isEditingCallback && this.props.isEditingCallback(false);
+ this.props.isEditingCallback?.(false);
}
} else if (e.key === "Escape") {
e.stopPropagation();
this._editing = false;
- this.props.isEditingCallback && this.props.isEditingCallback(false);
+ this.props.isEditingCallback?.(false);
+ } else if (e.key === ":") {
+ this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
}
}
@action
onClick = (e: React.MouseEvent) => {
e.nativeEvent.stopPropagation();
- if (!this.props.onClick || !this.props.onClick(e)) {
- this._editing = true;
- this.props.isEditingCallback && this.props.isEditingCallback(true);
+ if (this._ref.current && this.props.showMenuOnLoad) {
+ this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
+ } else {
+ if (!this.props.onClick || !this.props.onClick(e)) {
+ this._editing = true;
+ this.props.isEditingCallback?.(true);
+ }
}
e.stopPropagation();
}
@@ -110,7 +122,7 @@ export class EditableView extends React.Component<EditableProps> {
private finalizeEdit(value: string, shiftDown: boolean) {
this._editing = false;
if (this.props.SetValue(value, shiftDown)) {
- this.props.isEditingCallback && this.props.isEditingCallback(false);
+ this.props.isEditingCallback?.(false);
}
}
@@ -125,6 +137,7 @@ export class EditableView extends React.Component<EditableProps> {
return wasFocused !== this._editing;
}
+ _ref = React.createRef<HTMLDivElement>();
render() {
if (this._editing && this.props.GetValue() !== undefined) {
return this.props.autosuggestProps
@@ -151,9 +164,10 @@ export class EditableView extends React.Component<EditableProps> {
style={{ display: this.props.display, fontSize: this.props.fontSize }}
/>;
} else {
- if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue();
- return (
+ this.props.autosuggestProps?.resetValue();
+ return (this.props.contents instanceof ObjectField ? (null) :
<div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
+ ref={this._ref}
style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
onClick={this.onClick}>
<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span>
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
new file mode 100644
index 000000000..d980b0a91
--- /dev/null
+++ b/src/client/views/GestureOverlay.scss
@@ -0,0 +1,20 @@
+.gestureOverlay-cont {
+ width: 100vw;
+ height: 100vh;
+ position: absolute;
+ top: 0;
+ left: 0;
+ touch-action: none;
+}
+
+.clipboardDoc-cont {
+ position: absolute;
+ width: 300px;
+ height: 300px;
+}
+
+.filter-cont {
+ position: absolute;
+ background-color: transparent;
+ border: 1px solid black;
+} \ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
new file mode 100644
index 000000000..580c53a37
--- /dev/null
+++ b/src/client/views/GestureOverlay.tsx
@@ -0,0 +1,536 @@
+import React = require("react");
+import { Touchable } from "./Touchable";
+import { observer } from "mobx-react";
+import "./GestureOverlay.scss";
+import { computed, observable, action, runInAction, IReactionDisposer, reaction } from "mobx";
+import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { InkingControl } from "./InkingControl";
+import { InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { LinkManager } from "../util/LinkManager";
+import { DocUtils } from "../documents/Documents";
+import { undoBatch } from "../util/UndoManager";
+import { Scripting } from "../util/Scripting";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import Palette from "./Palette";
+import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange } from "../../Utils";
+import { DocumentView } from "./nodes/DocumentView";
+import { Transform } from "../util/Transform";
+import { DocumentContentsView } from "./nodes/DocumentContentsView";
+
+@observer
+export default class GestureOverlay extends Touchable {
+ static Instance: GestureOverlay;
+
+ @observable public Color: string = "rgb(244, 67, 54)";
+ @observable public Width: number = 5;
+ @observable public SavedColor?: string;
+ @observable public SavedWidth?: number;
+ @observable public Tool: ToolglassTools = ToolglassTools.None;
+
+ @observable private _thumbX?: number;
+ @observable private _thumbY?: number;
+ @observable private _pointerY?: number;
+ @observable private _points: { X: number, Y: number }[] = [];
+ @observable private _palette?: JSX.Element;
+ @observable private _clipboardDoc?: JSX.Element;
+
+ @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
+ @computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
+
+ private _d1: Doc | undefined;
+ private _thumbDoc: Doc | undefined;
+ private thumbIdentifier?: number;
+ private pointerIdentifier?: number;
+ private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
+
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ GestureOverlay.Instance = this;
+ }
+
+ getNewTouches(e: React.TouchEvent | TouchEvent) {
+ const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches);
+ const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches);
+ const nt: (React.Touch | Touch)[] = Array.from(e.touches);
+ this._hands.forEach((hand) => {
+ for (let i = 0; i < e.targetTouches.length; i++) {
+ const pt = e.targetTouches.item(i);
+ if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+ ntt.splice(ntt.indexOf(pt), 1);
+ }
+ }
+
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ const pt = e.changedTouches.item(i);
+ if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+ nct.splice(nct.indexOf(pt), 1);
+ }
+ }
+
+ for (let i = 0; i < e.touches.length; i++) {
+ const pt = e.touches.item(i);
+ if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+ nt.splice(nt.indexOf(pt), 1);
+ }
+ }
+ });
+ return { ntt, nct, nt };
+ }
+
+ onReactTouchStart = (te: React.TouchEvent) => {
+ const actualPts: React.Touch[] = [];
+ for (let i = 0; i < te.touches.length; i++) {
+ const pt: any = te.touches.item(i);
+ actualPts.push(pt);
+ // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
+ // and this seems to be the only way of differentiating pen and touch on touch events
+ if (pt.radiusX > 1 && pt.radiusY > 1) {
+ // if (typeof pt.identifier !== "string") {
+ // pt.identifier = Utils.GenerateGuid();
+ // }
+ this.prevPoints.set(pt.identifier, pt);
+ }
+ }
+
+ const ptsToDelete: number[] = [];
+ this.prevPoints.forEach(pt => {
+ if (!actualPts.includes(pt)) {
+ ptsToDelete.push(pt.identifier);
+ }
+ });
+
+ ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
+ const nts = this.getNewTouches(te);
+ console.log(nts.nt.length);
+
+ if (nts.nt.length < 5) {
+ const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
+ target?.dispatchEvent(
+ new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchStart",
+ {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: te
+ }
+ }
+ )
+ );
+ document.removeEventListener("touchmove", this.onReactTouchMove);
+ document.removeEventListener("touchend", this.onReactTouchEnd);
+ document.addEventListener("touchmove", this.onReactTouchMove);
+ document.addEventListener("touchend", this.onReactTouchEnd);
+ }
+ else {
+ this.handleHandDown(te);
+ document.removeEventListener("touchmove", this.onReactTouchMove);
+ document.removeEventListener("touchend", this.onReactTouchEnd);
+ }
+ }
+
+ onReactTouchMove = (e: TouchEvent) => {
+ const nts: any = this.getNewTouches(e);
+ document.dispatchEvent(
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchMove",
+ {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e
+ }
+ })
+ );
+ }
+
+ onReactTouchEnd = (e: TouchEvent) => {
+ const nts: any = this.getNewTouches(e);
+ document.dispatchEvent(
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchEnd",
+ {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e
+ }
+ })
+ );
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ const pt = e.changedTouches.item(i);
+ if (pt) {
+ if (this.prevPoints.has(pt.identifier)) {
+ this.prevPoints.delete(pt.identifier);
+ }
+ }
+ }
+
+ if (this.prevPoints.size === 0) {
+ document.removeEventListener("touchmove", this.onReactTouchMove);
+ document.removeEventListener("touchend", this.onReactTouchEnd);
+ }
+ e.stopPropagation();
+ }
+
+ handleHandDown = async (e: React.TouchEvent) => {
+ const fingers = new Array<React.Touch>();
+ for (let i = 0; i < e.touches.length; i++) {
+ const pt: any = e.touches.item(i);
+ if (pt.radiusX > 1 && pt.radiusY > 1) {
+ for (let j = 0; j < e.targetTouches.length; j++) {
+ const tPt = e.targetTouches.item(j);
+ if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
+ if (pt && this.prevPoints.has(pt.identifier)) {
+ fingers.push(pt);
+ }
+ }
+ }
+ }
+ }
+ const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ const rightMost = Math.max(...fingers.map(f => f.clientX));
+ const leftMost = Math.min(...fingers.map(f => f.clientX));
+ let pointer: React.Touch | undefined;
+ // left hand
+ if (thumb.clientX === rightMost) {
+ pointer = fingers.reduce((a, v) => a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v);
+ }
+ // right hand
+ else if (thumb.clientX === leftMost) {
+ pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v);
+ }
+ else {
+ console.log("not hand");
+ }
+ this.pointerIdentifier = pointer?.identifier;
+ runInAction(() => this._pointerY = pointer?.clientY);
+ if (thumb.identifier === this.thumbIdentifier) {
+ this._thumbX = thumb.clientX;
+ this._thumbY = thumb.clientY;
+ this._hands.set(thumb.identifier, fingers);
+ return;
+ }
+ this.thumbIdentifier = thumb?.identifier;
+ this._hands.set(thumb.identifier, fingers);
+ const others = fingers.filter(f => f !== thumb);
+ const minX = Math.min(...others.map(f => f.clientX));
+ const minY = Math.min(...others.map(f => f.clientY));
+
+ const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc);
+ if (thumbDoc) {
+ runInAction(() => {
+ this._thumbDoc = thumbDoc;
+ this._thumbX = thumb.clientX;
+ this._thumbY = thumb.clientY;
+ this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
+ });
+ }
+
+ this.removeMoveListeners();
+ document.removeEventListener("touchmove", this.handleHandMove);
+ document.addEventListener("touchmove", this.handleHandMove);
+ document.removeEventListener("touchend", this.handleHandUp);
+ document.addEventListener("touchend", this.handleHandUp);
+ }
+
+ @action
+ handleHandMove = (e: TouchEvent) => {
+ const fingers = new Array<React.Touch>();
+ for (let i = 0; i < e.touches.length; i++) {
+ const pt: any = e.touches.item(i);
+ if (pt.radiusX > 1 && pt.radiusY > 1) {
+ for (let j = 0; j < e.targetTouches.length; j++) {
+ const tPt = e.targetTouches.item(j);
+ if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
+ if (pt && this.prevPoints.has(pt.identifier)) {
+ this._hands.forEach(hand => hand.some(f => {
+ if (f.identifier === pt.identifier) {
+ fingers.push(pt);
+ }
+ }));
+ }
+ }
+ }
+ }
+ }
+ const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) {
+ this._hands.set(thumb.identifier, fingers);
+ }
+
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ const pt = e.changedTouches.item(i);
+ if (pt && pt.identifier === this.thumbIdentifier && this._thumbX && this._thumbDoc) {
+ if (Math.abs(pt.clientX - this._thumbX) > 20) {
+ this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
+ this._thumbX = pt.clientX;
+ }
+ }
+ if (pt && pt.identifier === this.pointerIdentifier) {
+ this._pointerY = pt.clientY;
+ }
+ }
+ }
+
+ @action
+ handleHandUp = (e: TouchEvent) => {
+ if (e.touches.length < 3) {
+ // this.onTouchEnd(e);
+ if (this.thumbIdentifier) this._hands.delete(this.thumbIdentifier);
+ this._palette = undefined;
+ this.thumbIdentifier = undefined;
+ this._thumbDoc = undefined;
+ document.removeEventListener("touchend", this.handleHandUp);
+ }
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent) => {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ this._points.push({ X: e.clientX, Y: e.clientY });
+ e.stopPropagation();
+ e.preventDefault();
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ this._points.push({ X: e.clientX, Y: e.clientY });
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ handleLineGesture = (): boolean => {
+ let actionPerformed = false;
+ const B = this.svgBounds;
+ const ep1 = this._points[0];
+ const ep2 = this._points[this._points.length - 1];
+
+ const target1 = document.elementFromPoint(ep1.X, ep1.Y);
+ const target2 = document.elementFromPoint(ep2.X, ep2.Y);
+ const callback = (doc: Doc) => {
+ if (!this._d1) {
+ this._d1 = doc;
+ }
+ else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
+ DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
+ actionPerformed = true;
+ }
+ };
+ const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Line,
+ bounds: B,
+ callbackFn: callback
+ }
+ });
+ target1?.dispatchEvent(ge);
+ target2?.dispatchEvent(ge);
+ return actionPerformed;
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent) => {
+ if (this._points.length > 1) {
+ const B = this.svgBounds;
+ const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
+
+ const xInGlass = points[0].X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && points[0].X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
+ const yInGlass = points[0].Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && points[0].Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+
+ if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
+ switch (this.Tool) {
+ case ToolglassTools.InkToText:
+ break;
+ }
+ }
+ else {
+ const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
+ let actionPerformed = false;
+ if (result && result.Score > 0.7) {
+ switch (result.Name) {
+ case GestureUtils.Gestures.Box:
+ const target = document.elementFromPoint(this._points[0].X, this._points[0].Y);
+ target?.dispatchEvent(new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Box,
+ bounds: B
+ }
+ }));
+ actionPerformed = true;
+ break;
+ case GestureUtils.Gestures.Line:
+ actionPerformed = this.handleLineGesture();
+ break;
+ case GestureUtils.Gestures.Scribble:
+ console.log("scribble");
+ break;
+ }
+ if (actionPerformed) {
+ this._points = [];
+ }
+ }
+
+ if (!actionPerformed) {
+ const target = document.elementFromPoint(this._points[0].X, this._points[0].Y);
+ target?.dispatchEvent(
+ new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Stroke,
+ bounds: B
+ }
+ }
+ )
+ );
+ this._points = [];
+ }
+ }
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ @computed get svgBounds() {
+ const xs = this._points.map(p => p.X);
+ const ys = this._points.map(p => p.Y);
+ const right = Math.max(...xs);
+ const left = Math.min(...xs);
+ const bottom = Math.max(...ys);
+ const top = Math.min(...ys);
+ return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
+ }
+
+ @computed get currentStroke() {
+ if (this._points.length <= 1) {
+ return (null);
+ }
+
+ const B = this.svgBounds;
+
+ return (
+ <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
+ {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
+ </svg>
+ );
+ }
+
+ @computed get elements() {
+ return [
+ this.props.children,
+ this._palette,
+ this.currentStroke
+ ];
+ }
+
+ @action
+ public openFloatingDoc = (doc: Doc) => {
+ this._clipboardDoc =
+ <DocumentView
+ Document={doc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ onClick={undefined}
+ removeDocument={undefined}
+ ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
+ ContentScaling={returnOne}
+ PanelWidth={() => 300}
+ PanelHeight={() => 300}
+ renderDepth={0}
+ backgroundColor={returnEmptyString}
+ focus={emptyFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}
+ />;
+ }
+
+ @action
+ public closeFloatingDoc = () => {
+ this._clipboardDoc = undefined;
+ }
+
+ render() {
+ return (
+ <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+ {this.elements}
+ <div className="clipboardDoc-cont" style={{
+ transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
+ height: this.height,
+ width: this.height,
+ pointerEvents: this._clipboardDoc ? "unset" : "none",
+ touchAction: this._clipboardDoc ? "unset" : "none",
+ }}>
+ {this._clipboardDoc}
+ </div>
+ <div className="filter-cont" style={{
+ transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
+ height: this.height,
+ width: this.height,
+ pointerEvents: "none",
+ touchAction: "none",
+ display: this.showBounds ? "unset" : "none",
+ }}>
+ </div>
+ </div >);
+ }
+}
+
+export enum ToolglassTools {
+ InkToText = "inktotext",
+ None = "none",
+}
+
+Scripting.addGlobal("GestureOverlay", GestureOverlay);
+Scripting.addGlobal(function setToolglass(tool: any) {
+ runInAction(() => GestureOverlay.Instance.Tool = tool);
+});
+Scripting.addGlobal(function setPen(width: any, color: any) {
+ runInAction(() => {
+ GestureOverlay.Instance.SavedColor = GestureOverlay.Instance.Color;
+ GestureOverlay.Instance.Color = color;
+ GestureOverlay.Instance.SavedWidth = GestureOverlay.Instance.Width;
+ GestureOverlay.Instance.Width = width;
+ });
+});
+Scripting.addGlobal(function resetPen() {
+ runInAction(() => {
+ GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)";
+ GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5;
+ });
+}); \ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss
deleted file mode 100644
index daff58fd6..000000000
--- a/src/client/views/InkSelectDecorations.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.inkSelectDecorations {
- position: absolute;
- border: black 1px solid;
- z-index: 9001;
-} \ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
deleted file mode 100644
index 3ad50762d..000000000
--- a/src/client/views/InkSelectDecorations.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React = require("react");
-import { Touchable } from "./Touchable";
-import { PointData } from "../../new_fields/InkField";
-import { observer } from "mobx-react";
-import { computed, observable, action, runInAction } from "mobx";
-import "./InkSelectDecorations.scss";
-
-@observer
-export default class InkSelectDecorations extends Touchable {
- static Instance: InkSelectDecorations;
-
- @observable private _selectedInkNodes: Map<any, any> = new Map();
-
- constructor(props: Readonly<{}>) {
- super(props);
-
- InkSelectDecorations.Instance = this;
- }
-
- @action
- public SetSelected = (inkNodes: Map<any, any>, keepOld: boolean = false) => {
- if (!keepOld) {
- this._selectedInkNodes = new Map();
- }
- inkNodes.forEach((value: any, key: any) => {
- runInAction(() => this._selectedInkNodes.set(key, value));
- });
- }
-
- @computed
- get Bounds(): { x: number, y: number, b: number, r: number } {
- const left = Number.MAX_VALUE;
- const top = Number.MAX_VALUE;
- const right = -Number.MAX_VALUE;
- const bottom = -Number.MAX_VALUE;
- this._selectedInkNodes.forEach((value: PointData, key: string) => {
- // value.pathData.map(val => {
- // left = Math.min(val.x, left);
- // top = Math.min(val.y, top);
- // right = Math.max(val.x, right);
- // bottom = Math.max(val.y, bottom);
- // });
- });
- return { x: left, y: top, b: bottom, r: right };
- }
-
- render() {
- const bounds = this.Bounds;
- return <div style={{
- top: bounds.y, left: bounds.x,
- height: bounds.b - bounds.y,
- width: bounds.r - bounds.x
- }} />;
- }
-} \ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index e33f193b8..6cee702ee 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -2,21 +2,18 @@ import { action, computed, observable } from "mobx";
import { ColorState } from 'react-color';
import { Doc } from "../../new_fields/Doc";
import { InkTool } from "../../new_fields/InkField";
-import { List } from "../../new_fields/List";
-import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { Utils } from "../../Utils";
+import { FieldValue, NumCast, StrCast } from "../../new_fields/Types";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
import { Scripting } from "../util/Scripting";
import { SelectionManager } from "../util/SelectionManager";
-import { undoBatch, UndoManager } from "../util/UndoManager";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-
+import { undoBatch } from "../util/UndoManager";
+import GestureOverlay from "./GestureOverlay";
export class InkingControl {
@observable static Instance: InkingControl;
- @observable private _selectedTool: InkTool = InkTool.None;
- @observable private _selectedColor: string = "rgb(244, 67, 54)";
- @observable private _selectedWidth: string = "5";
+ @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(CurrentUserUtils.UserDocument.inkTool)) ?? InkTool.None; }
+ @computed private get _selectedColor(): string { return GestureOverlay.Instance.Color ?? FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; }
+ @computed private get _selectedWidth(): string { return GestureOverlay.Instance.Width?.toString() ?? FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; }
@observable public _open: boolean = false;
constructor() {
@@ -24,7 +21,8 @@ export class InkingControl {
}
switchTool = action((tool: InkTool): void => {
- this._selectedTool = tool;
+ // this._selectedTool = tool;
+ CurrentUserUtils.UserDocument.inkTool = tool;
});
decimalToHexString(number: number) {
if (number < 0) {
@@ -36,70 +34,24 @@ export class InkingControl {
@undoBatch
switchColor = action((color: ColorState): void => {
- this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
+ CurrentUserUtils.UserDocument.inkColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
const selected = SelectionManager.SelectedDocuments();
- const oldColors = selected.map(view => {
+ selected.map(view => {
const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
view.props.Document.layout instanceof Doc ? view.props.Document.layout :
- view.props.Document.isTemplateField ? view.props.Document : Doc.GetProto(view.props.Document);
- const sel = window.getSelection();
- if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) {
- targetDoc.color = this._selectedColor;
- return {
- target: targetDoc,
- previous: StrCast(targetDoc.color)
- };
- }
- const oldColor = StrCast(targetDoc.backgroundColor);
- let matchedColor = this._selectedColor;
- const cvd = view.props.ContainingCollectionDoc;
- let ruleProvider = view.props.ruleProvider;
- if (cvd) {
- if (!cvd.colorPalette) {
- const defaultPalette = ["rg(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
- "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
- const colorPalette = Cast(cvd.colorPalette, listSpec("string"));
- if (!colorPalette) cvd.colorPalette = new List<string>(defaultPalette);
- }
- const cp = Cast(cvd.colorPalette, listSpec("string")) as string[];
- let closest = 0;
- let dist = 10000000;
- const ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
- for (let i = 0; i < cp.length; i++) {
- const cpcol = Utils.fromRGBAstr(cp[i]);
- const d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g));
- if (d < dist) {
- dist = d;
- closest = i;
- }
- }
- cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")";
- cvd.colorPalette = new List(cp);
- matchedColor = cp[closest];
- ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined;
- ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
- }
- (!ruleProvider && targetDoc) && (Doc.Layout(view.props.Document).backgroundColor = matchedColor);
-
- return {
- target: targetDoc,
- previous: oldColor
- };
+ view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
+ targetDoc && (Doc.Layout(view.props.Document).backgroundColor = CurrentUserUtils.UserDocument.inkColor);
});
- //let captured = this._selectedColor;
- // UndoManager.AddEvent({
- // undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous),
- // redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured)
- // });
} else {
CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor);
}
});
@action
switchWidth = (width: string): void => {
- this._selectedWidth = width;
+ // this._selectedWidth = width;
+ CurrentUserUtils.UserDocument.inkWidth = width;
}
@computed
@@ -114,7 +66,8 @@ export class InkingControl {
@action
updateSelectedColor(value: string) {
- this._selectedColor = value;
+ // this._selectedColor = value;
+ CurrentUserUtils.UserDocument.inkColor = value;
}
@computed
@@ -127,6 +80,7 @@ Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { Ink
Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); });
+Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); });
Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); }); \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a413eebc9..f315ce12a 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -9,24 +9,18 @@ import { InkingControl } from "./InkingControl";
import "./InkingStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
+import { TraceMobx } from "../../new_fields/util";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { ContextMenu } from "./ContextMenu";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
+import { library } from "@fortawesome/fontawesome-svg-core";
+
+library.add(faPaintBrush);
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
-export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) {
- const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
- return (
- <polyline
- points={pts}
- style={{
- fill: "none",
- stroke: color ?? InkingControl.Instance.selectedColor,
- strokeWidth: width ?? InkingControl.Instance.selectedWidth
- }}
- />
- );
-}
-
@observer
export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
@@ -34,7 +28,13 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
@computed get PanelWidth() { return this.props.PanelWidth(); }
@computed get PanelHeight() { return this.props.PanelHeight(); }
+ private analyzeStrokes = () => {
+ const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.Document, ["inkAnalysis", "handwriting"], [data]);
+ }
+
render() {
+ TraceMobx();
const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
@@ -42,18 +42,29 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
const top = Math.min(...ys);
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- const points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+ const points = InteractionUtils.CreatePolyline(data, left, top, this.Document.color ?? InkingControl.Instance.selectedColor, this.Document.strokeWidth ?? parseInt(InkingControl.Instance.selectedWidth));
const width = right - left;
const height = bottom - top;
const scaleX = this.PanelWidth / width;
const scaleY = this.PanelHeight / height;
return (
- <svg width={width} height={height} style={{
- transformOrigin: "top left",
- transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
- mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
- pointerEvents: "all"
- }}>
+ <svg
+ width={width}
+ height={height}
+ style={{
+ transformOrigin: "top left",
+ transform: `scale(${scaleX}, ${scaleY})`,
+ mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
+ pointerEvents: "all"
+ }}
+ onContextMenu={() => {
+ ContextMenu.Instance.addItem({
+ description: "Analyze Stroke",
+ event: this.analyzeStrokes,
+ icon: "paint-brush"
+ });
+ }}
+ >
{points}
</svg>
);
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4c8c95529..d39c217ec 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -9,8 +9,8 @@
.mainContent-div {
position: relative;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
}
// add nodes menu. Note that the + button is actually an input label, not an actual button.
@@ -28,6 +28,7 @@
top: 0;
left: 0;
z-index: 1;
+ touch-action: none;
}
.mainView-mainContent {
@@ -57,16 +58,32 @@
overflow: hidden;
}
+
+.mainView-settings {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ font-size: 8px;
+}
+
+.mainView-settings:hover {
+ transform: none !important;
+}
+
.mainView-logout {
position: absolute;
- right: 5;
- bottom: 5;
+ right: 0;
+ bottom: 0;
font-size: 8px;
}
+.mainView-logout:hover {
+ transform: none !important;
+}
+
.mainView-libraryFlyout {
height: 100%;
- width:100%;
+ width: 100%;
position: absolute;
display: flex;
flex-direction: column;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 34da7f823..a9e81093a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import {
- faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
- faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faVideo
+ faFileAlt, faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
+ faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard, faVideo,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -36,16 +36,18 @@ import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import InkSelectDecorations from './InkSelectDecorations';
+import GestureOverlay from './GestureOverlay';
import { Scripting } from '../util/Scripting';
import { AudioBox } from './nodes/AudioBox';
+import SettingsManager from '../util/SettingsManager';
import { TraceMobx } from '../../new_fields/util';
+import { RadialMenu } from './nodes/RadialMenu';
import RichTextMenu from '../util/RichTextMenu';
@observer
export class MainView extends React.Component {
public static Instance: MainView;
- private _buttonBarHeight = 75;
+ private _buttonBarHeight = 35;
private _flyoutSizeOnDown = 0;
private _urlState: HistoryUtil.DocUrl;
private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -62,7 +64,7 @@ export class MainView extends React.Component {
public isPointerDown = false;
- componentWillMount() {
+ componentDidMount() {
const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
@@ -99,6 +101,8 @@ export class MainView extends React.Component {
}
}
+ library.add(faFileAlt);
+ library.add(faStickyNote);
library.add(faFont);
library.add(faExclamation);
library.add(faPortrait);
@@ -129,6 +133,8 @@ export class MainView extends React.Component {
library.add(faLongArrowAltRight);
library.add(faCheck);
library.add(faCaretUp);
+ library.add(faFilter);
+ library.add(faBullseye);
library.add(faArrowDown);
library.add(faArrowUp);
library.add(faCloudUploadAlt);
@@ -137,6 +143,9 @@ export class MainView extends React.Component {
library.add(faChevronRight);
library.add(faEllipsisV);
library.add(faMusic);
+ library.add(faPhone);
+ library.add(faClipboard);
+ library.add(faStamp);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -172,9 +181,9 @@ export class MainView extends React.Component {
} else {
if (received && this._urlState.sharing) {
reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
- initialized => initialized && received && DocServer.GetRefField(received).then(field => {
- if (field instanceof Doc && field.viewType !== CollectionViewType.Docking) {
- CollectionDockingView.AddRightSplit(field, undefined);
+ initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
+ if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
+ CollectionDockingView.AddRightSplit(docField, undefined);
}
}),
);
@@ -195,8 +204,8 @@ export class MainView extends React.Component {
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
- width: this._panelWidth * .7,
- height: this._panelHeight,
+ _width: this._panelWidth * .7,
+ _height: this._panelHeight,
title: "Collection " + workspaceCount,
backgroundColor: "white"
};
@@ -272,7 +281,6 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
onClick={undefined}
- ruleProvider={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
@@ -303,8 +311,10 @@ export class MainView extends React.Component {
</Measure>;
}
+ _canClick = false;
onPointerDown = (e: React.PointerEvent) => {
if (this._flyoutTranslate) {
+ this._canClick = true;
this._flyoutSizeOnDown = e.clientX;
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -335,11 +345,12 @@ export class MainView extends React.Component {
@action
onPointerMove = (e: PointerEvent) => {
this.flyoutWidth = Math.max(e.clientX, 0);
+ Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false);
this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
}
@action
onPointerUp = (e: PointerEvent) => {
- if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4) {
+ if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4 && this._canClick) {
this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0;
this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30);
}
@@ -370,7 +381,6 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
removeDocument={undefined}
- ruleProvider={undefined}
onClick={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
@@ -397,7 +407,6 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
removeDocument={returnFalse}
- ruleProvider={undefined}
onClick={undefined}
ScreenToLocalTransform={this.mainContainerXf}
ContentScaling={returnOne}
@@ -414,6 +423,9 @@ export class MainView extends React.Component {
zoomToScale={emptyFunction}
getScale={returnOne}>
</DocumentView>
+ <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
+ Settings
+ </button>
<button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
@@ -490,7 +502,6 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
removeDocument={this.remButtonDoc}
- ruleProvider={undefined}
onClick={undefined}
ScreenToLocalTransform={this.buttonBarXf}
ContentScaling={returnOne}
@@ -510,12 +521,15 @@ export class MainView extends React.Component {
return (<div id="mainView-container">
<DictationOverlay />
<SharingManager />
+ <SettingsManager />
<GoogleAuthenticationManager />
<DocumentDecorations />
- <InkSelectDecorations />
- {this.mainContent}
+ <GestureOverlay>
+ {this.mainContent}
+ </GestureOverlay>
<PreviewCursor />
<ContextMenu />
+ <RadialMenu />
<PDFMenu />
<MarqueeOptionsMenu />
<RichTextMenu />
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index 7da55fd1c..5f4a52c0c 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -8,6 +8,10 @@
}
}
+#metadataEntry-outer {
+ overflow: auto !important;
+}
+
.metadataEntry-keys {
max-height: 80;
overflow-y: auto;
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 243cdb8f6..23b21ae0c 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -19,7 +19,6 @@ export interface MetadataEntryProps {
export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
@observable private _currentKey: string = "";
@observable private _currentValue: string = "";
- @observable private suggestions: string[] = [];
private _addChildren: boolean = false;
@observable _allSuggestions: string[] = [];
_suggestionDispser: IReactionDisposer | undefined;
@@ -178,11 +177,11 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
}
docSource = docSource as Doc[] | Doc;
if (docSource instanceof Doc) {
- if (docSource.viewType === undefined) {
+ if (docSource._viewType === undefined) {
return (null);
}
} else if (Array.isArray(docSource)) {
- if (!docSource.every(doc => doc.viewType !== undefined)) {
+ if (!docSource.every(doc => doc._viewType !== undefined)) {
return null;
}
}
@@ -197,7 +196,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
_ref = React.createRef<HTMLInputElement>();
render() {
return (
- <div className="metadataEntry-outerDiv">
+ <div className="metadataEntry-outerDiv" id="metadataEntry-outer">
<div className="metadataEntry-inputArea">
Key:
<Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 350a75d29..7a99bf0ae 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -144,7 +144,7 @@ export class OverlayView extends React.Component {
return (null);
}
return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
- d.inOverlay = true;
+ setTimeout(() => d.inOverlay = true, 0);
let offsetx = 0, offsety = 0;
const onPointerMove = action((e: PointerEvent) => {
if (e.buttons === 1) {
@@ -178,7 +178,6 @@ export class OverlayView extends React.Component {
// select={emptyFunction}
// layoutKey={"layout"}
bringToFront={emptyFunction}
- ruleProvider={undefined}
addDocument={undefined}
removeDocument={undefined}
ContentScaling={returnOne}
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
new file mode 100644
index 000000000..4513de2b0
--- /dev/null
+++ b/src/client/views/Palette.scss
@@ -0,0 +1,21 @@
+.palette-container {
+ .palette-thumb {
+ touch-action: pan-x;
+ overflow: scroll;
+ position: absolute;
+ width: 90px;
+ height: 70px;
+
+ .palette-thumbContent {
+ transition: transform .3s;
+
+ .collectionView {
+ overflow: visible;
+
+ .collectionLinearView-outer {
+ overflow: visible;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
new file mode 100644
index 000000000..10aac96a0
--- /dev/null
+++ b/src/client/views/Palette.tsx
@@ -0,0 +1,80 @@
+import * as React from "react";
+import "./Palette.scss";
+import { PointData } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { Docs } from "../documents/Documents";
+import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
+import { List } from "../../new_fields/List";
+import { DocumentView } from "./nodes/DocumentView";
+import { emptyPath, returnFalse, emptyFunction, returnOne, returnEmptyString, returnTrue } from "../../Utils";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { Transform } from "../util/Transform";
+import { computed, action, IReactionDisposer, reaction, observable } from "mobx";
+import { FieldValue, Cast, NumCast } from "../../new_fields/Types";
+import { observer } from "mobx-react";
+import { DocumentContentsView } from "./nodes/DocumentContentsView";
+import { CollectionStackingView } from "./collections/CollectionStackingView";
+import { CollectionView } from "./collections/CollectionView";
+import { CollectionSubView, SubCollectionViewProps } from "./collections/CollectionSubView";
+import { makeInterface } from "../../new_fields/Schema";
+import { documentSchema } from "../../new_fields/documentSchemas";
+
+export interface PaletteProps {
+ x: number;
+ y: number;
+ thumb: number[];
+ thumbDoc: Doc;
+}
+
+@observer
+export default class Palette extends React.Component<PaletteProps> {
+ private _selectedDisposer?: IReactionDisposer;
+ @observable private _selectedIndex: number = 0;
+
+ componentDidMount = () => {
+ this._selectedDisposer = reaction(
+ () => NumCast(this.props.thumbDoc.selectedIndex),
+ (i) => this._selectedIndex = i,
+ { fireImmediately: true }
+ );
+ }
+
+ componentWillUnmount = () => {
+ this._selectedDisposer && this._selectedDisposer();
+ }
+
+ render() {
+ return (
+ <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
+ <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
+ <div className="palette-thumbContent" style={{ transform: `translate(-${(this._selectedIndex * 50) + 10}px, 0px)` }}>
+ <DocumentView
+ Document={this.props.thumbDoc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={() => window.screen.width}
+ PanelHeight={() => window.screen.height}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}>
+ </DocumentView>
+ </div>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 9706d0f99..c011adb20 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -31,8 +31,8 @@ export class PreviewCursor extends React.Component<{}> {
if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
- title: url, width: 400, height: 315,
- nativeWidth: 600, nativeHeight: 472.5,
+ title: url, _width: 400, _height: 315,
+ _nativeWidth: 600, _nativeHeight: 472.5,
x: newPoint[0], y: newPoint[1]
}));
}
@@ -42,17 +42,17 @@ export class PreviewCursor extends React.Component<{}> {
if (re.test(e.clipboardData.getData("text/plain"))) {
const url = e.clipboardData.getData("text/plain");
return PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
- title: url, width: 500, height: 300,
+ title: url, _width: 500, _height: 300,
// nativeWidth: 300, nativeHeight: 472.5,
x: newPoint[0], y: newPoint[1]
}));
}
// creates text document
- return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument({
- width: 500,
+ return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
+ _width: 500,
limitHeight: 400,
- autoHeight: true,
+ _autoHeight: true,
x: newPoint[0],
y: newPoint[1],
title: "-pasted text-"
@@ -65,7 +65,7 @@ export class PreviewCursor extends React.Component<{}> {
return PreviewCursor._addDocument(Docs.Create.ImageDocument(
arr[1], {
- width: 300, title: arr[1],
+ _width: 300, title: arr[1],
x: newPoint[0],
y: newPoint[1],
}));
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index 69bebe0e9..bbed8cd96 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -15,12 +15,12 @@
.templating-button {
width: 20px;
height: 20px;
- border-radius: 50%;
- opacity: 0.9;
- font-size: 14;
- background-color: $dark-color;
- color: $light-color;
- text-align: center;
+ padding-left: 5px;
+ background: black;
+ color: white;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
cursor: pointer;
&:hover {
@@ -42,6 +42,7 @@
.templateToggle, .chromeToggle {
text-align: left;
+ color: black;
}
input {
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index d953d3bab..f61eb9cd0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,16 +1,14 @@
-import { action, observable } from "mobx";
+import { action, observable, runInAction, ObservableSet } from "mobx";
import { observer } from "mobx-react";
-import { DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
import './TemplateMenu.scss';
import { DocumentView } from "./nodes/DocumentView";
import { Template, Templates } from "./Templates";
import React = require("react");
-import { Doc } from "../../new_fields/Doc";
-import { StrCast } from "../../new_fields/Types";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faEdit, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
+import { Doc, DocListCast } from "../../new_fields/Doc";
+import { StrCast, Cast } from "../../new_fields/Types";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -43,7 +41,7 @@ class OtherToggle extends React.Component<{ checked: boolean, name: string, togg
}
export interface TemplateMenuProps {
- docs: DocumentView[];
+ docViews: DocumentView[];
templates: Map<Template, boolean>;
}
@@ -51,20 +49,14 @@ export interface TemplateMenuProps {
@observer
export class TemplateMenu extends React.Component<TemplateMenuProps> {
@observable private _hidden: boolean = true;
- private _downx = 0;
- private _downy = 0;
- private _dragRef = React.createRef<HTMLUListElement>();
- toggleCustom = (e: React.ChangeEvent<HTMLInputElement>): void => {
- this.props.docs.map(dv => dv.setCustomView(e.target.checked));
- }
- toggleNarrative = (e: React.ChangeEvent<HTMLInputElement>): void => {
- this.props.docs.map(dv => dv.setNarrativeView(e.target.checked));
+ toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
+ this.props.docViews.map(dv => dv.setCustomView(e.target.checked, layout));
}
toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => {
SelectionManager.DeselectAll();
- const topDocView = this.props.docs[0];
+ const topDocView = this.props.docViews[0];
const ex = e.target.getBoundingClientRect().left;
const ey = e.target.getBoundingClientRect().top;
DocumentView.FloatDoc(topDocView, ex, ey);
@@ -75,25 +67,12 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@action
toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
if (event.target.checked) {
- this.props.docs.map(d => d.Document["show" + template.Name] = template.Name.toLowerCase());
+ this.props.docViews.map(d => d.Document["show" + template.Name] = template.Name.toLowerCase());
} else {
- this.props.docs.map(d => d.Document["show" + template.Name] = "");
+ this.props.docViews.map(d => d.Document["show" + template.Name] = "");
}
}
- @undoBatch
- @action
- clearTemplates = (event: React.MouseEvent) => {
- Templates.TemplateList.forEach(template => this.props.docs.forEach(d => d.Document["show" + template.Name] = undefined));
- ["backgroundColor", "borderRounding", "width", "height"].forEach(field => this.props.docs.forEach(d => {
- if (d.Document.isTemplateDoc && d.props.DataDoc) {
- d.Document[field] = undefined;
- } else if (d.Document["default" + field[0].toUpperCase() + field.slice(1)] !== undefined) {
- d.Document[field] = Doc.GetProto(d.Document)[field] = undefined;
- }
- }));
- }
-
@action
toggleTemplateActivity = (): void => {
this._hidden = !this._hidden;
@@ -102,68 +81,45 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@undoBatch
@action
toggleChrome = (): void => {
- this.props.docs.map(dv => {
+ this.props.docViews.map(dv => {
const layout = Doc.Layout(dv.Document);
- layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
+ layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : "enabled");
});
}
- onAliasButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
- e.stopPropagation();
- }
- onAliasButtonDown = (e: React.PointerEvent): void => {
- this._downx = e.clientX;
- this._downy = e.clientY;
- e.stopPropagation();
- e.preventDefault();
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.addEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
- document.addEventListener("pointerup", this.onAliasButtonUp);
- }
- onAliasButtonMoved = (e: PointerEvent): void => {
- if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
-
- const dragDocView = this.props.docs[0];
- const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
- const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- dragData.embedDoc = true;
- dragData.dropAction = "alias";
- DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
- offsetX: dragData.offset[0],
- offsetY: dragData.offset[1],
- hideSource: false
- });
+ // todo: add brushes to brushMap to save with a style name
+ onCustomKeypress = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ runInAction(() => this._addedKeys.add(this._customRef.current!.value));
}
- e.stopPropagation();
+ }
+ componentDidMount() {
+ !this._addedKeys && (this._addedKeys = new ObservableSet());
+ Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))).
+ filter(key => key.startsWith("layout_")).
+ map(key => runInAction(() => this._addedKeys.add(key.replace("layout_", ""))));
+ DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => {
+ if (StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title)) {
+ runInAction(() => this._addedKeys.add(StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title)));
+ }
+ });
}
+ _addedKeys = new ObservableSet();
+ _customRef = React.createRef<HTMLInputElement>();
render() {
- const layout = Doc.Layout(this.props.docs[0].Document);
+ const layout = Doc.Layout(this.props.docViews[0].Document);
const templateMenu: Array<JSX.Element> = [];
this.props.templates.forEach((checked, template) =>
templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
- templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docs[0].Document.z ? true : false} toggle={this.toggleFloat} />);
- templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
- templateMenu.push(<OtherToggle key={"narrative"} name={"Narrative"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") === "layout_narrative"} toggle={this.toggleNarrative} />);
- templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
- return (
- <Flyout anchorPoint={anchorPoints.LEFT_TOP}
- content={<ul className="template-list" ref={this._dragRef} style={{ display: "block" }}>
- {templateMenu}
- {<button onClick={this.clearTemplates}>Restore Defaults</button>}
- </ul>}>
- <span className="parentDocumentSelector-button" >
- <FontAwesomeIcon icon={faEdit} size={"lg"} />
- </span>
- {/* <div className="templating-menu" onPointerDown={this.onAliasButtonDown}>
- <div title="Drag:(create alias). Tap:(modify layout)." className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
- </div> */}
- </Flyout>
+ templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docViews[0].Document.z ? true : false} toggle={this.toggleFloat} />);
+ templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
+ this._addedKeys && Array.from(this._addedKeys).map(layout =>
+ templateMenu.push(<OtherToggle key={layout} name={layout} checked={StrCast(this.props.docViews[0].Document.layoutKey, "layout") === "layout_" + layout} toggle={e => this.toggleLayout(e, layout)} />)
);
+ return <ul className="template-list" style={{ display: "block" }}>
+ {templateMenu}
+ <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress}></input>
+ </ul>;
}
} \ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 8af8a6280..8c60f1c36 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -43,7 +43,7 @@ export namespace Templates {
`<div>
<div style="height:100%; width:100%;">{layout}</div>
<div style="bottom: 0; font-size:14px; width:100%; position:absolute">
- <FormattedTextBox {...props} height="min-content" fieldKey={"caption"} hideOnLeave={"true"} />
+ <FormattedTextBox {...props} fieldKey={"caption"} hideOnLeave={"true"} />
</div>
</div>` );
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 251cd41e5..7800c4019 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -1,12 +1,18 @@
import * as React from 'react';
import { action } from 'mobx';
import { InteractionUtils } from '../util/InteractionUtils';
+import { SelectionManager } from '../util/SelectionManager';
+import { RadialMenu } from './nodes/RadialMenu';
const HOLD_DURATION = 1000;
export abstract class Touchable<T = {}> extends React.Component<T> {
+ //private holdTimer: NodeJS.Timeout | undefined;
private holdTimer: NodeJS.Timeout | undefined;
+ private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _touchDrag: boolean = false;
protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
@@ -19,26 +25,57 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
* When a touch even starts, we keep track of each touch that is associated with that event
*/
@action
- protected onTouchStart = (e: React.TouchEvent): void => {
- for (let i = 0; i < e.targetTouches.length; i++) {
- const pt: any = e.targetTouches.item(i);
- // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
- // and this seems to be the only way of differentiating pen and touch on touch events
- if (pt.radiusX > 0.5 && pt.radiusY > 0.5) {
+ protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
+ const actualPts: React.Touch[] = [];
+ const te = me.touchEvent;
+ // loop through all touches on screen
+ for (const pt of me.touches) {
+ actualPts.push(pt);
+ if (this.prevPoints.has(pt.identifier)) {
this.prevPoints.set(pt.identifier, pt);
}
+ // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives
+ for (const tPt of me.changedTouches) {
+ if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) {
+ // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
+ // and this seems to be the only way of differentiating pen and touch on touch events
+ if (pt.radiusX > 1 && pt.radiusY > 1) {
+ this.prevPoints.set(pt.identifier, pt);
+ }
+ }
+ }
}
+ const ptsToDelete: number[] = [];
+ this.prevPoints.forEach(pt => {
+ if (!actualPts.includes(pt)) {
+ ptsToDelete.push(pt.identifier);
+ }
+ });
+
+ // console.log(ptsToDelete.length);
+ ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
+
if (this.prevPoints.size) {
switch (this.prevPoints.size) {
case 1:
- this.handle1PointerDown(e);
- e.persist();
- this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e), HOLD_DURATION);
+ this.handle1PointerDown(te, me);
+ te.persist();
+ // if (this.holdTimer) {
+ // clearTimeout(this.holdTimer)
+ // this.holdTimer = undefined;
+ // }
+ this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te, me), HOLD_DURATION);
+ // e.stopPropagation();
+ // console.log(this.holdTimer);
break;
case 2:
- this.handle2PointersDown(e);
+ this.handle2PointersDown(te, me);
+ // e.stopPropagation();
break;
+ // case 5:
+ // this.handleHandDown(te);
+ // break;
}
}
}
@@ -47,26 +84,29 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
* Handle touch move event
*/
@action
- protected onTouch = (e: TouchEvent): void => {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ const te = me.touchEvent;
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
// if we're not actually moving a lot, don't consider it as dragging yet
if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
this._touchDrag = true;
if (this.holdTimer) {
+ console.log("CLEAR");
clearTimeout(this.holdTimer);
+ // this.holdTimer = undefined;
}
+ // console.log(myTouches.length);
switch (myTouches.length) {
case 1:
- this.handle1PointerMove(e);
+ this.handle1PointerMove(te, me);
break;
case 2:
- this.handle2PointersMove(e);
+ this.handle2PointersMove(te, me);
break;
}
- for (let i = 0; i < e.targetTouches.length; i++) {
- const pt = e.targetTouches.item(i);
+ for (const pt of me.touches) {
if (pt) {
if (this.prevPoints.has(pt.identifier)) {
this.prevPoints.set(pt.identifier, pt);
@@ -76,11 +116,11 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
}
@action
- protected onTouchEnd = (e: TouchEvent): void => {
+ protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
// console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up");
// remove all the touches associated with the event
- for (let i = 0; i < e.changedTouches.length; i++) {
- const pt = e.changedTouches.item(i);
+ const te = me.touchEvent;
+ for (const pt of me.changedTouches) {
if (pt) {
if (this.prevPoints.has(pt.identifier)) {
this.prevPoints.delete(pt.identifier);
@@ -89,9 +129,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
}
if (this.holdTimer) {
clearTimeout(this.holdTimer);
+ console.log("clear");
}
this._touchDrag = false;
- e.stopPropagation();
+ te.stopPropagation();
// if (e.targetTouches.length === 0) {
@@ -101,40 +142,66 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
if (this.prevPoints.size === 0) {
this.cleanUpInteractions();
}
+ e.stopPropagation();
}
cleanUpInteractions = (): void => {
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
+ this.removeMoveListeners();
+ this.removeEndListeners();
}
- handle1PointerMove = (e: TouchEvent): any => {
+ handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
e.stopPropagation();
e.preventDefault();
}
- handle2PointersMove = (e: TouchEvent): any => {
+ handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
e.stopPropagation();
e.preventDefault();
}
- handle1PointerDown = (e: React.TouchEvent): any => {
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
+ handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
}
- handle2PointersDown = (e: React.TouchEvent): any => {
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
+ handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
}
- handle1PointerHoldStart = (e: React.TouchEvent): any => {
- console.log("Hold");
+ handle1PointerHoldStart = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
e.stopPropagation();
e.preventDefault();
+ this.removeMoveListeners();
+ }
+
+ addMoveListeners = () => {
+ const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
+ document.addEventListener("dashOnTouchMove", handler);
+ this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler);
+ }
+
+ removeMoveListeners = () => {
+ this.moveDisposer && this.moveDisposer();
+ }
+
+ addEndListeners = () => {
+ const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
+ document.addEventListener("dashOnTouchEnd", handler);
+ this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler);
+ }
+
+ removeEndListeners = () => {
+ this.endDisposer && this.endDisposer();
+ }
+
+ handleHandDown = (e: React.TouchEvent) => {
+ // e.stopPropagation();
+ // e.preventDefault();
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
new file mode 100644
index 000000000..4815f1a59
--- /dev/null
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -0,0 +1,40 @@
+
+.collectionCarouselView-outer {
+ background: gray;
+ .collectionCarouselView-caption {
+ margin-left: 10%;
+ margin-right: 10%;
+ height: 50;
+ display: inline-block;
+ width: 80%;
+ }
+ .collectionCarouselView-image {
+ height: calc(100% - 50px);
+ display: inline-block;
+ width: 100%;
+ }
+}
+.carouselView-back {
+ position: absolute;
+ display: flex;
+ left: 0;
+ top: 50%;
+ width: 30;
+ height: 30;
+ background: lightgray;
+ align-items: center;
+ border-radius: 5px;
+ justify-content: center;
+}
+.carouselView-fwd {
+ position: absolute;
+ display: flex;
+ right: 0;
+ top: 50%;
+ width: 30;
+ height: 30;
+ background: lightgray;
+ align-items: center;
+ border-radius: 5px;
+ justify-content: center;
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
new file mode 100644
index 000000000..00edf71dd
--- /dev/null
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -0,0 +1,79 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { observable, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { makeInterface } from '../../../new_fields/Schema';
+import { NumCast, StrCast } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import "./CollectionCarouselView.scss";
+import { CollectionSubView } from './CollectionSubView';
+import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
+import { Doc } from '../../../new_fields/Doc';
+import { FormattedTextBox } from '../nodes/FormattedTextBox';
+
+type CarouselDocument = makeInterface<[typeof documentSchema,]>;
+const CarouselDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionCarouselView extends CollectionSubView(CarouselDocument) {
+ @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ private _dropDisposer?: DragManager.DragDropDisposer;
+
+ componentWillUnmount() {
+ this._dropDisposer && this._dropDisposer();
+ }
+
+ componentDidMount() {
+ }
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this._dropDisposer && this._dropDisposer();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ }
+ }
+
+ advance = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + 1) % this.childLayoutPairs.length;
+ }
+ goback = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ }
+
+ panelHeight = () => this.props.PanelHeight() - 50;
+ @computed get content() {
+ const index = NumCast(this.layoutDoc._itemIndex);
+ return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
+ <div>
+ <div className="collectionCarouselView-image">
+ <ContentFittingDocumentView {...this.props}
+ Document={this.childLayoutPairs[index].layout}
+ DataDocument={this.childLayoutPairs[index].data}
+ PanelHeight={this.panelHeight}
+ getTransform={this.props.ScreenToLocalTransform} />
+ </div>
+ <div className="collectionCarouselView-caption" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}>
+ <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+ </div>
+ </div>
+ }
+ @computed get buttons() {
+ return <>
+ <div key="back" className="carouselView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={this.goback}>
+ <FontAwesomeIcon icon={faCaretLeft} size={"2x"} />
+ </div>
+ <div key="fwd" className="carouselView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={this.advance}>
+ <FontAwesomeIcon icon={faCaretRight} size={"2x"} />
+ </div>
+ </>;
+ }
+ render() {
+ return <div className="collectionCarouselView-outer" ref={this.createDashEventsTarget}>
+ {this.content}
+ {this.buttons}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 022eccc13..cb413b3e3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -35,6 +35,7 @@ import { ComputedField } from '../../../new_fields/ScriptField';
import { InteractionUtils } from '../../util/InteractionUtils';
import { TraceMobx } from '../../../new_fields/util';
import { Scripting } from '../../util/Scripting';
+import { PresElementBox } from '../presentationview/PresElementBox';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -132,7 +133,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
@action
- public static CloseRightSplit(document: Doc): boolean {
+ public static CloseRightSplit(document: Opt<Doc>): boolean {
if (!CollectionDockingView.Instance) return false;
const instance = CollectionDockingView.Instance;
let retVal = false;
@@ -140,14 +141,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) &&
- Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
+ ((!document && DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document.isDisplayPanel) ||
+ (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)))) {
child.contentItems[0].remove();
instance.layoutChanged(document);
return true;
} else {
Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) &&
- Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
+ ((!document && DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document.isDisplayPanel) ||
+ (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)))) {
child.contentItems[j].remove();
child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
return true;
@@ -172,9 +175,43 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
this.stateChanged();
}
+ @undoBatch
+ @action
+ public static ReplaceRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[], addToSplit?: boolean): boolean {
+ if (!CollectionDockingView.Instance) return false;
+ const instance = CollectionDockingView.Instance;
+ let retVal = false;
+ if (instance._goldenLayout.root.contentItems[0].isRow) {
+ retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+ if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+ DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) {
+ const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath);
+ child.addChild(newItemStackConfig, undefined);
+ !addToSplit && child.contentItems[0].remove();
+ instance.layoutChanged(document);
+ return true;
+ }
+ return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+ if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.Document.isDisplayPanel) {
+ const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath);
+ child.addChild(newItemStackConfig, undefined);
+ !addToSplit && child.contentItems[j].remove();
+ instance.layoutChanged(document);
+ return true;
+ }
+ return false;
+ });
+ });
+ }
+ if (retVal) {
+ instance.stateChanged();
+ }
+ return retVal;
+ }
+
//
- // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
+ // Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
//
@undoBatch
@action
@@ -207,36 +244,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
instance.layoutChanged();
return true;
}
+
//
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@undoBatch
@action
- public static UseRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
- if (!CollectionDockingView.Instance) return false;
- const instance = CollectionDockingView.Instance;
- if (instance._goldenLayout.root.contentItems[0].isRow) {
- let found: DocumentView | undefined;
- Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
- if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
- DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.props.Document.isDisplayPanel) {
- found = DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!;
- } else {
- Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
- if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.props.Document.isDisplayPanel) {
- found = DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!;
- return true;
- }
- return false;
- });
- }
- });
- if (found) {
- Doc.GetProto(found.props.Document).data = new List<Doc>([document]);
- } else {
- const stackView = Docs.Create.FreeformDocument([document], { fitToBox: true, isDisplayPanel: true, title: "document viewer" });
- CollectionDockingView.AddRightSplit(stackView, undefined, []);
- }
+ public static UseRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[], shiftKey?: boolean) {
+ document.isDisplayPanel = true;
+ if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, dataDoc, libraryPath, shiftKey)) {
+ CollectionDockingView.AddRightSplit(document, dataDoc, libraryPath);
}
}
@@ -305,7 +322,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
componentDidMount: () => void = () => {
if (this._containerRef.current) {
this.reactionDisposer = reaction(
- () => StrCast(this.props.Document.dockingConfig),
+ () => this.props.Document.dockingConfig,
() => {
if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
// Because this is in a set timeout, if this component unmounts right after mounting,
@@ -451,7 +468,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
onPointerDown={e => {
e.preventDefault();
e.stopPropagation();
- DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+ const dragData = new DragManager.DocumentDragData([doc]);
+ dragData.dropAction = doc.dropAction === "alias" ? "alias" : doc.dropAction === "copy" ? "copy" : undefined;
+ DragManager.StartDocumentDrag([dragSpan], dragData, e.clientX, e.clientY);
}}>
<FontAwesomeIcon icon="file" size="lg" />
</span>, dragSpan);
@@ -504,7 +523,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
stack.header.element[0].style.backgroundColor = DocServer.Control.isReadOnly() ? "#228540" : undefined;
stack.header.element.on('mousedown', (e: any) => {
if (e.target === stack.header.element[0] && e.button === 1) {
- this.AddTab(stack, Docs.Create.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
+ this.AddTab(stack, Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
}
});
@@ -618,24 +637,31 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
**/
@undoBatch
@action
- public PinDoc(doc: Doc) {
+ public static PinDoc(doc: Doc) {
//add this new doc to props.Document
const curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc;
if (curPres) {
- const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
- Doc.GetProto(pinDoc).presentationTargetDoc = doc;
- Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
- const data = Cast(curPres.data, listSpec(Doc));
- if (data) {
- data.push(pinDoc);
- } else {
- curPres.data = new List([pinDoc]);
- }
+ const pinDoc = Doc.MakeAlias(doc);
+ pinDoc.presentationTargetDoc = doc;
+ Doc.AddDocToList(curPres, "data", pinDoc);
if (!DocumentManager.Instance.getDocumentView(curPres)) {
- this.addDocTab(curPres, undefined, "onRight");
+ CollectionDockingView.AddRightSplit(curPres, undefined);
}
}
}
+ /**
+ * Adds a document to the presentation view
+ **/
+ @undoBatch
+ @action
+ public static UnpinDoc(doc: Doc) {
+ //add this new doc to props.Document
+ const curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc;
+ if (curPres) {
+ const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc));
+ ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]);
+ }
+ }
componentDidMount() {
const observer = new _global.ResizeObserver(action((entries: any) => {
@@ -664,19 +690,19 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
get layoutDoc() { return this._document && Doc.Layout(this._document); }
- panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth;
+ panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : this._panelWidth;
panelHeight = () => this._panelHeight;
- nativeWidth = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!.fitWidth ? NumCast(this.layoutDoc!.nativeWidth) || this._panelWidth : 0;
- nativeHeight = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!.fitWidth ? NumCast(this.layoutDoc!.nativeHeight) || this._panelHeight : 0;
+ nativeWidth = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
+ nativeHeight = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
contentScaling = () => {
if (this.layoutDoc!.type === DocumentType.PDF) {
- if ((this.layoutDoc && this.layoutDoc.fitWidth) ||
- this._panelHeight / NumCast(this.layoutDoc!.nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!.nativeWidth)) {
- return this._panelWidth / NumCast(this.layoutDoc!.nativeWidth);
+ if ((this.layoutDoc && this.layoutDoc._fitWidth) ||
+ this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
+ return this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
} else {
- return this._panelHeight / NumCast(this.layoutDoc!.nativeHeight);
+ return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight);
}
}
const nativeH = this.nativeHeight();
@@ -722,7 +748,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
- ruleProvider={undefined}
ContentScaling={this.contentScaling}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
@@ -733,7 +758,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
focus={emptyFunction}
backgroundColor={returnEmptyString}
addDocTab={this.addDocTab}
- pinToPres={this.PinDoc}
+ pinToPres={DockedFrameRenderer.PinDoc}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
zoomToScale={emptyFunction}
@@ -745,7 +770,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
(<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
style={{
transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
- height: this.layoutDoc && this.layoutDoc.fitWidth ? undefined : "100%",
+ height: this.layoutDoc && this.layoutDoc._fitWidth ? undefined : "100%",
width: this.widthpercent
}}>
{this.docView}
@@ -753,4 +778,4 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); });
-Scripting.addGlobal(function useRightSplit(doc: any) { CollectionDockingView.UseRightSplit(doc, undefined); });
+Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 9191bf822..67062ae41 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -1,9 +1,9 @@
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { makeInterface } from '../../../new_fields/Schema';
-import { BoolCast, NumCast, StrCast } from '../../../new_fields/Types';
+import { BoolCast, NumCast, StrCast, Cast } from '../../../new_fields/Types';
import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { Transform } from '../../util/Transform';
@@ -13,6 +13,7 @@ import { CollectionSubView } from './CollectionSubView';
import { DocumentView } from '../nodes/DocumentView';
import { documentSchema } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
+import { ScriptField } from '../../../new_fields/ScriptField';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -21,22 +22,49 @@ const LinearDocument = makeInterface(documentSchema);
@observer
export class CollectionLinearView extends CollectionSubView(LinearDocument) {
@observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ @observable private _selectedIndex = -1;
private _dropDisposer?: DragManager.DragDropDisposer;
private _widthDisposer?: IReactionDisposer;
+ private _selectedDisposer?: IReactionDisposer;
componentWillUnmount() {
this._dropDisposer && this._dropDisposer();
this._widthDisposer && this._widthDisposer();
+ this._selectedDisposer && this._selectedDisposer();
+ this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+ Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
+ });
}
componentDidMount() {
// is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported).
- this._widthDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
- () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+ () => this.props.Document._width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ { fireImmediately: true }
+ );
+
+ this._selectedDisposer = reaction(
+ () => NumCast(this.props.Document.selectedIndex),
+ (i) => runInAction(() => {
+ this._selectedIndex = i;
+ let selected: any = undefined;
+ this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map(async (pair, ind) => {
+ const isSelected = this._selectedIndex === ind;
+ if (isSelected) {
+ selected = pair;
+ }
+ else {
+ Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
+ }
+ });
+ if (selected && selected.layout) {
+ Cast(selected.layout.proto?.onPointerDown, ScriptField)?.script.run({ this: selected.layout.proto }, console.log);
+ }
+ }),
{ fireImmediately: true }
);
}
- protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this._dropDisposer && this._dropDisposer();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
@@ -45,7 +73,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
- dimension = () => NumCast(this.props.Document.height); // 2 * the padding
+ dimension = () => NumCast(this.props.Document._height); // 2 * the padding
getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
if (!ele.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
@@ -55,16 +83,16 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
render() {
const guid = Utils.GenerateGuid();
return <div className="collectionLinearView-outer">
- <div className="collectionLinearView" ref={this.createDropTarget} >
+ <div className="collectionLinearView" ref={this.createDashEventsTarget} >
<input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
<label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
- <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document.width, 25) }}>
- {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => {
- const nested = pair.layout.viewType === CollectionViewType.Linear;
+ <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25) }}>
+ {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+ const nested = pair.layout._viewType === CollectionViewType.Linear;
const dref = React.createRef<HTMLDivElement>();
- const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
+ const nativeWidth = NumCast(pair.layout._nativeWidth, this.dimension());
const deltaSize = nativeWidth * .15 / 2;
return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
style={{
@@ -80,7 +108,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
addDocTab={this.props.addDocTab}
pinToPres={emptyFunction}
removeDocument={this.props.removeDocument}
- ruleProvider={undefined}
onClick={undefined}
ScreenToLocalTransform={this.getTransform(dref)}
ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 80752303c..e25a2f5eb 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -136,7 +136,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
addDocument = (value: string, shiftDown?: boolean) => {
this._createAliasSelected = false;
const key = StrCast(this.props.parent.props.Document.sectionFilter);
- const newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value });
+ const newDoc = Docs.Create.TextDocument("", { _height: 18, _width: 200, title: value });
newDoc[key] = this.getValue(this.props.heading);
return this.props.parent.props.addDocument(newDoc);
}
@@ -258,7 +258,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@computed get contentLayout() {
const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
const style = this.props.parent; const collapsed = this._collapsed;
- const chromeStatus = this.props.parent.props.Document.chromeStatus;
+ const chromeStatus = this.props.parent.props.Document._chromeStatus;
const newEditableViewProps = {
GetValue: () => "",
SetValue: this.addDocument,
diff --git a/src/client/views/collections/CollectionPivotView.scss b/src/client/views/collections/CollectionPivotView.scss
deleted file mode 100644
index bd3d6c77b..000000000
--- a/src/client/views/collections/CollectionPivotView.scss
+++ /dev/null
@@ -1,57 +0,0 @@
-.collectionPivotView {
- display: flex;
- flex-direction: row;
- position: absolute;
- height:100%;
- width:100%;
- .collectionPivotView-flyout {
- width: 400px;
- height: 300px;
- display: inline-block;
- .collectionPivotView-flyout-item {
- background-color: lightgray;
- text-align: left;
- display: inline-block;
- position: relative;
- width: 100%;
- }
- }
-
- .collectionPivotView-treeView {
- display:flex;
- flex-direction: column;
- width: 200px;
- height: 100%;
- .collectionPivotView-addfacet {
- display:inline-block;
- width: 200px;
- height: 30px;
- background: darkGray;
- text-align: center;
- .collectionPivotView-button {
- align-items: center;
- display: flex;
- width: 100%;
- height: 100%;
- .collectionPivotView-span {
- margin: auto;
- }
- }
- > div, > div > div {
- width: 100%;
- height: 100%;
- text-align: center;
- }
- }
- .collectionPivotView-tree {
- display:inline-block;
- width: 200px;
- height: calc(100% - 30px);
- }
- }
- .collectionPivotView-pivot {
- display:inline-block;
- width: calc(100% - 200px);
- height: 100%;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
deleted file mode 100644
index 6af7cce70..000000000
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { CollectionSubView } from "./CollectionSubView";
-import React = require("react");
-import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
-import { faEdit, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import "./CollectionPivotView.scss";
-import { observer } from "mobx-react";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import { CollectionTreeView } from "./CollectionTreeView";
-import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
-import { Docs } from "../../documents/Documents";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { CompileScript } from "../../util/Scripting";
-import { anchorPoints, Flyout } from "../TemplateMenu";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { List } from "../../../new_fields/List";
-import { Set } from "typescript-collections";
-
-@observer
-export class CollectionPivotView extends CollectionSubView(doc => doc) {
- componentDidMount = () => {
- this.props.Document.freeformLayoutEngine = "pivot";
- if (!this.props.Document.facetCollection) {
- const facetCollection = Docs.Create.FreeformDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true });
- facetCollection.target = this.props.Document;
-
- const scriptText = "setDocFilter(context.target, heading, this.title, checked)";
- const script = CompileScript(scriptText, {
- params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name },
- typecheck: false,
- editable: true,
- });
- if (script.compiled) {
- facetCollection.onCheckedClick = new ScriptField(script);
- }
-
- const openDocText = "const alias = getAlias(this); alias.layoutKey = 'layoutCustom'; useRightSplit(alias); ";
- const openDocScript = CompileScript(openDocText, {
- params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name },
- typecheck: false,
- editable: true,
- });
- if (openDocScript.compiled) {
- this.props.Document.onChildClick = new ScriptField(openDocScript);
- }
-
- this.props.Document.facetCollection = facetCollection;
- this.props.Document.fitToBox = true;
- }
- }
-
- @computed get fieldExtensionDoc() {
- return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
- }
-
- bodyPanelWidth = () => this.props.PanelWidth() - 200;
- getTransform = () => this.props.ScreenToLocalTransform().translate(-200, 0);
-
- @computed get _allFacets() {
- const facets = new Set<string>();
- this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
- return facets.toArray();
- }
-
- facetClick = (facet: string) => {
- const facetCollection = this.props.Document.facetCollection;
- if (facetCollection instanceof Doc) {
- const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facet);
- if (found !== -1) {
- //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]);
- (facetCollection.data as List<Doc>).splice(found, 1);
- } else {
- const facetValues = new Set<string>();
- this.childDocs.forEach(child => {
- Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facet]?.toString() || "(null)"));
- facetValues.add(child[facet]?.toString() || "(null)");
- });
-
- const newFacetVals = facetValues.toArray().map(val => Docs.Create.TextDocument({ title: val.toString() }));
- const newFacet = Docs.Create.FreeformDocument(newFacetVals, { title: facet, treeViewOpen: true, isFacetFilter: true });
- Doc.AddDocToList(facetCollection, "data", newFacet);
- }
- }
- }
-
- render() {
- const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null);
- const flyout = (
- <div className="collectionPivotView-flyout" title=" ">
- {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" onClick={e => this.facetClick(facet)}>
- <input type="checkbox" checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
- return d.title === facet;
- })} />
- <span className="checkmark" />
- {facet}
- </label>)}
- </div>
- );
- return !facetCollection ? (null) : <div className="collectionPivotView">
- <div className="collectionPivotView-treeView">
- <div className="collectionPivotView-addFacet" onPointerDown={e => e.stopPropagation()}>
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
- <div className="collectionPivotView-button">
- <span className="collectionPivotView-span">Facet Filters</span>
- <FontAwesomeIcon icon={faEdit} size={"lg"} />
- </div>
- </Flyout>
- </div>
- <div className="collectionPivotView-tree">
- <CollectionTreeView {...this.props} Document={facetCollection} />
- </div>
- </div>
- <div className="collectionPivotView-pivot">
- <CollectionFreeFormView {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
- </div>
- </div>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 79a34bc00..caffa7eb1 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -145,7 +145,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
DataDoc: this.props.rowProps.original,
LibraryPath: [],
fieldKey: this.props.rowProps.column.id as string,
- ruleProvider: undefined,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
isSelected: returnFalse,
@@ -226,14 +225,14 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
if (value.startsWith(":=")) {
return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
}
- const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (!script.compiled) {
return false;
}
return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
}}
OnFillDown={async (value: string) => {
- const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
DocListCast(this.props.Document[this.props.fieldKey]).
forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, script.run));
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index cb95dcbbc..a24140b48 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -15,6 +15,11 @@
display: flex;
justify-content: space-between;
flex-wrap: nowrap;
+ touch-action: none;
+
+ div {
+ touch-action: none;
+ }
.collectionSchemaView-tableContainer {
@@ -34,9 +39,9 @@
cursor: col-resize;
}
- .documentView-node:first-child {
- background: $light-color;
- }
+ // .documentView-node:first-child {
+ // background: $light-color;
+ // }
}
.ReactTable {
@@ -49,7 +54,7 @@
.rt-table {
height: 100%;
display: -webkit-inline-box;
- direction: ltr;
+ direction: ltr;
overflow: visible;
}
@@ -202,7 +207,7 @@ button.add-column {
border-bottom: 1px solid lightgray;
&:first-child {
- padding-top : 0;
+ padding-top: 0;
}
&:last-child {
@@ -231,7 +236,7 @@ button.add-column {
transition: background-color 0.2s;
&:hover {
- background-color: $light-color-secondary;
+ background-color: $light-color-secondary;
}
&.active {
@@ -267,7 +272,7 @@ button.add-column {
overflow-y: scroll;
position: absolute;
top: 28px;
- box-shadow: 0 10px 16px rgba(0,0,0,0.1);
+ box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
.key-option {
background-color: $light-color;
@@ -380,6 +385,7 @@ button.add-column {
&.editing {
padding: 0;
+
input {
outline: 0;
border: none;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b466d9511..fa8be5177 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -144,7 +144,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
LibraryPath={this.props.LibraryPath}
childDocs={this.childDocs}
renderDepth={this.props.renderDepth}
- ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
PanelWidth={this.previewWidth}
PanelHeight={this.previewHeight}
getTransform={this.getPreviewTransform}
@@ -478,7 +477,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@undoBatch
createRow = () => {
- const newDoc = Docs.Create.TextDocument({ title: "", width: 100, height: 30 });
+ const newDoc = Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 });
this.props.addDocument(newDoc);
}
@@ -625,6 +624,19 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return Array.from(Object.keys(keys));
}
+ @undoBatch
+ @action
+ toggleTextwrap = async () => {
+ const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ if (textwrappedRows.length) {
+ this.props.Document.textwrappedSchemaRows = new List<string>([]);
+ } else {
+ const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+ const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
+ this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ }
+ }
+
@action
toggleTextWrapRow = (doc: Doc): void => {
const textWrapped = this.textWrappedRows;
@@ -643,7 +655,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const expanded = {};
//@ts-ignore
expandedRowsList.forEach(row => expanded[row] = true);
- console.log("text wrapped rows", ...[...this.textWrappedRows]); // TODO: get component to rerender on text wrap change without needign to console.log :((((
+ const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
return <ReactTable
style={{ position: "relative" }}
@@ -679,7 +691,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
+ // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
+ ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" })
}
}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index e1577cfee..293dc5414 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -19,6 +19,7 @@
position: absolute;
top: 0;
overflow-y: auto;
+ overflow-x: hidden;
flex-wrap: wrap;
transition: top .5s;
>div {
@@ -385,4 +386,4 @@
.rc-switch-checked .rc-switch-inner {
left: 8px;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ca792c134..d772ace23 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,14 +4,13 @@ import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types";
import { emptyFunction, Utils } from "../../../Utils";
-import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -39,31 +38,31 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
@computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
- @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d).layout as Doc) || d); }
- @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
- @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
- @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
+ @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); }
+ @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
+ @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); }
+ @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
@computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
- @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document.chromeStatus !== 'view-mode' && this.props.Document.chromeStatus !== 'disabled')); }
+ @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
@computed get columnWidth() {
return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
- children(docs: Doc[]) {
+ children(docs: Doc[], columns?: number) {
this._docXfs.length = 0;
return docs.map((d, i) => {
- const width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
const height = () => this.getDocHeight(d);
+ const width = () => this.widthScale * Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
const dref = React.createRef<HTMLDivElement>();
const dxf = () => this.getDocTransform(d, dref.current!);
this._docXfs.push({ dxf: dxf, width: width, height: height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.getDisplayDoc(d, (d.resolvedDataDoc as Doc) || d, dxf, width)}
+ {this.getDisplayDoc(d, Cast(d.resolvedDataDoc, Doc, null) || this.props.DataDoc, dxf, width)}
</div>;
});
}
@@ -104,7 +103,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
componentDidMount() {
super.componentDidMount();
this._heightDisposer = reaction(() => {
- if (this.props.Document.autoHeight) {
+ if (this.props.Document._autoHeight) {
const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
if (this.isStackingView) {
const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
@@ -125,7 +124,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
},
(hgt: number) => {
const doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
- doc && hgt > 0 && (Doc.Layout(doc).height = hgt);
+ doc && hgt > 0 && (Doc.Layout(doc)._height = hgt);
},
{ fireImmediately: true }
);
@@ -148,11 +147,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
- this.createDropTarget(ele!); //so the whole grid is the drop target?
- }
-
- overlays = (doc: Doc) => {
- return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), titleHover: StrCast(this.props.Document.showTitleHovers), caption: StrCast(this.props.Document.showCaptions) } : {};
+ this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
}
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
@@ -165,9 +160,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
Document={doc}
DataDocument={dataDoc}
LibraryPath={this.props.LibraryPath}
- showOverlays={this.overlays}
renderDepth={this.props.renderDepth + 1}
- ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
fitToBox={this.props.fitToBox}
onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
PanelWidth={width}
@@ -188,16 +181,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
getDocHeight(d?: Doc) {
if (!d) return 0;
const layoutDoc = Doc.Layout(d);
- const nw = NumCast(layoutDoc.nativeWidth);
- const nh = NumCast(layoutDoc.nativeHeight);
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
- if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) {
+ if (!layoutDoc.ignoreAspect && !layoutDoc._fitWidth && nw && nh) {
const aspect = nw && nh ? nh / nw : 1;
- if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+ if (!(d._nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
return wid * aspect;
}
- return layoutDoc.fitWidth ? !layoutDoc.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc.nativeHeight)) / NumCast(layoutDoc.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
+ return layoutDoc._fitWidth ? !layoutDoc._nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
+ Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
}
columnDividerDown = (e: React.PointerEvent) => {
@@ -242,7 +235,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
targInd = i;
- const axis = this.Document.viewType === CollectionViewType.Masonry ? 0 : 1;
+ const axis = this.Document._viewType === CollectionViewType.Masonry ? 0 : 1;
plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
}
});
@@ -302,7 +295,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropTarget}
+ createDropTarget={this.createDashEventsTarget}
screenToLocalTransform={this.props.ScreenToLocalTransform}
/>;
}
@@ -337,7 +330,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropTarget}
+ createDropTarget={this.createDashEventsTarget}
screenToLocalTransform={this.props.ScreenToLocalTransform}
setDocHeight={this.setDocHeight}
/>;
@@ -360,7 +353,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
onToggle = (checked: Boolean) => {
- this.props.Document.chromeStatus = checked ? "collapsed" : "view-mode";
+ this.props.Document._chromeStatus = checked ? "collapsed" : "view-mode";
}
onContextMenu = (e: React.MouseEvent): void => {
@@ -383,6 +376,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
}
+ @computed get contentScale() {
+ const heightExtra = this.heightPercent > 1 ? this.heightPercent : 1;
+ return Math.min(this.props.Document[WidthSym]() / this.props.PanelWidth(), heightExtra * this.props.Document[HeightSym]() / this.props.PanelHeight());
+ }
+ @computed get widthScale() {
+ return this.heightPercent < 1 ? Math.max(1, this.contentScale) : 1;
+ }
+ @computed get heightPercent() {
+ return this.props.PanelHeight() / this.layoutDoc[HeightSym]();
+ }
render() {
TraceMobx();
const editableViewProps = {
@@ -395,24 +398,26 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
<div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
ref={this.createRef}
style={{
- transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`,
- height: `${Math.max(100, 100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]()))}%`,
- transformOrigin: "top"
+ overflowY: this.props.active() ? "auto" : "hidden",
+ transform: `scale(${Math.min(1, this.heightPercent)})`,
+ height: `${100 * Math.max(1, this.contentScale)}%`,
+ width: `${100 * this.widthScale}%`,
+ transformOrigin: "top left",
}}
onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
onDrop={this.onDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={e => e.stopPropagation()} >
+ onWheel={e => this.props.active() && e.stopPropagation()} >
{this.renderedSections}
{!this.showAddAGroup ? (null) :
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div>}
- {this.props.Document.chromeStatus !== 'disabled' ? <Switch
+ {this.props.Document._chromeStatus !== 'disabled' ? <Switch
onChange={this.onToggle}
onClick={this.onToggle}
- defaultChecked={this.props.Document.chromeStatus !== 'view-mode'}
+ defaultChecked={this.props.Document._chromeStatus !== 'view-mode'}
checkedChildren="edit"
unCheckedChildren="view"
/> : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 23a664359..21982f1ca 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -4,23 +4,24 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { RichTextField } from "../../../new_fields/RichTextField";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { ScriptField } from "../../../new_fields/ScriptField";
import { NumCast, StrCast } from "../../../new_fields/Types";
+import { ImageField } from "../../../new_fields/URLField";
+import { TraceMobx } from "../../../new_fields/util";
import { Docs } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
import { anchorPoints, Flyout } from "../DocumentDecorations";
import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
import "./CollectionStackingView.scss";
-import { TraceMobx } from "../../../new_fields/util";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageField } from "../../../new_fields/URLField";
-import { ImageBox } from "../nodes/ImageBox";
library.add(faPalette);
@@ -135,18 +136,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
addDocument = (value: string, shiftDown?: boolean) => {
- if (value === ":freeForm") {
- return this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { width: 200, height: 200 }));
- } else if (value.startsWith(":")) {
- const { Document, addDocument } = this.props.parent.props;
- const fieldKey = value.substring(1);
- const proto = Doc.GetProto(Document);
- const created = Docs.Get.DocumentFromField(Document, fieldKey, proto);
- return created ? addDocument(created) : false;
- }
+ if (!value) return false;
this._createAliasSelected = false;
const key = StrCast(this.props.parent.props.Document.sectionFilter);
- const newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true });
+ const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true });
newDoc[key] = this.getValue(this.props.heading);
const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
@@ -269,6 +262,57 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@observable _headingsHack: number = 1;
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
+ const layoutItems: ContextMenuProps[] = [];
+ const docItems: ContextMenuProps[] = [];
+
+ const dataDoc = this.props.parent.props.DataDoc || this.props.parent.Document;
+ Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey =>
+ docItems.push({
+ description: ":" + fieldKey, event: () => {
+ const created = Docs.Get.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.parent.props.Document));
+ if (created) {
+ if (this.props.parent.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
+ }
+ return this.props.parent.props.addDocument(created);
+ }
+ }, icon: "compress-arrows-alt"
+ }));
+ Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey =>
+ docItems.push({
+ description: ":" + fieldKey, event: () => {
+ const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+ if (created) {
+ if (this.props.parent.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
+ }
+ return this.props.parent.props.addDocument(created);
+ }
+ }, icon: "compress-arrows-alt"
+ }));
+ layoutItems.push({ description: ":freeform", event: () => this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200, _LODdisable: true })), icon: "compress-arrows-alt" });
+ layoutItems.push({ description: ":carousel", event: () => this.props.parent.props.addDocument(Docs.Create.CarouselDocument([], { _width: 400, _height: 200, _LODdisable: true })), icon: "compress-arrows-alt" });
+ layoutItems.push({ description: ":columns", event: () => this.props.parent.props.addDocument(Docs.Create.MulticolumnDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
+ layoutItems.push({ description: ":image", event: () => this.props.parent.props.addDocument(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
+
+ ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
+ ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
+ ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
+ Doc.GetProto(this.props.parent.props.Document)[name] = "";
+ const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+ if (created) {
+ if (this.props.parent.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
+ }
+ this.props.parent.props.addDocument(created);
+ }
+ });
+ const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
+ ContextMenu.Instance.displayMenu(x, y);
+ }
+
render() {
TraceMobx();
const cols = this.props.cols();
@@ -304,7 +348,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
style={{
width: (style.columnWidth) /
((uniqueHeadings.length +
- ((this.props.parent.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+ ((this.props.parent.props.Document._chromeStatus !== 'view-mode' && this.props.parent.props.Document._chromeStatus !== 'disabled') ? 1 : 0)) || 1)
}}>
<div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
{/* the default bucket (no key value) has a tooltip that describes what it is.
@@ -344,7 +388,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div>
</div> : (null);
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
- const chromeStatus = this.props.parent.props.Document.chromeStatus;
+ const chromeStatus = this.props.parent.props.Document._chromeStatus;
return (
<div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
@@ -363,13 +407,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
gridTemplateColumns: singleColumn ? undefined : templatecols,
gridAutoRows: singleColumn ? undefined : "0px"
}}>
- {this.props.parent.children(this.props.docList)}
+ {this.props.parent.children(this.props.docList, uniqueHeadings.length)}
{singleColumn ? (null) : this.props.parent.columnDragger}
</div>
{(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
style={{ width: style.columnWidth / style.numGroupColumns }}>
- <EditableView {...newEditableViewProps} />
+ <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
</div> : null}
</div>
}
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index 105061f46..8c7e113b2 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -23,10 +23,6 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
this.props.Document.staves = 5;
}
- @computed get fieldExtensionDoc() {
- return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
- }
-
@computed get addStaffButton() {
return <div onPointerDown={this.addStaff}>+</div>;
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5753dd34e..62b9e8380 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
import * as rp from 'request-promise';
import CursorField from "../../../new_fields/CursorField";
import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
@@ -23,6 +23,8 @@ import { basename } from 'path';
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { Networking } from "../../Network";
+import { GestureUtils } from "../../../pen-gestures/GestureUtils";
+import { InteractionUtils } from "../../util/InteractionUtils";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc) => boolean;
@@ -38,56 +40,69 @@ export interface CollectionViewProps extends FieldViewProps {
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
- ruleProvider: Doc | undefined;
children?: never | (() => JSX.Element[]) | React.ReactNode;
isAnnotationOverlay?: boolean;
annotationsKey: string;
+ layoutEngine?: () => string;
}
export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
private dropDisposer?: DragManager.DragDropDisposer;
+ private gestureDisposer?: GestureUtils.GestureEventDisposer;
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private _childLayoutDisposer?: IReactionDisposer;
- protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
- this.dropDisposer && this.dropDisposer();
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this.dropDisposer?.();
+ this.gestureDisposer?.();
+ this.multiTouchDisposer?.();
if (ele) {
this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
+ this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
- this.createDropTarget(ele);
+ this.createDashEventsTarget(ele);
}
componentDidMount() {
- this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
- async (args) => {
- if (args[1] instanceof Doc) {
- this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), "layoutFromParent"));
+ this._childLayoutDisposer = reaction(() => [this.childDocs, (Cast(this.props.Document.childLayout, Doc) as Doc)?.[Id]],
+ (args) => {
+ const childLayout = Cast(this.props.Document.childLayout, Doc);
+ if (childLayout instanceof Doc) {
+ this.childDocs.map(doc => {
+ doc.layout_fromParent = childLayout;
+ doc.layoutKey = "layout_fromParent";
+ });
}
- else if (!(args[1] instanceof Promise)) {
- this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layoutKey === "layoutFromParent" && (doc.layoutKey = "layout"));
+ else if (!(childLayout instanceof Promise)) {
+ this.childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
}
- });
+ }, { fireImmediately: true });
}
componentWillUnmount() {
this._childLayoutDisposer && this._childLayoutDisposer();
}
- @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
- @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+ @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
// The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
// When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
- return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey];
+ const { annotationsKey, fieldKey } = this.props;
+ if (annotationsKey) {
+ return this.dataDoc[fieldKey + "-" + annotationsKey];
+ }
+ return this.dataDoc[fieldKey];
}
get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
- const { Document, DataDoc, fieldKey } = this.props;
- const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, fieldKey, doc)).filter(pair => pair.layout);
+ const { Document, DataDoc } = this.props;
+ const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).filter(pair => pair.layout);
return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
get childDocList() {
@@ -132,6 +147,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
@undoBatch
+ protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
+
+ }
+
+ @undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
@@ -139,8 +159,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this
if (docDragData && !docDragData.applyAsTemplate) {
if (de.altKey && docDragData.draggedDocuments.length) {
- this.childDocs.map(doc =>
- Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layoutFromParent"));
+ this.childDocs.map(doc => {
+ doc.layout_fromParent = docDragData.draggedDocuments[0];
+ doc.layoutKey = "layout_fromParent";
+ });
e.stopPropagation();
return true;
}
@@ -196,7 +218,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
}
} else if (text) {
- this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }));
+ this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
}
return;
}
@@ -206,7 +228,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
if (img) {
const split = img.split("src=\"")[1].split("\"")[0];
- const doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
+ let source = split;
+ if (split.startsWith("data:image") && split.includes("base64")) {
+ const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
+ source = Utils.prepend(clientAccessPath);
+ }
+ const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
ImageUtils.ExtractExif(doc);
this.props.addDocument(doc);
return;
@@ -221,7 +248,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
});
} else {
- const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", width: 300, height: 300, documentText: text });
+ const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300, documentText: text });
this.props.addDocument(htmlDoc);
}
return;
@@ -229,12 +256,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
if (text && text.indexOf("www.youtube.com/watch") !== -1) {
const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
- this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
+ this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 }));
return;
}
let matches: RegExpExecArray | null;
if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
- const newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." });
+ const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
const proto = newBox.proto!;
const documentId = matches[2];
proto[GoogleRef] = documentId;
@@ -281,13 +308,20 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
formData.append('file', file);
const dropFileName = file ? file.name : "-empty-";
- promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => {
- results.map(action(({ clientAccessPath }: any) => {
- const full = { ...options, width: 300, title: dropFileName };
+ promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
+ results.map(action((result: any) => {
+ const { clientAccessPath, nativeWidth, nativeHeight, contentSize } = result;
+ const full = { ...options, _width: 300, title: dropFileName };
const pathname = Utils.prepend(clientAccessPath);
Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
- doc && (Doc.GetProto(doc).fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
- doc && this.props.addDocument(doc);
+ if (doc) {
+ const proto = Doc.GetProto(doc);
+ proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+ nativeWidth && (proto["data-nativeWidth"] = nativeWidth);
+ nativeHeight && (proto["data-nativeHeight"] = nativeHeight);
+ contentSize && (proto.contentSize = contentSize);
+ this.props?.addDocument(doc);
+ }
});
}));
}));
@@ -298,7 +332,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
} else {
if (text && !text.includes("https://")) {
- this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 }));
+ this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
}
batch.end();
}
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
new file mode 100644
index 000000000..a5ce73a92
--- /dev/null
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -0,0 +1,126 @@
+.collectionTimeView, .collectionTimeView-pivot {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ .collectionTimeView-backBtn {
+ background: green;
+ display: inline;
+ margin-right: 20px;
+ }
+ .collectionFreeform-customText {
+ text-align: left;
+ }
+ .collectionFreeform-customDiv {
+ position: absolute;
+ }
+ .collectionTimeView-thumb {
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ transform: rotate(45deg);
+ display: inline-block;
+ background: gray;
+ bottom: 0;
+ margin-bottom: -17px;
+ border-radius: 9px;
+ opacity: 0.25;
+ }
+ .collectionTimeView-thumb-min {
+ margin-left:25%;
+ }
+ .collectionTimeView-thumb-max {
+ margin-left:75%;
+ }
+ .collectionTimeView-thumb-mid {
+ margin-left:50%;
+ }
+
+ .collectionTimeView-flyout {
+ width: 400px;
+ height: 300px;
+ display: inline-block;
+
+ .collectionTimeView-flyout-item {
+ background-color: lightgray;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ }
+ }
+
+ .pivotKeyEntry {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ z-index: 10;
+ pointer-events: all;
+ padding: 5px;
+ border: 1px solid black;
+ }
+
+ .collectionTimeView-treeView {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ height: 100%;
+
+ .collectionTimeView-addfacet {
+ display: inline-block;
+ width: 200px;
+ height: 30px;
+ background: darkGray;
+ text-align: center;
+
+ .collectionTimeView-button {
+ align-items: center;
+ display: flex;
+ width: 100%;
+ height: 100%;
+
+ .collectionTimeView-span {
+ margin: auto;
+ }
+ }
+
+ >div,
+ >div>div {
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ }
+ }
+
+ .collectionTimeView-tree {
+ display: inline-block;
+ width: 100%;
+ height: calc(100% - 30px);
+ }
+ }
+
+ .collectionTimeView-innards {
+ display: inline-block;
+ width: calc(100% - 200px);
+ height: 100%;
+ }
+
+ .collectionTimeView-dragger {
+ background-color: lightgray;
+ height: 40px;
+ width: 20px;
+ position: absolute;
+ border-radius: 10px;
+ top: 55%;
+ border: 1px black solid;
+ z-index: 2;
+ left: -10px;
+ }
+}
+.collectionTimeView-pivot {
+ .collectionFreeform-customText {
+ text-align: center;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
new file mode 100644
index 000000000..4983acbc2
--- /dev/null
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -0,0 +1,331 @@
+import { faEdit } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, trace, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Set } from "typescript-collections";
+import { Doc, DocListCast, Field } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { listSpec } from "../../../new_fields/Schema";
+import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Docs } from "../../documents/Documents";
+import { Scripting } from "../../util/Scripting";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { EditableView } from "../EditableView";
+import { anchorPoints, Flyout } from "../TemplateMenu";
+import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTimeView.scss";
+import React = require("react");
+import { CollectionTreeView } from "./CollectionTreeView";
+import { ObjectField } from "../../../new_fields/ObjectField";
+
+@observer
+export class CollectionTimeView extends CollectionSubView(doc => doc) {
+ _changing = false;
+ @observable _layoutEngine = "pivot";
+
+ componentDidMount() {
+ const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
+ if (!this.props.Document._facetCollection) {
+ const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true });
+ facetCollection.target = this.props.Document;
+ this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
+
+ const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
+ const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+ facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name });
+ this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "boolean", containingCollection: Doc.name, shiftKey: "boolean" });
+ this.props.Document._facetCollection = facetCollection;
+ this.props.Document._fitToBox = true;
+ }
+ if (!this.props.Document.onViewDefClick) {
+ this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" })
+ }
+ }
+
+ bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
+
+ @computed get _allFacets() {
+ const facets = new Set<string>();
+ this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+ Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+ return facets.toArray();
+ }
+
+ /**
+ * Responds to clicking the check box in the flyout menu
+ */
+ facetClick = (facetHeader: string) => {
+ const facetCollection = this.props.Document._facetCollection;
+ if (facetCollection instanceof Doc) {
+ const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
+ if (found !== -1) {
+ (facetCollection.data as List<Doc>).splice(found, 1);
+ const docFilter = Cast(this.props.Document._docFilter, listSpec("string"));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+ docFilter.splice(index, 3);
+ }
+ }
+ } else {
+ const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+ const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+ const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+ newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+ Doc.AddDocToList(facetCollection, "data", newFacet);
+ }
+ }
+ }
+ _canClick = false;
+ _facetWidthOnDown = 0;
+ @observable _facetWidth = 0;
+ onPointerDown = (e: React.PointerEvent) => {
+ this._canClick = true;
+ this._facetWidthOnDown = e.screenX;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+ Math.abs(e.movementX) > 6 && (this._canClick = false);
+ }
+ @action
+ onPointerUp = (e: PointerEvent) => {
+ if (Math.abs(e.screenX - this._facetWidthOnDown) < 6 && this._canClick) {
+ this._facetWidth = this._facetWidth < 15 ? 200 : 0;
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
+ const docItems: ContextMenuProps[] = [];
+ const keySet: Set<string> = new Set();
+
+ this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
+ pair.layout[fieldKey] instanceof RichTextField ||
+ typeof (pair.layout[fieldKey]) === "number" ||
+ typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
+ keySet.toArray().map(fieldKey =>
+ docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
+ docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" })
+ ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
+ const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ ContextMenu.Instance.displayMenu(x, y, ":");
+ }
+
+ @observable private collapsed: boolean = false;
+ private toggleVisibility = action(() => this.collapsed = !this.collapsed);
+
+ _downX = 0;
+ onMinDown = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.onMinMove);
+ document.removeEventListener("pointerup", this.onMinUp);
+ document.addEventListener("pointermove", this.onMinMove);
+ document.addEventListener("pointerup", this.onMinUp);
+ this._downX = e.clientX;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ @action
+ onMinMove = (e: PointerEvent) => {
+ const delta = e.clientX - this._downX;
+ this._downX = e.clientX;
+ const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
+ const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10));
+ this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq + (maxReq - minReq) * delta / this.props.PanelWidth();
+ }
+ onMinUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.onMinMove);
+ document.removeEventListener("pointermove", this.onMinUp);
+ }
+
+ onMaxDown = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.onMaxMove);
+ document.removeEventListener("pointermove", this.onMaxUp);
+ document.addEventListener("pointermove", this.onMaxMove);
+ document.addEventListener("pointerup", this.onMaxUp);
+ this._downX = e.clientX;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ @action
+ onMaxMove = (e: PointerEvent) => {
+ const delta = e.clientX - this._downX;
+ this._downX = e.clientX;
+ const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
+ const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10));
+ this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq + (maxReq - minReq) * delta / this.props.PanelWidth();
+ }
+ onMaxUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.onMaxMove);
+ document.removeEventListener("pointermove", this.onMaxUp);
+ }
+
+ onMidDown = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.onMidMove);
+ document.removeEventListener("pointermove", this.onMidUp);
+ document.addEventListener("pointermove", this.onMidMove);
+ document.addEventListener("pointerup", this.onMidUp);
+ this._downX = e.clientX;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ @action
+ onMidMove = (e: PointerEvent) => {
+ const delta = e.clientX - this._downX;
+ this._downX = e.clientX;
+ const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
+ const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10));
+ this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq - (maxReq - minReq) * delta / this.props.PanelWidth();
+ this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq - (maxReq - minReq) * delta / this.props.PanelWidth();
+ }
+ onMidUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.onMidMove);
+ document.removeEventListener("pointermove", this.onMidUp);
+ }
+
+ layoutEngine = () => this._layoutEngine;
+ @computed get contents() {
+ return <div className="collectionTimeView-innards" key="timeline" style={{ width: this.bodyPanelWidth() }}>
+ <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
+ </div>;
+ }
+ @computed get filterView() {
+ trace();
+ const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
+ const flyout = (
+ <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}` }}>
+ {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+ <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
+ <span className="checkmark" />
+ {facet}
+ </label>)}
+ </div>
+ );
+ return <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+ <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+ <div className="collectionTimeView-button">
+ <span className="collectionTimeView-span">Facet Filters</span>
+ <FontAwesomeIcon icon={faEdit} size={"lg"} />
+ </div>
+ </Flyout>
+ </div>
+ <div className="collectionTimeView-tree" key="tree">
+ <CollectionTreeView {...this.props} Document={facetCollection} />
+ </div>
+ </div>;
+ }
+
+ public static SyncTimelineToPresentation(doc: Doc) {
+ const fieldKey = Doc.LayoutFieldKey(doc);
+ doc[fieldKey + "-timelineCur"] = ComputedField.MakeFunction("(curPresentationItem()[this._pivotField || 'year'] || 0)");
+ }
+ specificMenu = (e: React.MouseEvent) => {
+ const layoutItems: ContextMenuProps[] = [];
+ const doc = this.props.Document;
+
+ layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline" }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot" }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" });
+
+ ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" });
+ }
+
+ render() {
+ const newEditableViewProps = {
+ GetValue: () => "",
+ SetValue: (value: any) => {
+ if (value?.length) {
+ this.props.Document._pivotField = value;
+ return true;
+ }
+ return false;
+ },
+ showMenuOnLoad: true,
+ contents: ":" + StrCast(this.props.Document._pivotField),
+ toggle: this.toggleVisibility,
+ color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+ };
+
+ let nonNumbers = 0;
+ this.childDocs.map(doc => {
+ const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)])));
+ if (Number.isNaN(num)) {
+ nonNumbers++;
+ }
+ });
+ const forceLayout = StrCast(this.props.Document._forceRenderEngine);
+ const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
+ if (doTimeline !== (this._layoutEngine === "timeline")) {
+ if (!this._changing) {
+ this._changing = true;
+ setTimeout(action(() => {
+ this._layoutEngine = doTimeline ? "timeline" : "pivot";
+ this._changing = false;
+ }), 0);
+ }
+ }
+
+
+ const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
+ return !facetCollection ? (null) :
+ <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
+ style={{ height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+ <div className={"pivotKeyEntry"}>
+ <button className="collectionTimeView-backBtn" style={{ width: 50, height: 20, background: "green" }}
+ onClick={action(() => {
+ let pfilterIndex = NumCast(this.props.Document._pfilterIndex);
+ if (pfilterIndex > 0) {
+ this.props.Document._docFilter = ObjectField.MakeCopy(this.props.Document["_pfilter" + --pfilterIndex] as ObjectField);
+ this.props.Document._pfilterIndex = pfilterIndex;
+ } else {
+ this.props.Document._docFilter = new List([]);
+ }
+ })}>
+ back
+ </button>
+ <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
+ </div>
+ {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) :
+ <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+ <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
+ </div>
+ }
+ {this.filterView}
+ {this.contents}
+ {!this.props.isSelected() || !doTimeline ? (null) : <>
+ <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
+ <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
+ <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
+ </>}
+ </div>;
+ }
+}
+
+Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+ let pfilterIndex = NumCast(pivotDoc._pfilterIndex);
+ pivotDoc["_pfilter" + pfilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilter as ObjectField);
+ pivotDoc._pfilterIndex = ++pfilterIndex;
+ runInAction(() => {
+ pivotDoc._docFilter = new List();
+ (bounds.payload as string[]).map(filterVal =>
+ Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+ });
+}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 0b9dc2eb2..2fa6813d7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -7,9 +7,9 @@
border-radius: inherit;
box-sizing: border-box;
height: 100%;
- width:100%;
+ width: 100%;
position: relative;
- top:0;
+ top: 0;
padding-left: 10px;
padding-right: 10px;
background: $light-color-secondary;
@@ -17,6 +17,7 @@
overflow: auto;
user-select: none;
cursor: default;
+ touch-action: pan-y;
ul {
list-style: none;
@@ -115,6 +116,7 @@
.treeViewItem-header {
border: transparent 1px solid;
display: flex;
+
.editableView-container-editing-oneLine {
min-width: 15px;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70860b6bd..001064b30 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,19 +1,22 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked, runInAction } from "mobx";
+import { action, computed, observable, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, Field, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
-import { emptyFunction, Utils, returnFalse, emptyPath } from '../../../Utils';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { emptyFunction, emptyPath, returnFalse, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
+import { makeTemplate } from '../../util/DropConverter';
+import { Scripting } from '../../util/Scripting';
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -21,14 +24,17 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from "../EditableView";
import { MainView } from '../MainView';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import { ImageBox } from '../nodes/ImageBox';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { ScriptBox } from '../ScriptBox';
import { Templates } from '../Templates';
-import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { ScriptBox } from '../ScriptBox';
+import { CollectionViewType } from './CollectionView';
+import { RichTextField } from '../../../new_fields/RichTextField';
+import { ObjectField } from '../../../new_fields/ObjectField';
export interface TreeViewProps {
@@ -39,7 +45,6 @@ export interface TreeViewProps {
prevSibling?: Doc;
renderDepth: number;
deleteDoc: (doc: Doc) => boolean;
- ruleProvider: Doc | undefined;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
@@ -175,8 +180,8 @@ class TreeView extends React.Component<TreeViewProps> {
SetValue={undoBatch((value: string) => Doc.SetInPlace(this.props.document, key, value, false) || true)}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.props.document, key, value, false);
- const layoutDoc = this.props.document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layoutCustom)) : undefined;
- const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ const layoutDoc = this.props.document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layout_custom)) : undefined;
+ const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
return this.props.addDocument(doc);
})}
@@ -207,7 +212,7 @@ class TreeView extends React.Component<TreeViewProps> {
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
}
- ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
e.stopPropagation();
@@ -253,21 +258,21 @@ class TreeView extends React.Component<TreeViewProps> {
}
docWidth = () => {
const layoutDoc = Doc.Layout(this.props.document);
- const aspect = NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth);
+ const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth);
if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
- return NumCast(layoutDoc.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
+ return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
}
docHeight = () => {
const layoutDoc = Doc.Layout(this.props.document);
const bounds = this.boundsOfCollectionDocument;
return Math.min(this.MAX_EMBED_HEIGHT, (() => {
- const aspect = NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth, 1);
+ const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth, 1);
if (aspect) return this.docWidth() * aspect;
if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
- return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) :
- Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc.nativeHeight)) / NumCast(layoutDoc.nativeWidth,
- NumCast(this.props.containingCollection.height)))) :
- NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50;
+ return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
+ Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
+ NumCast(this.props.containingCollection._height)))) :
+ NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
})());
}
@@ -319,8 +324,6 @@ class TreeView extends React.Component<TreeViewProps> {
return rows;
}
- noOverlays = (doc: Doc) => ({ title: "", caption: "" });
-
@computed get renderContent() {
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
@@ -347,8 +350,6 @@ class TreeView extends React.Component<TreeViewProps> {
DataDocument={this.templateDataDoc}
LibraryPath={emptyPath}
renderDepth={this.props.renderDepth + 1}
- showOverlays={this.noOverlays}
- ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
fitToBox={this.boundsOfCollectionDocument !== undefined}
PanelWidth={this.docWidth}
PanelHeight={this.docHeight}
@@ -369,12 +370,12 @@ class TreeView extends React.Component<TreeViewProps> {
@action
bulletClick = (e: React.MouseEvent) => {
if (this.props.onCheckedClick && this.props.document.type !== DocumentType.COL) {
- this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
+ // this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
ScriptCast(this.props.onCheckedClick).script.run({
- this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document,
+ this: this.props.document.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.props.document,
heading: this.props.containingCollection.title,
- checked: this.props.document.treeViewChecked === "check" ? false : this.props.document.treeViewChecked === "x" ? "x" : "none",
- context: this.props.treeViewId
+ checked: this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check",
+ containingTreeView: this.props.treeViewId,
}, console.log);
} else {
this.treeViewOpen = !this.treeViewOpen;
@@ -515,7 +516,7 @@ class TreeView extends React.Component<TreeViewProps> {
const rowWidth = () => panelWidth() - 20;
return docs.map((child, i) => {
- const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+ const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return (null);
}
@@ -545,7 +546,7 @@ class TreeView extends React.Component<TreeViewProps> {
};
const childLayout = Doc.Layout(pair.layout);
const rowHeight = () => {
- const aspect = NumCast(childLayout.nativeWidth, 0) / NumCast(childLayout.nativeHeight, 0);
+ const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0);
return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
};
return !(child instanceof Doc) ? (null) : <TreeView
@@ -555,7 +556,6 @@ class TreeView extends React.Component<TreeViewProps> {
containingCollection={containingCollection}
prevSibling={docs[i]}
treeViewId={treeViewId}
- ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
key={child[Id]}
indentDocument={indent}
outdentDocument={outdent}
@@ -626,11 +626,68 @@ export class CollectionTreeView extends CollectionSubView(Document) {
const layoutItems: ContextMenuProps[] = [];
layoutItems.push({ description: (this.props.Document.preventTreeViewOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" });
+ layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
+ ContextMenu.Instance.addItem({
+ description: "Buxton Layout", icon: "eye", event: () => {
+ DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
+ DocListCast(d.data).map((img, i) => {
+ const caption = (d.captions as any)[i]?.data;
+ if (caption instanceof ObjectField) {
+ Doc.GetProto(img).caption = ObjectField.MakeCopy(caption);
+ }
+ d.captions = undefined;
+ });
+ });
+ const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create;
+ const { Document } = this.props;
+ const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
+ const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] } ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
+
+ const textDoc = TextDocument("", { title: "details", _autoHeight: true });
+ const detailView = Docs.Create.StackingDocument([
+ CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
+ textDoc,
+ TextDocument("", { title: "short_description", _autoHeight: true }),
+ TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true })
+ ], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" });
+ textDoc.data = new RichTextField(detailedTemplate, "year company");
+ detailView.isTemplateDoc = makeTemplate(detailView);
+
+
+ const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
+ heroView.proto!.layout = ImageBox.LayoutString("hero");
+ heroView.showTitle = "title";
+ heroView.showTitleHover = "titlehover";
+
+ Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+ Docs.Create.FontIconDocument({
+ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), title: "hero view", icon: "portrait"
+ }));
+
+ Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+ Docs.Create.FontIconDocument({
+ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "file-alt"
+ }));
+
+
+ Document.childLayout = heroView;
+ Document.childDetailed = detailView;
+ Document._viewType = CollectionViewType.Time;
+ Document._forceActive = true;
+ Document._pivotField = "company";
+ Document.childDropAction = "alias";
+ }
+ });
const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean" }) });
+ onClicks.push({
+ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document,
+ "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean", treeViewContainer: Doc.name })
+ });
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
@@ -651,7 +708,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
return !this.childDocs ? (null) : (
<div className="collectionTreeView-dropTarget" id="body"
- style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
+ style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
onContextMenu={this.onContextMenu}
onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
@@ -665,8 +722,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.dataDoc, "title", value, false);
- const layoutDoc = this.props.Document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layoutCustom)) : undefined;
- const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ const layoutDoc = this.props.Document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layout_custom)) : undefined;
+ const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false);
})} />)}
@@ -682,4 +739,37 @@ export class CollectionTreeView extends CollectionSubView(Document) {
</div >
);
}
-} \ No newline at end of file
+}
+
+Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) {
+ const allCollectionDocs = DocListCast(dataDoc[dataKey]);
+ const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
+ set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+
+ let nonNumbers = 0;
+ facetValues.map(val => {
+ const num = Number(val);
+ if (Number.isNaN(num)) {
+ nonNumbers++;
+ }
+ });
+ const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue =>
+ Docs.Create.TextDocument("", {
+ title: facetValue.toString(),
+ treeViewChecked: ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)",
+ { layoutDoc: Doc.name, facetHeader: "string", facetValue: "string" },
+ { layoutDoc, facetHeader, facetValue })
+ }));
+ return new List<Doc>(facetValueDocSet);
+});
+
+Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
+ const docFilters = Cast(layoutDoc._docFilter, listSpec("string"), []);
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [header, value, state] = docFilters.slice(i, i + 3);
+ if (header === facetHeader && value === facetValue) {
+ return state;
+ }
+ }
+ return undefined;
+}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a665b678b..bdd908807 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -26,15 +26,17 @@ import { Touchable } from '../Touchable';
import { CollectionDockingView } from "./CollectionDockingView";
import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionLinearView } from './CollectionLinearView';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionPivotView } from './CollectionPivotView';
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
import { CollectionStaffView } from './CollectionStaffView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { CollectionTimeView } from './CollectionTimeView';
+import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -47,10 +49,12 @@ export enum CollectionViewType {
Tree,
Stacking,
Masonry,
- Pivot,
+ Multicolumn,
+ Multirow,
+ Time,
+ Carousel,
Linear,
Staff,
- Multicolumn,
Timeline
}
@@ -63,9 +67,11 @@ export namespace CollectionViewType {
["tree", CollectionViewType.Tree],
["stacking", CollectionViewType.Stacking],
["masonry", CollectionViewType.Masonry],
- ["pivot", CollectionViewType.Pivot],
+ ["multicolumn", CollectionViewType.Multicolumn],
+ ["multirow", CollectionViewType.Multirow],
+ ["time", CollectionViewType.Time],
+ ["carousel", CollectionViewType.Carousel],
["linear", CollectionViewType.Linear],
- ["multicolumn", CollectionViewType.Multicolumn]
]);
export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
@@ -91,18 +97,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
@observable private static _safeMode = false;
public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
- @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
- @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-
get collectionViewType(): CollectionViewType | undefined {
- if (!this.extensionDoc) return CollectionViewType.Invalid;
- NumCast(this.props.Document.viewType) && setTimeout(() => {
- if (this.props.Document.viewType) {
- this.extensionDoc!.viewType = NumCast(this.props.Document.viewType);
- }
- Doc.GetProto(this.props.Document).viewType = this.props.Document.viewType = undefined;
- });
- const viewField = NumCast(this.extensionDoc.viewType, Cast(this.props.Document.viewType, "number"));
+ const viewField = NumCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
if (viewField === CollectionViewType.Freeform) {
return CollectionViewType.Tree;
@@ -111,15 +107,15 @@ export class CollectionView extends Touchable<FieldViewProps> {
return CollectionViewType.Freeform;
}
}
- return viewField === undefined ? CollectionViewType.Invalid : viewField;
+ return viewField;
}
componentDidMount = () => {
- this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus),
+ this._reactionDisposer = reaction(() => StrCast(this.props.Document._chromeStatus),
() => {
// chrome status is one of disabled, collapsed, or visible. this determines initial state from document
// chrome status may also be view-mode, in reference to stacking view's toggle mode. it is essentially disabled mode, but prevents the toggle button from showing up on the left sidebar.
- const chromeStatus = this.props.Document.chromeStatus;
+ const chromeStatus = this.props.Document._chromeStatus;
if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
runInAction(() => this._collapsed = true);
}
@@ -129,7 +125,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
componentWillUnmount = () => this._reactionDisposer && this._reactionDisposer();
// bcz: Argh? What's the height of the collection chromes??
- chromeHeight = () => (this.props.Document.chromeStatus === "enabled" ? -60 : 0);
+ chromeHeight = () => (this.props.Document._chromeStatus === "enabled" ? -60 : 0);
active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
@@ -137,10 +133,9 @@ export class CollectionView extends Touchable<FieldViewProps> {
@action.bound
addDocument(doc: Doc): boolean {
- const targetDataDoc = Doc.GetProto(this.props.Document);
+ const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document);
Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
- const extension = Doc.fieldExtensionDoc(targetDataDoc, this.props.fieldKey); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field
- extension && (extension.lastModified = new DateField(new Date(Date.now())));
+ targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
Doc.GetProto(doc).lastOpened = new DateField;
return true;
}
@@ -149,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
removeDocument(doc: Doc): boolean {
const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
docView && SelectionManager.DeselectDoc(docView);
- const value = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+ const value = Cast((this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
@@ -188,24 +183,26 @@ export class CollectionView extends Touchable<FieldViewProps> {
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
+ case CollectionViewType.Multirow: return (<CollectionMultirowView chromeCollapsed={true} key="rpwview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
+ case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
- case CollectionViewType.Pivot: { return (<CollectionPivotView key="collview" {...props} />); }
+ case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); }
case CollectionViewType.Freeform:
- default: { this.props.Document.freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
+ default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
}
@action
private collapse = (value: boolean) => {
this._collapsed = value;
- this.props.Document.chromeStatus = value ? "collapsed" : "enabled";
+ this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
}
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
// currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
- const chrome = this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
+ const chrome = this.props.Document._chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
<CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />;
return [chrome, this.SubViewHelper(type, renderProps)];
}
@@ -215,24 +212,26 @@ export class CollectionView extends Touchable<FieldViewProps> {
if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
const existingVm = ContextMenu.Instance.findByDescription("View Modes...");
const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
- subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" });
+ subItems.push({ description: "Freeform", event: () => { this.props.Document._viewType = CollectionViewType.Freeform; }, icon: "signature" });
if (CollectionView._safeMode) {
- ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
+ ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document._viewType = CollectionViewType.Invalid, icon: "project-diagram" });
}
- subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" });
- subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" });
- subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
+ subItems.push({ description: "Schema", event: () => this.props.Document._viewType = CollectionViewType.Schema, icon: "th-list" });
+ subItems.push({ description: "Treeview", event: () => this.props.Document._viewType = CollectionViewType.Tree, icon: "tree" });
+ subItems.push({ description: "Stacking", event: () => this.props.Document._viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
subItems.push({
description: "Stacking (AutoHeight)", event: () => {
- this.props.Document.viewType = CollectionViewType.Stacking;
- this.props.Document.autoHeight = true;
+ this.props.Document._viewType = CollectionViewType.Stacking;
+ this.props.Document._autoHeight = true;
}, icon: "ellipsis-v"
});
- subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
- subItems.push({ description: "Multicolumn", event: () => this.props.Document.viewType = CollectionViewType.Multicolumn, icon: "columns" });
- subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
- subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
- switch (this.props.Document.viewType) {
+ subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" });
+ subItems.push({ description: "Multicolumn", event: () => this.props.Document._viewType = CollectionViewType.Multicolumn, icon: "columns" });
+ subItems.push({ description: "Multirow", event: () => this.props.Document._viewType = CollectionViewType.Multirow, icon: "columns" });
+ subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
+ subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" });
+ subItems.push({ description: "Pivot/Time", event: () => this.props.Document._viewType = CollectionViewType.Time, icon: "columns" });
+ switch (this.props.Document._viewType) {
case CollectionViewType.Freeform: {
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
break;
@@ -244,6 +243,12 @@ export class CollectionView extends Touchable<FieldViewProps> {
const existing = ContextMenu.Instance.findByDescription("Layout...");
const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+ if (this.props.Document.childLayout instanceof Doc) {
+ layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, undefined, "onRight"), icon: "project-diagram" });
+ }
+ if (this.props.Document.childDetailed instanceof Doc) {
+ layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, undefined, "onRight"), icon: "project-diagram" });
+ }
!existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
const more = ContextMenu.Instance.findByDescription("More...");
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 64411b5fe..517f467b7 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -9,8 +9,7 @@
background: lightgrey;
.collectionViewChrome {
- display: grid;
- grid-template-columns: 1fr auto;
+ display: flex;
padding-bottom: 10px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: hidden;
@@ -20,7 +19,7 @@
.collectionViewBaseChrome-viewPicker {
font-size: 75%;
- text-transform: uppercase;
+ //text-transform: uppercase;
letter-spacing: 2px;
background: rgb(238, 238, 238);
color: grey;
@@ -34,6 +33,26 @@
outline-color: black;
}
+ .collectionViewBaseChrome-cmdPicker {
+ margin-left: 3px;
+ margin-right: 0px;
+ background: rgb(238, 238, 238);
+ border: none;
+ color: grey;
+ }
+ .commandEntry-outerDiv {
+ pointer-events: all;
+ background-color: gray;
+ display: flex;
+ flex-direction: row;
+ .commandEntry-drop {
+ color:white;
+ width:25px;
+ margin-top: auto;
+ margin-bottom: auto;
+ }
+ }
+
.collectionViewBaseChrome-collapse {
transition: all .5s, opacity 0.3s;
position: absolute;
@@ -53,6 +72,18 @@
.collectionViewBaseChrome-viewSpecs {
margin-left: 10px;
display: grid;
+
+ .collectionViewBaseChrome-filterIcon {
+ position: relative;
+ display: flex;
+ margin: auto;
+ background: gray;
+ color: white;
+ width: 40px;
+ height: 40px;
+ align-items: center;
+ justify-content: center;
+ }
.collectionViewBaseChrome-viewSpecsInput {
padding: 12px 10px 11px 10px;
@@ -240,7 +271,6 @@
.commandEntry-outerDiv {
display: flex;
flex-direction: column;
- width: 165px;
height: 40px;
}
.commandEntry-inputArea {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 996c7671e..a21e78188 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -39,31 +39,39 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
_templateCommand = {
- title: "set template", script: "setChildLayout(this.target, this.source && this.source.length ? this.source[0]:undefined)", params: ["target", "source"],
+ title: "=> item view", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"],
initialize: emptyFunction,
immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
};
+ _narrativeCommand = {
+ title: "=> click item view", script: "setChildDetailedLayout(this.target, this.source?.[0])", params: ["target", "source"],
+ initialize: emptyFunction,
+ immediate: (draggedDocs: Doc[]) => Doc.setChildDetailedLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
+ };
_contentCommand = {
- // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting...
- title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
+ title: "=> content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
initialize: emptyFunction,
immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d)))
};
_viewCommand = {
- title: "restore view", script: "this.target.panX = this.restoredPanX; this.target.panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
- immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; },
- initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
+ title: "=> saved view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
+ initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document._panX; button.restoredPanY = this.props.CollectionView.props.Document._panY; button.restoredScale = this.props.CollectionView.props.Document.scale; },
+ immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document._panX = 0; this.props.CollectionView.props.Document._panY = 0; this.props.CollectionView.props.Document.scale = 1; },
};
- _freeform_commands = [this._contentCommand, this._templateCommand, this._viewCommand];
+ _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand];
_stacking_commands = [this._contentCommand, this._templateCommand];
_masonry_commands = [this._contentCommand, this._templateCommand];
+ _schema_commands = [this._templateCommand, this._narrativeCommand];
_tree_commands = [];
private get _buttonizableCommands() {
switch (this.props.type) {
case CollectionViewType.Tree: return this._tree_commands;
+ case CollectionViewType.Schema: return this._schema_commands;
case CollectionViewType.Stacking: return this._stacking_commands;
case CollectionViewType.Masonry: return this._stacking_commands;
case CollectionViewType.Freeform: return this._freeform_commands;
+ case CollectionViewType.Time: return this._freeform_commands;
+ case CollectionViewType.Carousel: return this._freeform_commands;
}
return [];
}
@@ -126,7 +134,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
runInAction(() => {
this.addKeyRestrictions(fields);
// chrome status is one of disabled, collapsed, or visible. this determines initial state from document
- const chromeStatus = this.props.CollectionView.props.Document.chromeStatus;
+ const chromeStatus = this.props.CollectionView.props.Document._chromeStatus;
if (chromeStatus) {
if (chromeStatus === "disabled") {
throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
@@ -143,24 +151,35 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
//@ts-ignore
- this.props.CollectionView.props.Document.viewType = parseInt(e.target.selectedOptions[0].value);
+ this.props.CollectionView.props.Document._viewType = parseInt(e.target.selectedOptions[0].value);
+ }
+
+ commandChanged = (e: React.ChangeEvent) => {
+ //@ts-ignore
+ runInAction(() => this._currentKey = e.target.selectedOptions[0].value);
}
@action
openViewSpecs = (e: React.SyntheticEvent) => {
- this._viewSpecsOpen = true;
+ if (this._viewSpecsOpen) this.closeViewSpecs();
+ else {
+ this._viewSpecsOpen = true;
- //@ts-ignore
- if (!e.target.classList[0].startsWith("qs")) {
- this.closeDatePicker();
- }
+ //@ts-ignore
+ if (!e.target?.classList[0]?.startsWith("qs")) {
+ this.closeDatePicker();
+ }
- e.stopPropagation();
- document.removeEventListener("pointerdown", this.closeViewSpecs);
- document.addEventListener("pointerdown", this.closeViewSpecs);
+ e.stopPropagation();
+ document.removeEventListener("pointerdown", this.closeViewSpecs);
+ document.addEventListener("pointerdown", this.closeViewSpecs);
+ }
}
- @action closeViewSpecs = () => { this._viewSpecsOpen = false; document.removeEventListener("pointerdown", this.closeViewSpecs); };
+ @action closeViewSpecs = () => {
+ this._viewSpecsOpen = false;
+ document.removeEventListener("pointerdown", this.closeViewSpecs);
+ };
@action
openDatePicker = (e: React.PointerEvent) => {
@@ -217,12 +236,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
`(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
"true";
- const docFilter = Cast(this.props.CollectionView.props.Document.docFilter, listSpec("string"), []);
- const docFilterText = Doc.MakeDocFilter(docFilter);
- const finalScript = docFilterText && !fullScript.startsWith("(())") ? `${fullScript} ${docFilterText ? "&&" : ""} (${docFilterText})` :
- docFilterText ? docFilterText : fullScript;
-
- this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(finalScript, { doc: Doc.name });
+ this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
}
@action
@@ -236,9 +250,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@action
toggleCollapse = () => {
- this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled";
+ this.props.CollectionView.props.Document._chromeStatus = this.props.CollectionView.props.Document._chromeStatus === "enabled" ? "collapsed" : "enabled";
if (this.props.collapse) {
- this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled");
+ this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
}
}
@@ -256,32 +270,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
return this.props.CollectionView.props.Document;
}
- private get pivotKey() {
- return StrCast(this.document.pivotField);
- }
-
- private set pivotKey(value: string) {
- this.document.pivotField = value;
- }
-
- @observable private pivotKeyDisplay = this.pivotKey;
- getPivotInput = () => {
- if (StrCast(this.document.freeformLayoutEngine) !== "pivot") {
- return (null);
- }
- return (<input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="PIVOT ON..."
- value={this.pivotKeyDisplay}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => this.pivotKeyDisplay = e.currentTarget.value)}
- onKeyPress={action((e: React.KeyboardEvent<HTMLInputElement>) => {
- const value = e.currentTarget.value;
- if (e.which === 13) {
- this.pivotKey = value;
- this.pivotKeyDisplay = "";
- }
- })} />);
- }
-
@action.bound
clearFilter = () => {
this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name });
@@ -377,7 +365,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
render() {
- const collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
+ const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
return (
<div className="collectionViewChrome-cont" style={{ top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined }}>
<div className="collectionViewChrome">
@@ -396,23 +384,21 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
className="collectionViewBaseChrome-viewPicker"
onPointerDown={stopPropagation}
onChange={this.viewChanged}
- value={NumCast(this.props.CollectionView.props.Document.viewType)}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Pivot View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">Linear View</option>
+ value={NumCast(this.props.CollectionView.props.Document._viewType)}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">MultiCol</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">MultiRow</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Pivot/Time</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Carousel</option>
</select>
- <div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
- <input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="FILTER"
- value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
- onChange={(e) => { }}
- onPointerDown={this.openViewSpecs}
- id="viewSpecsInput" />
- {this.getPivotInput()}
+ <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+ <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} >
+ <FontAwesomeIcon icon="filter" size="2x" />
+ </div>
<div className="collectionViewBaseChrome-viewSpecsMenu"
onPointerDown={this.openViewSpecs}
style={{
@@ -453,17 +439,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</div>
</div>
<div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
- <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <div className="commandEntry-inputArea" onPointerDown={this.autoSuggestDown} >
- <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
- getSuggestionValue={this.getSuggestionValue}
- suggestions={this.suggestions}
- alwaysRenderSuggestions={true}
- renderSuggestion={this.renderSuggestion}
- onSuggestionsFetchRequested={this.onSuggestionFetch}
- onSuggestionsClearRequested={this.onSuggestionClear}
- ref={this._autosuggestRef} />
+ <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <div className="commandEntry-drop">
+ <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
</div>
+ <select
+ className="collectionViewBaseChrome-cmdPicker"
+ onPointerDown={stopPropagation}
+ onChange={this.commandChanged}
+ value={this._currentKey}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""}>{""}</option>
+ {this._buttonizableCommands.map(cmd =>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+ )}
+ </select>
</div>
</div>
</div>
@@ -604,15 +593,6 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
return (
<div className="collectionSchemaViewChrome-cont">
<div className="collectionSchemaViewChrome-toggle">
- <div className="collectionSchemaViewChrome-label">Wrap Text: </div>
- <div className="collectionSchemaViewChrome-toggler" onClick={this.toggleTextwrap}>
- <div className={"collectionSchemaViewChrome-togglerButton" + (textWrapped ? " on" : " off")}>
- {textWrapped ? "on" : "off"}
- </div>
- </div>
- </div>
-
- <div className="collectionSchemaViewChrome-toggle">
<div className="collectionSchemaViewChrome-label">Show Preview: </div>
<div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
<div className={"collectionSchemaViewChrome-togglerButton" + (previewWidth !== 0 ? " on" : " off")}>
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index d293bb5ca..a266861bd 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -35,8 +35,6 @@
pointer-events: all;
position: relative;
display: inline-block;
- padding-left: 5px;
- padding-right: 5px;
}
.parentDocumentSelector-metadata {
pointer-events: auto;
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 24aa6ddfa..115f8d633 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -6,7 +6,7 @@ import { observable, action, runInAction } from "mobx";
import { Id } from "../../../new_fields/FieldSymbols";
import { SearchUtil } from "../../util/SearchUtil";
import { CollectionDockingView } from "./CollectionDockingView";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, StrCast } from "../../../new_fields/Types";
import { CollectionViewType } from "./CollectionView";
import { DocumentButtonBar } from "../DocumentButtonBar";
import { DocumentManager } from "../../util/DocumentManager";
@@ -21,7 +21,13 @@ export const Flyout = higflyout.default;
library.add(faEdit);
-type SelectorProps = { Document: Doc, Views: DocumentView[], Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
+type SelectorProps = {
+ Document: Doc,
+ Views: DocumentView[],
+ Stack?: any,
+ addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void
+};
+
@observer
export class SelectorContextMenu extends React.Component<SelectorProps> {
@observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -49,31 +55,22 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
getOnClick({ col, target }: { col: Doc, target: Doc }) {
return () => {
col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
- if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
- const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
- col.panX = newPanX;
- col.panY = newPanY;
+ if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
+ col._panX = newPanX;
+ col._panY = newPanY;
}
this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
};
}
- get metadataMenu() {
- return <div className="parentDocumentSelector-metadata">
- <Flyout anchorPoint={anchorPoints.TOP_LEFT}
- content={<MetadataEntryMenu docs={() => this.props.Views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
- <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
- </Flyout>
- </div>;
- }
render() {
return <div >
- <div key="metadata">Metadata: {this.metadataMenu}</div>
<p key="contexts">Contexts:</p>
- {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
{this._otherDocs.length ? <hr key="hr" /> : null}
- {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
</div>;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 8c8da63cc..baf09fe5b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,99 +1,179 @@
import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
-import { NumCast, StrCast, Cast, DateCast } from "../../../../new_fields/Types";
+import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
import { ScriptBox } from "../../ScriptBox";
import { CompileScript } from "../../../util/Scripting";
import { ScriptField } from "../../../../new_fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
-import { emptyFunction } from "../../../../Utils";
+import { emptyFunction, aggregateBounds } from "../../../../Utils";
import React = require("react");
-import { ObservableMap, runInAction } from "mobx";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { DateField } from "../../../../new_fields/DateField";
-
-interface PivotData {
- type: string;
- text: string;
- x: number;
- y: number;
- width: number;
- height: number;
- fontSize: number;
-}
+import { Id, ToString } from "../../../../new_fields/FieldSymbols";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { RefField } from "../../../../new_fields/RefField";
export interface ViewDefBounds {
+ type: string;
+ text?: string;
x: number;
y: number;
z?: number;
- width: number;
- height: number;
+ zIndex?: number;
+ width?: number;
+ height?: number;
transition?: string;
+ fontSize?: number;
+ highlight?: boolean;
+ color?: string;
+ payload: any;
+}
+
+export interface PoolData {
+ x?: number,
+ y?: number,
+ z?: number,
+ zIndex?: number,
+ width?: number,
+ height?: number,
+ color?: string,
+ transition?: string,
+ highlight?: boolean,
}
export interface ViewDefResult {
ele: JSX.Element;
bounds?: ViewDefBounds;
}
-
function toLabel(target: FieldResult<Field>) {
- if (target instanceof DateField) {
- const date = DateCast(target).date;
- if (date) {
- return `${date.toDateString()} ${date.toTimeString()}`;
- }
+ if (target instanceof ObjectField || target instanceof RefField) {
+ return target[ToString]();
}
return String(target);
}
+/**
+ * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
+ *
+ * @param {String} text The text to be rendered.
+ * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
+ *
+ * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
+ */
+function getTextWidth(text: string, font: string): number {
+ // re-use canvas object for better performance
+ var canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
+ var context = canvas.getContext("2d");
+ context.font = font;
+ var metrics = context.measureText(text);
+ return metrics.width;
+}
+
+interface pivotColumn {
+ docs: Doc[],
+ filters: string[]
+}
+
-export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
- const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
- const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
+export function computePivotLayout(
+ poolData: Map<string, PoolData>,
+ pivotDoc: Doc,
+ childDocs: Doc[],
+ filterDocs: Doc[],
+ childPairs: { layout: Doc, data?: Doc }[],
+ panelDim: number[],
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+ const fieldKey = "data";
+ const pivotColumnGroups = new Map<FieldResult<Field>, pivotColumn>();
- for (const doc of childDocs) {
- const val = doc[StrCast(pivotDoc.pivotField, "title")];
+ const pivotFieldKey = toLabel(pivotDoc._pivotField);
+ for (const doc of filterDocs) {
+ const val = Field.toString(doc[pivotFieldKey] as Field);
if (val) {
- !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
- pivotColumnGroups.get(val)!.push(doc);
+ !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
+ pivotColumnGroups.get(val)!.docs.push(doc);
+ }
+ }
+ let nonNumbers = 0;
+ childDocs.map(doc => {
+ const num = toNumber(doc[pivotFieldKey]);
+ if (num === undefined || Number.isNaN(num)) {
+ nonNumbers++;
+ }
+ });
+ const pivotNumbers = nonNumbers / childDocs.length < .1;
+ if (pivotColumnGroups.size > 10) {
+ const arrayofKeys = Array.from(pivotColumnGroups.keys());
+ const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort();
+ const clusterSize = Math.ceil(pivotColumnGroups.size / 10);
+ const numClusters = Math.ceil(sortedKeys.length / clusterSize);
+ for (let i = 0; i < numClusters; i++) {
+ for (let j = i * clusterSize + 1; j < Math.min(sortedKeys.length, (i + 1) * clusterSize); j++) {
+ const curgrp = pivotColumnGroups.get(sortedKeys[i * clusterSize])!;
+ const newgrp = pivotColumnGroups.get(sortedKeys[j])!;
+ curgrp.docs.push(...newgrp.docs);
+ curgrp.filters.push(...newgrp.filters);
+ pivotColumnGroups.delete(sortedKeys[j]);
+ }
+ }
+ }
+ const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
+ const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
+ const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
+ const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
+ let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
+
+ const colWidth = panelDim[0] / pivotColumnGroups.size;
+ const colHeight = panelDim[1] - max_text;
+ let numCols = 0;
+ let bestArea = 0;
+ let pivotAxisWidth = 0;
+ for (let i = 1; i < 10; i++) {
+ const numInCol = Math.ceil(maxInColumn / i);
+ const hd = colHeight / numInCol;
+ const wd = colWidth / i;
+ const dim = Math.min(hd, wd);
+ if (dim > bestArea) {
+ bestArea = dim;
+ numCols = i;
+ pivotAxisWidth = dim;
}
}
- const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
- let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
const docMap = new Map<Doc, ViewDefBounds>();
- const groupNames: PivotData[] = [];
- if (panelDim[0] < 2500) numCols = Math.min(5, numCols);
- if (panelDim[0] < 2000) numCols = Math.min(4, numCols);
- if (panelDim[0] < 1400) numCols = Math.min(3, numCols);
- if (panelDim[0] < 1000) numCols = Math.min(2, numCols);
- if (panelDim[0] < 600) numCols = 1;
+ const groupNames: ViewDefBounds[] = [];
const expander = 1.05;
const gap = .15;
let x = 0;
- pivotColumnGroups.forEach((val, key) => {
+ const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort();
+ sortedPivotKeys.forEach(key => {
+ const val = pivotColumnGroups.get(key)!;
let y = 0;
let xCount = 0;
+ const text = toLabel(key);
groupNames.push({
type: "text",
- text: toLabel(key),
+ text,
x,
- y: pivotAxisWidth + 50,
+ y: pivotAxisWidth,
width: pivotAxisWidth * expander * numCols,
- height: NumCast(pivotDoc.pivotFontSize, 10),
- fontSize: NumCast(pivotDoc.pivotFontSize, 10)
+ height: max_text,
+ fontSize,
+ payload: val
});
- for (const doc of val) {
+ for (const doc of val.docs) {
const layoutDoc = Doc.Layout(doc);
let wid = pivotAxisWidth;
- let hgt = layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
+ let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
if (hgt > pivotAxisWidth) {
hgt = pivotAxisWidth;
- wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
+ wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
docMap.set(doc, {
- x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0),
- y: -y,
+ type: "doc",
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
+ y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
- height: hgt
+ height: hgt,
+ payload: undefined
});
xCount++;
if (xCount >= numCols) {
@@ -104,21 +184,171 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
x += pivotAxisWidth * (numCols * expander + gap);
});
- childPairs.map(pair => {
- const defaultPosition = {
- x: NumCast(pair.layout.x),
- y: NumCast(pair.layout.y),
- z: NumCast(pair.layout.z),
- width: NumCast(pair.layout.width),
- height: NumCast(pair.layout.height)
- };
- const pos = docMap.get(pair.layout) || defaultPosition;
- const data = poolData.get(pair.layout[Id]);
- if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height) {
- runInAction(() => poolData.set(pair.layout[Id], { transition: "transform 1s", ...pos }));
+ const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
+ const dividers = sortedPivotKeys.map((key, i) =>
+ ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+ groupNames.push(...dividers);
+ return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+}
+
+function toNumber(val: FieldResult<Field>) {
+ return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+}
+
+export function computeTimelineLayout(
+ poolData: Map<string, PoolData>,
+ pivotDoc: Doc,
+ childDocs: Doc[],
+ filterDocs: Doc[],
+ childPairs: { layout: Doc, data?: Doc }[],
+ panelDim: number[],
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+ const fieldKey = "data";
+ const pivotDateGroups = new Map<number, Doc[]>();
+ const docMap = new Map<Doc, ViewDefBounds>();
+ const groupNames: ViewDefBounds[] = [];
+ const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
+ const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
+ const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null);
+ const minTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTimeSpan && (curTime - curTimeSpan);
+ const maxTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTimeSpan && (curTime + curTimeSpan);
+ const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
+ const fontHeight = panelDim[1] > 58 ? 30 : panelDim[1] / 2;
+ const findStack = (time: number, stack: number[]) => {
+ const index = stack.findIndex(val => val === undefined || val < x);
+ return index === -1 ? stack.length : index;
+ }
+
+ let minTime = Number.MAX_VALUE;
+ let maxTime = -Number.MAX_VALUE;
+ filterDocs.map(doc => {
+ const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
+ if (!(Number.isNaN(num) || (minTimeReq && num < minTimeReq) || (maxTimeReq && num > maxTimeReq))) {
+ !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
+ pivotDateGroups.get(num)!.push(doc);
+ minTime = Math.min(num, minTime);
+ maxTime = Math.max(num, maxTime);
+ }
+ });
+ if (curTime !== undefined) {
+ if (curTime > maxTime || curTime - minTime > maxTime - curTime) {
+ maxTime = curTime + (curTime - minTime);
+ } else {
+ minTime = curTime - (maxTime - curTime);
+ }
+ }
+ setTimeout(() => {
+ pivotDoc[fieldKey + "-timelineMin"] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime;
+ pivotDoc[fieldKey + "-timelineMax"] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime;
+ }, 0);
+
+ if (maxTime === minTime) {
+ maxTime = minTime + 1;
+ }
+
+ const arrayofKeys = Array.from(pivotDateGroups.keys());
+ const sortedKeys = arrayofKeys.sort((n1, n2) => n1 - n2);
+ const scaling = panelDim[0] / (maxTime - minTime);
+ let x = 0;
+ let prevKey = Math.floor(minTime);
+
+ if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) {
+ groupNames.push({ type: "text", text: prevKey.toString(), x: x, y: 0, height: fontHeight, fontSize, payload: undefined });
+ }
+ if (!sortedKeys.length && curTime !== undefined) {
+ groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined });
+ }
+
+ const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5);
+ let stacking: number[] = [];
+ let zind = 0;
+ sortedKeys.forEach(key => {
+ if (curTime !== undefined && curTime > prevKey && curTime <= key) {
+ groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key });
+ }
+ const keyDocs = pivotDateGroups.get(key)!;
+ x += scaling * (key - prevKey);
+ const stack = findStack(x, stacking);
+ prevKey = key;
+ if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
+ groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
+ }
+ layoutDocsAtTime(keyDocs, key);
+ });
+ if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
+ x = (curTime - minTime) * scaling;
+ groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: undefined });
+ }
+ if (Math.ceil(maxTime - minTime) * scaling > x + 25) {
+ groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
+ }
+
+ const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1, payload: undefined };
+ return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
+
+ function layoutDocsAtTime(keyDocs: Doc[], key: number) {
+ keyDocs.forEach(doc => {
+ const stack = findStack(x, stacking);
+ const layoutDoc = Doc.Layout(doc);
+ let wid = pivotAxisWidth;
+ let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
+ if (hgt > pivotAxisWidth) {
+ hgt = pivotAxisWidth;
+ wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
+ }
+ docMap.set(doc, {
+ type: "doc",
+ x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
+ zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt, payload: undefined
+ });
+ stacking[stack] = x + pivotAxisWidth;
+ });
+ }
+}
+
+function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
+ poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
+ extraDocs: Doc[]) {
+
+ const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
+ const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
+ const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
+ aggBounds.r = 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;
+
+ childPairs.filter(d => docMap.get(d.layout)).map(pair => {
+ const newPosRaw = docMap.get(pair.layout);
+ if (newPosRaw) {
+ const newPos = {
+ x: newPosRaw.x * scale,
+ y: newPosRaw.y * scale,
+ z: newPosRaw.z,
+ highlight: newPosRaw.highlight,
+ zIndex: newPosRaw.zIndex,
+ width: (newPosRaw.width || 0) * scale,
+ height: newPosRaw.height! * scale
+ };
+ poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos });
}
});
- return { elements: viewDefsToJSX(groupNames) };
+ extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
+
+ return {
+ elements: viewDefsToJSX(extras.concat(groupNames.map(gname => ({
+ type: gname.type,
+ text: gname.text,
+ x: gname.x * scale,
+ y: gname.y * scale,
+ color: gname.color,
+ width: gname.width === undefined ? undefined : gname.width * scale,
+ height: Math.max(fontHeight, (gname.height || 0) * scale),
+ fontSize: gname.fontSize,
+ payload: gname.payload
+ }))))
+ };
}
export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index b8fbaef5c..f04b79ea4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -68,8 +68,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
(this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
const m = targetAhyperlink.getBoundingClientRect();
const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
- this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
+ this.props.B.props.Document[bfield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
+ this.props.B.props.Document[bfield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
}, 0);
}
})
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 58fb81453..0b5e44ccb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -22,6 +22,8 @@
.collectionFreeform-customText {
position: absolute;
text-align: center;
+ overflow-y: auto;
+ overflow-x: hidden;
}
.collectionfreeformview-container {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7985e541f..2518a4a55 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,13 +1,13 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
+import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
-import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
@@ -26,14 +26,13 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
import { ContextMenu } from "../../ContextMenu";
import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingControl } from "../../InkingControl";
-import { CreatePolyline } from "../../InkingStroke";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentViewProps } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
import PDFMenu from "../../pdf/PDFMenu";
import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData, ViewDefBounds } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
@@ -42,19 +41,16 @@ import React = require("react");
import { computedFn } from "mobx-utils";
import { TraceMobx } from "../../../../new_fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { LinkManager } from "../../../util/LinkManager";
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
export const panZoomSchema = createSchema({
- panX: "number",
- panY: "number",
+ _panX: "number",
+ _panY: "number",
scale: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
- isRuleProvider: "boolean",
fitToBox: "boolean",
xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
@@ -76,22 +72,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _clusterDistance: number = 75;
private _hitCluster = false;
private _layoutComputeReaction: IReactionDisposer | undefined;
- private _layoutPoolData = new ObservableMap<string, any>();
+ private _layoutPoolData = observable.map<string, any>();
- public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@observable _clusterSets: (Doc[])[] = [];
- @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+ @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
@computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
- @computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; }
- @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+ @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth); }
+ @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private easing = () => this.props.Document.panTransformType === "Ease";
- private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
- private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+ private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
+ private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
@@ -103,14 +99,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
- const maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
- let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
- if (heading === 0) {
- const sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
- DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
- heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
- }
- !this.Document.isRuleProvider && (newBox.heading = heading);
this.addDocument(newBox);
}
private addDocument = (newBox: Doc) => {
@@ -138,6 +126,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
+ if (this.props.Document.isBackground) return false;
const xf = this.getTransform();
const xfo = this.getTransformOverlay();
const [xp, yp] = xf.transformPoint(de.x, de.y);
@@ -155,18 +144,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const layoutDoc = Doc.Layout(d);
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
- if (!NumCast(layoutDoc.width)) {
- layoutDoc.width = 300;
+ if (!NumCast(layoutDoc._width)) {
+ layoutDoc._width = 300;
}
- if (!NumCast(layoutDoc.height)) {
- const nw = NumCast(layoutDoc.nativeWidth);
- const nh = NumCast(layoutDoc.nativeHeight);
- layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300;
+ if (!NumCast(layoutDoc._height)) {
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
+ layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
}
this.bringToFront(d);
}));
- de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]);
+ (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
}
}
else if (de.complete.annoDragData) {
@@ -191,8 +180,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const layoutDoc = Doc.Layout(cd);
const cx = NumCast(cd.x) - this._clusterDistance;
const cy = NumCast(cd.y) - this._clusterDistance;
- const cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance;
- const ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance;
+ const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
NumCast(cd.cluster) : cluster;
}, -1);
@@ -224,6 +213,41 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
+ @action
+ updateClusterDocs(docs: Doc[]) {
+ const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
+ if (this.props.Document.useClusters) {
+ const docFirst = docs[0];
+ docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
+ const preferredInd = NumCast(docFirst.cluster);
+ docs.map(doc => doc.cluster = -1);
+ docs.map(doc => this._clusterSets.map((set, i) => set.map(member => {
+ if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ docFirst.cluster = i;
+ }
+ })));
+ if (docFirst.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ docFirst.cluster = preferredInd;
+ }
+ this._clusterSets.map((set, i) => {
+ if (docFirst.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ docFirst.cluster = i;
+ }
+ });
+ if (docFirst.cluster === -1) {
+ docs.map(doc => {
+ doc.cluster = this._clusterSets.length;
+ this._clusterSets.push([doc]);
+ });
+ } else {
+ for (let i = this._clusterSets.length; i <= NumCast(docFirst.cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
+ }
+ childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
+ childLayouts.map(child => Doc.GetProto(child).clusterStr = child.cluster?.toString());
+ }
+ }
+
@undoBatch
@action
updateCluster(doc: Doc) {
@@ -274,26 +298,27 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return clusterColor;
}
- @observable private _points: { X: number, Y: number }[] = [];
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.nativeEvent.cancelBubble) return;
+ if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ return;
+ }
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
- if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+ if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
// if physically using a pen or we're in pen or highlighter mode
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
- e.stopPropagation();
- e.preventDefault();
- const point = this.getTransform().transformPoint(e.pageX, e.pageY);
- this._points.push({ X: point[0], Y: point[1] });
- }
+ // if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // const point = this.getTransform().transformPoint(e.pageX, e.pageY);
+ // this._points.push({ X: point[0], Y: point[1] });
+ // }
// if not using a pen and in no ink mode
- else if (InkingControl.Instance.selectedTool === InkTool.None) {
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
this._lastX = e.pageX;
this._lastY = e.pageY;
}
@@ -325,106 +350,80 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@action
- handle1PointerDown = (e: React.TouchEvent) => {
- const pt = e.targetTouches.item(0);
- if (pt) {
- this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
- if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
- if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
- e.stopPropagation();
- e.preventDefault();
- const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
- this._points.push({ X: point[0], Y: point[1] });
- }
- else if (InkingControl.Instance.selectedTool === InkTool.None) {
- this._lastX = pt.pageX;
- this._lastY = pt.pageY;
- e.stopPropagation();
- e.preventDefault();
- }
- else {
- e.stopPropagation();
- e.preventDefault();
+ handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ if (!e.nativeEvent.cancelBubble) {
+ // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt = me.changedTouches[0];
+ if (pt) {
+ this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
+ // this._points.push({ X: point[0], Y: point[1] });
+ // }
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ this._lastX = pt.pageX;
+ this._lastY = pt.pageY;
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ else {
+ e.preventDefault();
+ }
}
}
}
}
- @action
- onPointerUp = (e: PointerEvent): void => {
- if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && this._points.length <= 1) return;
-
- if (this._points.length > 1) {
- const B = this.svgBounds;
- const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
-
- const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
- let actionPerformed = false;
- if (result && result.Score > 0.7) {
- switch (result.Name) {
- case GestureUtils.Gestures.Box:
- const bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.Y)), b: Math.max(...this._points.map(p => p.Y)) };
- const sel = this.getActiveDocuments().filter(doc => {
- const l = NumCast(doc.x);
- const r = l + doc[WidthSym]();
- const t = NumCast(doc.y);
- const b = t + doc[HeightSym]();
- const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
- if (pass) {
- doc.x = l - B.left - B.width / 2;
- doc.y = t - B.top - B.height / 2;
- }
- return pass;
- });
- this.addDocument(Docs.Create.FreeformDocument(sel, { x: B.left, y: B.top, width: B.width, height: B.height, panX: 0, panY: 0 }));
- sel.forEach(d => this.props.removeDocument(d));
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.Line:
- const ep1 = this._points[0];
- const ep2 = this._points[this._points.length - 1];
- let d1: Doc | undefined;
- let d2: Doc | undefined;
- this.getActiveDocuments().map(doc => {
- const l = NumCast(doc.x);
- const r = l + doc[WidthSym]();
- const t = NumCast(doc.y);
- const b = t + doc[HeightSym]();
- if (!d1 && l < ep1.X && r > ep1.X && t < ep1.Y && b > ep1.Y) {
- d1 = doc;
- }
- else if (!d2 && l < ep2.X && r > ep2.X && t < ep2.Y && b > ep2.Y) {
- d2 = doc;
- }
- });
- if (d1 && d2) {
- if (!LinkManager.Instance.doesLinkExist(d1, d2)) {
- DocUtils.MakeLink({ doc: d1 }, { doc: d2 });
- actionPerformed = true;
- }
- }
- break;
- }
- if (actionPerformed) {
- this._points = [];
- }
- }
-
- if (!actionPerformed) {
- const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+ @undoBatch
+ onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+ switch (ge.gesture) {
+ case GestureUtils.Gestures.Stroke:
+ const points = ge.points;
+ const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+ const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
this.addDocument(inkDoc);
- this._points = [];
- }
+ e.stopPropagation();
+ break;
+ case GestureUtils.Gestures.Box:
+ const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+ const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+ const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] };
+ const bWidth = bounds.r - bounds.x;
+ const bHeight = bounds.b - bounds.y;
+ const sel = this.getActiveDocuments().filter(doc => {
+ const l = NumCast(doc.x);
+ const r = l + doc[WidthSym]();
+ const t = NumCast(doc.y);
+ const b = t + doc[HeightSym]();
+ const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
+ if (pass) {
+ doc.x = l - bounds.x - bWidth / 2;
+ doc.y = t - bounds.y - bHeight / 2;
+ }
+ return pass;
+ });
+ this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
+ sel.forEach(d => this.props.removeDocument(d));
+ break;
+
}
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
+ this.removeMoveListeners();
+ this.removeEndListeners();
}
@action
@@ -432,22 +431,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// I think it makes sense for the marquee menu to go away when panned. -syip2
MarqueeOptionsMenu.Instance.fadeOut(true);
- let x = this.Document.panX || 0;
- let y = this.Document.panY || 0;
- const docs = this.childLayoutPairs.map(pair => pair.layout);
+ let x = this.Document._panX || 0;
+ let y = this.Document._panY || 0;
+ const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- if (!this.isAnnotationOverlay) {
+ if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
PDFMenu.Instance.fadeOut(true);
- const minx = docs.length ? NumCast(docs[0].x) : 0;
- const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
- const miny = docs.length ? NumCast(docs[0].y) : 0;
- const maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
- const ranges = docs.filter(doc => doc).reduce((range, doc) => {
- const layoutDoc = Doc.Layout(doc);
- const x = NumCast(doc.x);
- const xe = x + NumCast(layoutDoc.width);
- const y = NumCast(doc.y);
- const ye = y + NumCast(layoutDoc.height);
+ const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
+ const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
+ const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
+ const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
+ const ranges = docs.filter(doc => doc).filter(doc => this.childDataProvider(doc)).reduce((range, doc) => {
+ const x = this.childDataProvider(doc).x;//NumCast(doc.x);
+ const y = this.childDataProvider(doc).y;//NumCast(doc.y);
+ const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
+ const ye = this.childDataProvider(doc).height + y; //y + NumCast(layoutDoc.height);
return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
[range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
}, [[minx, maxx], [miny, maxy]]);
@@ -473,13 +471,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
return;
}
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ return;
+ }
if (!e.cancelBubble) {
const selectedTool = InkingControl.Instance.selectedTool;
- if (selectedTool === InkTool.Highlighter || selectedTool === InkTool.Pen || InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- const point = this.getTransform().transformPoint(e.clientX, e.clientY);
- this._points.push({ X: point[0], Y: point[1] });
- }
- else if (selectedTool === InkTool.None) {
+ if (selectedTool === InkTool.None) {
if (this._hitCluster && this.tryDragCluster(e)) {
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
@@ -494,10 +491,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- handle1PointerMove = (e: TouchEvent) => {
+ handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
// panning a workspace
if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt = myTouches[0];
if (pt) {
if (InkingControl.Instance.selectedTool === InkTool.None) {
@@ -510,20 +507,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
this.pan(pt);
}
- else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
- const point = this.getTransform().transformPoint(pt.clientX, pt.clientY);
- this._points.push({ X: point[0], Y: point[1] });
- }
}
- e.stopPropagation();
+ // e.stopPropagation();
e.preventDefault();
}
}
- handle2PointersMove = (e: TouchEvent) => {
+ handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
// pinch zooming
if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt1 = myTouches[0];
const pt2 = myTouches[1];
@@ -560,35 +553,39 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
}
- e.stopPropagation();
+ // e.stopPropagation();
e.preventDefault();
}
}
@action
- handle2PointersDown = (e: React.TouchEvent) => {
+ handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
if (!e.nativeEvent.cancelBubble && this.props.active(true)) {
- const pt1: React.Touch | null = e.targetTouches.item(0);
- const pt2: React.Touch | null = e.targetTouches.item(1);
- if (!pt1 || !pt2) return;
-
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- this._lastX = centerX;
- this._lastY = centerY;
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
- e.stopPropagation();
+ // const pt1: React.Touch | null = e.targetTouches.item(0);
+ // const pt2: React.Touch | null = e.targetTouches.item(1);
+ // // if (!pt1 || !pt2) return;
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt1 = myTouches[0];
+ const pt2 = myTouches[1];
+ if (pt1 && pt2) {
+ const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+ this._lastX = centerX;
+ this._lastY = centerY;
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ e.stopPropagation();
+ }
}
}
cleanUpInteractions = () => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
+ this.removeMoveListeners();
+ this.removeEndListeners();
}
@action
@@ -627,8 +624,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
- this.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
- this.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+ this.Document._panX = this.isAnnotationOverlay ? newPanX : panX;
+ this.Document._panY = this.isAnnotationOverlay ? newPanY : panY;
}
}
@@ -652,14 +649,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// TODO This technically isn't correct if type !== "doc", as
// currently nothing is done, but we should probably push a new state
- if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) {
+ if (state.type === "doc" && this.Document._panX !== undefined && this.Document._panY !== undefined) {
const init = state.initializers![this.Document[Id]];
if (!init) {
- state.initializers![this.Document[Id]] = { panX: this.Document.panX, panY: this.Document.panY };
+ state.initializers![this.Document[Id]] = { panX: this.Document._panX, panY: this.Document._panY };
HistoryUtil.pushState(state);
- } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) {
- init.panX = this.Document.panX;
- init.panY = this.Document.panY;
+ } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) {
+ init.panX = this.Document._panX;
+ init.panY = this.Document._panY;
HistoryUtil.pushState(state);
}
}
@@ -675,13 +672,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
} else {
const layoutdoc = Doc.Layout(doc);
- const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2;
- const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2;
+ const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(layoutdoc._height) / 2;
const newState = HistoryUtil.getState();
newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
HistoryUtil.pushState(newState);
- const savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
+ const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
Doc.BrushDoc(this.props.Document);
@@ -691,8 +688,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
afterFocus && setTimeout(() => {
if (afterFocus && afterFocus()) {
- this.Document.panX = savedState.px;
- this.Document.panY = savedState.py;
+ this.Document._panX = savedState.px;
+ this.Document._panY = savedState.py;
this.Document.scale = savedState.s;
this.Document.panTransformType = savedState.pt;
}
@@ -702,7 +699,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
- this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
+ this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
zoomToScale = (scale: number) => {
@@ -721,7 +718,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
Document: childLayout,
LibraryPath: this.libraryPath,
layoutKey: undefined,
- ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
//onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
onClick: this.onChildClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
@@ -740,75 +736,143 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
};
}
- getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } {
+ getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
const result = this.Document.arrangeScript?.script.run(params, console.log);
if (result?.success) {
return { ...result, transition: "transform 1s" };
}
const layoutDoc = Doc.Layout(params.doc);
- return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") };
+ return {
+ x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), color: Cast(params.doc.color, "string"),
+ zIndex: Cast(params.doc.zIndex, "number"), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+ };
}
- viewDefsToJSX = (views: any[]) => {
+ viewDefsToJSX = (views: ViewDefBounds[]) => {
return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
}
- private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
+ onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
+ (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+ }
+ private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
+ const x = Cast(viewDef.x, "number");
+ const y = Cast(viewDef.y, "number");
+ const z = Cast(viewDef.z, "number");
+ const highlight = Cast(viewDef.highlight, "boolean");
+ const zIndex = Cast(viewDef.zIndex, "number");
+ const color = Cast(viewDef.color, "string");
+ const width = Cast(viewDef.width, "number", null);
+ const height = Cast(viewDef.height, "number", null);
if (viewDef.type === "text") {
const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
- const x = Cast(viewDef.x, "number");
- const y = Cast(viewDef.y, "number");
- const z = Cast(viewDef.z, "number");
- const width = Cast(viewDef.width, "number");
- const height = Cast(viewDef.height, "number");
const fontSize = Cast(viewDef.fontSize, "number");
- return [text, x, y, width, height].some(val => val === undefined) ? undefined :
+ return [text, x, y].some(val => val === undefined) ? undefined :
{
- ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+ ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color}
+ style={{ width, height, color, fontSize, transform: `translate(${x}px, ${y}px)` }}>
{text}
</div>,
- bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
+ bounds: viewDef
+ };
+ } else if (viewDef.type === "div") {
+ const backgroundColor = Cast(viewDef.color, "string");
+ return [x, y].some(val => val === undefined) ? undefined :
+ {
+ ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
+ style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
+ bounds: viewDef
};
}
}
- childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) { return this._layoutPoolData.get(doc[Id]); }.bind(this));
+ childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
+ if (!doc) {
+ console.log(doc);
+ }
+ return this._layoutPoolData.get(doc[Id]);
+ }.bind(this));
+
+ doTimelineLayout(poolData: Map<string, any>) {
+ return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
+ this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+ }
- doPivotLayout(poolData: ObservableMap<string, any>) {
- return computePivotLayout(poolData, this.props.Document, this.childDocs,
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+ doPivotLayout(poolData: Map<string, any>) {
+ return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
+ this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
}
- doFreeformLayout(poolData: ObservableMap<string, any>) {
+ _cachedPool: Map<string, any> = new Map();
+ doFreeformLayout(poolData: Map<string, any>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
- const data = poolData.get(pair.layout[Id]);
const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
- state = pos.state === undefined ? state : pos.state;
- if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height || pos.transition !== data.transition) {
- runInAction(() => poolData.set(pair.layout[Id], pos));
- }
+ poolData.set(pair.layout[Id], pos);
});
return { elements: elements };
}
- get doLayoutComputation() {
- let computedElementData: { elements: ViewDefResult[] };
- switch (this.Document.freeformLayoutEngine) {
- case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
- default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
+ @computed get doInternalLayoutComputation() {
+ const newPool = new Map<string, any>();
+ switch (this.props.layoutEngine?.()) {
+ case "timeline": return { newPool, computedElementData: this.doTimelineLayout(newPool) };
+ case "pivot": return { newPool, computedElementData: this.doPivotLayout(newPool) };
+ }
+ return { newPool, computedElementData: this.doFreeformLayout(newPool) };
+ }
+
+ @computed get filterDocs() {
+ const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
+ const clusters: { [key: string]: { [value: string]: string } } = {};
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [key, value, modifiers] = docFilters.slice(i, i + 3);
+ const cluster = clusters[key];
+ if (!cluster) {
+ const child: { [value: string]: string } = {};
+ child[value] = modifiers;
+ clusters[key] = child;
+ } else {
+ cluster[value] = modifiers;
+ }
}
+ const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
+ for (const key of Object.keys(clusters)) {
+ const cluster = clusters[key];
+ const satisfiesFacet = Object.keys(cluster).some(inner => {
+ const modifier = cluster[inner];
+ return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
+ });
+ if (!satisfiesFacet) {
+ return false;
+ }
+ }
+ return true;
+ }) : this.childDocs;
+ return filteredDocs;
+ }
+ get doLayoutComputation() {
+ const { newPool, computedElementData } = this.doInternalLayoutComputation;
+ runInAction(() =>
+ Array.from(newPool.keys()).map(key => {
+ const lastPos = this._cachedPool.get(key); // last computed pos
+ const newPos = newPool.get(key);
+ if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
+ this._layoutPoolData.set(key, newPos);
+ }
+ }));
+ this._cachedPool.clear();
+ Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
dataProvider={this.childDataProvider}
- ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
jitterRotation={NumCast(this.props.Document.jitterRotation)}
- fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />,
+ fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />,
bounds: this.childDataProvider(pair.layout)
}));
@@ -817,12 +881,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
componentDidMount() {
super.componentDidMount();
- this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; },
- action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)),
+ this._layoutComputeReaction = reaction(
+ () => (this.doLayoutComputation),
+ (computation) => this._layoutElements = computation?.elements.slice() || [],
{ fireImmediately: true, name: "doLayout" });
}
componentWillUnmount() {
- this._layoutComputeReaction && this._layoutComputeReaction();
+ this._layoutComputeReaction?.();
}
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
elementFunc = () => this._layoutElements;
@@ -834,70 +899,78 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
layoutDocsInGrid = () => {
UndoManager.RunInBatch(() => {
- const docs = DocListCast(this.Document[this.props.fieldKey]);
- const startX = this.Document.panX || 0;
+ const docs = this.childLayoutPairs;
+ const startX = this.Document._panX || 0;
let x = startX;
- let y = this.Document.panY || 0;
+ let y = this.Document._panY || 0;
let i = 0;
- const width = Math.max(...docs.map(doc => NumCast(doc.width)));
- const height = Math.max(...docs.map(doc => NumCast(doc.height)));
- for (const doc of docs) {
- doc.x = x;
- doc.y = y;
+ const width = Math.max(...docs.map(doc => NumCast(doc.layout._width)));
+ const height = Math.max(...docs.map(doc => NumCast(doc.layout._height)));
+ docs.forEach(pair => {
+ pair.layout.x = x;
+ pair.layout.y = y;
x += width + 20;
if (++i === 6) {
i = 0;
x = startX;
y += height + 20;
}
- }
+ });
}, "arrange contents");
}
- autoFormat = () => {
- this.Document.isRuleProvider = !this.Document.isRuleProvider;
- // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading.
- this.Document.isRuleProvider && this.childLayoutPairs.map(pair =>
- // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template)
- DocListCast(Doc.Layout(pair.layout).data).map(heading => {
- const headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading);
- const headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout;
- if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) {
- Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor;
- }
- })
- );
- }
-
- analyzeStrokes = async () => {
- const children = await DocListCastAsync(this.dataDoc.data);
- if (!children) {
- return;
- }
- const inkData: InkData[] = [];
- for (const doc of children) {
- const data = Cast(doc.data, InkField)?.inkData;
- data && inkData.push(data);
- }
- if (!inkData.length) {
- return;
- }
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData);
- }
+ private thumbIdentifier?: number;
+
+ // @action
+ // handleHandDown = (e: React.TouchEvent) => {
+ // const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+ // const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ // this.thumbIdentifier = thumb?.identifier;
+ // const others = fingers.filter(f => f !== thumb);
+ // const minX = Math.min(...others.map(f => f.clientX));
+ // const minY = Math.min(...others.map(f => f.clientY));
+ // const t = this.getTransform().transformPoint(minX, minY);
+ // const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
+
+ // const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+ // if (thumbDoc) {
+ // this._palette = <Palette x={t[0]} y={t[1]} thumb={th} thumbDoc={thumbDoc} />;
+ // }
+
+ // document.removeEventListener("touchmove", this.onTouch);
+ // document.removeEventListener("touchmove", this.handleHandMove);
+ // document.addEventListener("touchmove", this.handleHandMove);
+ // document.removeEventListener("touchend", this.handleHandUp);
+ // document.addEventListener("touchend", this.handleHandUp);
+ // }
+
+ // @action
+ // handleHandMove = (e: TouchEvent) => {
+ // for (let i = 0; i < e.changedTouches.length; i++) {
+ // const pt = e.changedTouches.item(i);
+ // if (pt?.identifier === this.thumbIdentifier) {
+ // }
+ // }
+ // }
+
+ // @action
+ // handleHandUp = (e: TouchEvent) => {
+ // this.onTouchEnd(e);
+ // if (this.prevPoints.size < 3) {
+ // this._palette = undefined;
+ // document.removeEventListener("touchend", this.handleHandUp);
+ // }
+ // }
onContextMenu = (e: React.MouseEvent) => {
const layoutItems: ContextMenuProps[] = [];
- if (this.childDocs.some(d => BoolCast(d.isTemplateDoc))) {
- layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
- }
- layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
- layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" });
- layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ layoutItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
- layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
layoutItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
@@ -931,7 +1004,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
description: "Add Note ...",
subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
description: (i + 1) + ": " + StrCast(note.title),
- event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })),
+ event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument("", { _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], _autoHeight: true, layout: note, title: StrCast(note.title) })),
icon: "eye"
})) as ContextMenuProps[],
icon: "eye"
@@ -948,44 +1021,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
];
}
- @computed get svgBounds() {
- const xs = this._points.map(p => p.X);
- const ys = this._points.map(p => p.Y);
- const right = Math.max(...xs);
- const left = Math.min(...xs);
- const bottom = Math.max(...ys);
- const top = Math.min(...ys);
- return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
- }
-
- @computed get currentStroke() {
- if (this._points.length <= 1) {
- return (null);
- }
-
- const B = this.svgBounds;
-
- return (
- <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, position: "absolute", zIndex: 30000 }}>
- {CreatePolyline(this._points, B.left, B.top)}
- </svg>
- );
- }
+ // @observable private _palette?: JSX.Element;
children = () => {
const eles: JSX.Element[] = [];
- this.extensionDoc && (eles.push(...this.childViews()));
- this.currentStroke && (eles.push(this.currentStroke));
+ eles.push(...this.childViews());
+ // this._palette && (eles.push(this._palette));
+ // this.currentStroke && (eles.push(this.currentStroke));
eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
return eles;
}
@computed get placeholder() {
return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title}</span>
+ <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
</div>;
}
@computed get marqueeView() {
- return <MarqueeView {...this.props} extensionDoc={this.extensionDoc!} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+ return <MarqueeView {...this.props} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
@@ -994,6 +1046,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
</MarqueeView>;
}
@computed get contentScaling() {
+ if (this.props.annotationsKey) return 0;
const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
return wscale < hscale ? wscale : hscale;
@@ -1007,12 +1060,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
// if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
- if (!this.extensionDoc) return (null);
// let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
return <div className={"collectionfreeformview-container"}
- ref={this.createDropTarget}
+ ref={this.createDashEventsTarget}
onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
style={{
pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
@@ -1020,7 +1072,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
}}>
- {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
+ {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />
</div>;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 32e39d25e..71f265484 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -25,18 +25,21 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
<button
className="antimodeMenu-button"
title="Create a Collection"
+ key="group"
onPointerDown={this.createCollection}>
<FontAwesomeIcon icon="object-group" size="lg" />
</button>,
<button
className="antimodeMenu-button"
title="Summarize Documents"
+ key="summarize"
onPointerDown={this.summarize}>
<FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
</button>,
<button
className="antimodeMenu-button"
title="Delete Documents"
+ key="delete"
onPointerDown={this.delete}>
<FontAwesomeIcon icon="trash-alt" size="lg" />
</button>,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 523edb918..e16f4011e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -29,7 +29,6 @@ interface MarqueeViewProps {
removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
- extensionDoc: Doc;
isAnnotationOverlay?: boolean;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
}
@@ -85,7 +84,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
ns.map(line => {
const indent = line.search(/\S|$/);
- const newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
+ const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, title: line });
this.props.addDocument(newBox);
y += 40 * this.props.getTransform().Scale;
});
@@ -95,17 +94,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
navigator.clipboard.readText().then(text => {
const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
if (ns.length === 1 && text.startsWith("http")) {
- this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
+ this.props.addDocument(Docs.Create.ImageDocument(text, { _nativeWidth: 300, _width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
} else {
this.pasteTable(ns, x, y);
}
});
} else if (!e.ctrlKey) {
this.props.addLiveTextDocument(
- Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
+ Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
} else if (e.keyCode > 48 && e.keyCode <= 57) {
const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
- const text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
+ const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
text.layout = notes[(e.keyCode - 49) % notes.length];
this.props.addLiveTextDocument(text);
}
@@ -128,7 +127,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
let groupAttr: string | number = "";
const rowProto = new Doc();
rowProto.title = rowProto.Id;
- rowProto.width = 200;
+ rowProto._width = 200;
rowProto.isPrototype = true;
for (let i = 1; i < ns.length - 1; i++) {
const values = ns[i].split("\t");
@@ -144,10 +143,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
docDataProto.title = i.toString();
const doc = Doc.MakeDelegate(docDataProto);
- doc.width = 200;
+ doc._width = 200;
docList.push(doc);
}
- const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+ const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", _width: 300, _height: 100 });
this.props.addDocument(newCol);
}
@@ -267,15 +266,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
get inkDoc() {
- return this.props.extensionDoc;
+ return this.props.Document;
}
get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
- return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField);
+ return Cast(this.props.Document.ink, InkField);
}
set ink(value: InkField | undefined) {
- this.props.extensionDoc && (this.props.extensionDoc.ink = value);
+ this.props.Document.ink = value;
}
@action
@@ -300,37 +299,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = (selected: Doc[]) => {
+ getCollection = (selected: Doc[], asTemplate: boolean) => {
const bounds = this.Bounds;
- const defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
- "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
- const colorPalette = Cast(this.props.Document.colorPalette, listSpec("string"));
- if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette);
- const palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]);
- const usedPaletted = new Map<string, number>();
- [...this.props.activeDocuments(), this.props.Document].map(child => {
- const bg = StrCast(Doc.Layout(child).backgroundColor);
- if (palette.indexOf(bg) !== -1) {
- palette.splice(palette.indexOf(bg), 1);
- if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
- else usedPaletted.set(bg, 1);
- }
- });
- usedPaletted.delete("#f1efeb");
- usedPaletted.delete("white");
- usedPaletted.delete("rgba(255,255,255,1)");
- const usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
- const chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
// const inkData = this.ink ? this.ink.inkData : undefined;
- const newCollection = Docs.Create.FreeformDocument(selected, {
+ const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
+ const newCollection = creator(selected, {
x: bounds.left,
y: bounds.top,
- panX: 0,
- panY: 0,
- backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- width: bounds.width,
- height: bounds.height,
+ _panX: 0,
+ _panY: 0,
+ backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : "white",
+ defaultBackgroundColor: this.props.isAnnotationOverlay ? "#00000015" : "white",
+ _width: bounds.width,
+ _height: bounds.height,
+ _LODdisable: true,
title: "a nested collection",
});
// const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
@@ -353,7 +335,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return d;
});
}
- const newCollection = this.getCollection(selected);
+ const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t");
this.props.addDocument(newCollection);
this.props.selectDocuments([newCollection], []);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -364,7 +346,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
- const newCollection = this.getCollection(selected);
+ const newCollection = this.getCollection(selected, false);
selected.map(d => {
this.props.removeDocument(d);
@@ -373,16 +355,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.page = -1;
return d;
});
- newCollection.chromeStatus = "disabled";
- const summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+ newCollection._chromeStatus = "disabled";
+ const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 300, _height: 100, _autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
newCollection.x = bounds.left + bounds.width;
Doc.GetProto(newCollection).summaryDoc = summary;
Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
- const container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
- container.viewType = CollectionViewType.Stacking;
- container.autoHeight = true;
+ const container = Docs.Create.FreeformDocument([summary, newCollection], {
+ x: bounds.left, y: bounds.top, _width: 300, _height: 200, _autoHeight: true,
+ _viewType: CollectionViewType.Stacking, _chromeStatus: "disabled", title: "-summary-"
+ });
Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
this.props.addLiveTextDocument(container);
} else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
@@ -405,12 +388,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.delete();
e.stopPropagation();
}
- if (e.key === "c" || e.key === "s" || e.key === "S") {
+ if (e.key === "c" || e.key === "t" || e.key === "s" || e.key === "S") {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
(e as any).propagationIsStopped = true;
- if (e.key === "c") {
+ if (e.key === "c" || e.key === "t") {
this.collection(e);
}
@@ -467,8 +450,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
- const w = NumCast(layoutDoc.width);
- const h = NumCast(layoutDoc.height);
+ const w = NumCast(layoutDoc._width);
+ const h = NumCast(layoutDoc._height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
selection.push(doc);
}
@@ -478,8 +461,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
- const w = NumCast(layoutDoc.width);
- const h = NumCast(layoutDoc.height);
+ const w = NumCast(layoutDoc._width);
+ const h = NumCast(layoutDoc._height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
selection.push(doc);
}
@@ -495,8 +478,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
- const w = NumCast(layoutDoc.width);
- const h = NumCast(layoutDoc.height);
+ const w = NumCast(layoutDoc._width);
+ const h = NumCast(layoutDoc._height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
selection.push(doc);
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index f57ba438a..0c74b8ddb 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -7,6 +7,7 @@
.document-wrapper {
display: flex;
flex-direction: column;
+ width: 100%;
.label-wrapper {
display: flex;
@@ -17,13 +18,13 @@
}
- .resizer {
+ .multiColumnResizer {
cursor: ew-resize;
transition: 0.5s opacity ease;
display: flex;
flex-direction: column;
- .internal {
+ .multiColumnResizer-hdl {
width: 100%;
height: 100%;
transition: 0.5s background-color ease;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 70e56183c..7d8de0db4 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,17 +1,18 @@
+import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
-import { makeInterface } from '../../../../new_fields/Schema';
-import { documentSchema } from '../../../../new_fields/documentSchemas';
-import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
import * as React from "react";
import { Doc } from '../../../../new_fields/Doc';
-import { NumCast, StrCast, BoolCast } from '../../../../new_fields/Types';
+import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { makeInterface } from '../../../../new_fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../new_fields/Types';
+import { DragManager } from '../../../util/DragManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
-import { Utils } from '../../../../Utils';
+import { CollectionSubView } from '../CollectionSubView';
import "./collectionMulticolumnView.scss";
-import { computed, trace, observable, action } from 'mobx';
-import { Transform } from '../../../util/Transform';
-import WidthLabel from './MulticolumnWidthLabel';
import ResizeBar from './MulticolumnResizer';
+import WidthLabel from './MulticolumnWidthLabel';
type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
const MulticolumnDocument = makeInterface(documentSchema);
@@ -26,13 +27,13 @@ interface LayoutData {
starSum: number;
}
-export const WidthUnit = {
+export const DimUnit = {
Pixel: "px",
Ratio: "*"
};
-const resolvedUnits = Object.values(WidthUnit);
-const resizerWidth = 4;
+const resolvedUnits = Object.values(DimUnit);
+const resizerWidth = 8;
@observer
export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
@@ -43,12 +44,12 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(({ layout }) => layout).filter(({ widthUnit }) => StrCast(widthUnit) === WidthUnit.Ratio);
+ return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout.dimUnit, "*") === DimUnit.Ratio);
}
/**
- * This loops through all childLayoutPairs and extracts the values for widthUnit
- * and widthMagnitude, ignoring any that are malformed. Additionally, it then
+ * This loops through all childLayoutPairs and extracts the values for dimUnit
+ * and dimMagnitude, ignoring any that are malformed. Additionally, it then
* normalizes the ratio values so that one * value is always 1, with the remaining
* values proportionate to that easily readable metric.
* @returns the list of the resolved width specifiers (unit and magnitude pairs)
@@ -58,11 +59,11 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
- this.childLayoutPairs.map(({ layout: { widthUnit, widthMagnitude } }) => {
- const unit = StrCast(widthUnit);
- const magnitude = NumCast(widthMagnitude);
+ this.childLayoutPairs.map(pair => {
+ const unit = StrCast(pair.layout.dimUnit, "*");
+ const magnitude = NumCast(pair.layout.dimMagnitude, 1);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
- (unit === WidthUnit.Ratio) && (starSum += magnitude);
+ (unit === DimUnit.Ratio) && (starSum += magnitude);
widthSpecifiers.push({ magnitude, unit });
}
/**
@@ -80,9 +81,9 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
setTimeout(() => {
const { ratioDefinedDocs } = this;
if (this.childLayoutPairs.length) {
- const minimum = Math.min(...ratioDefinedDocs.map(({ widthMagnitude }) => NumCast(widthMagnitude)));
+ const minimum = Math.min(...ratioDefinedDocs.map(doc => NumCast(doc.dimMagnitude, 1)));
if (minimum !== 0) {
- ratioDefinedDocs.forEach(layout => layout.widthMagnitude = NumCast(layout.widthMagnitude) / minimum);
+ ratioDefinedDocs.forEach(layout => layout.dimMagnitude = NumCast(layout.dimMagnitude, 1) / minimum, 1);
}
}
});
@@ -101,7 +102,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
@computed
private get totalFixedAllocation(): number | undefined {
return this.resolvedLayoutInformation?.widthSpecifiers.reduce(
- (sum, { magnitude, unit }) => sum + (unit === WidthUnit.Pixel ? magnitude : 0), 0);
+ (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
}
/**
@@ -117,7 +118,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
private get totalRatioAllocation(): number | undefined {
const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
- return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
+ return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._xMargin);
}
}
@@ -158,8 +159,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
if (columnUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
- let width = NumCast(layout.widthMagnitude);
- if (StrCast(layout.widthUnit) === WidthUnit.Ratio) {
+ let width = NumCast(layout.dimMagnitude, 1);
+ if (StrCast(layout.dimUnit, "*") === DimUnit.Ratio) {
width *= columnUnitLength;
}
return width;
@@ -186,6 +187,34 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
return Transform.Identity(); // type coersion, this case should never be hit
}
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.drop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
+ d.dimUnit = "*";
+ d.dimMagnitude = 1;
+ }));
+ }
+ return false;
+ }
+
+
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+ return <ContentFittingDocumentView
+ {...this.props}
+ Document={layout}
+ DataDocument={layout.resolvedDataDoc as Doc}
+ CollectionDoc={this.props.Document}
+ PanelWidth={width}
+ PanelHeight={height}
+ getTransform={dxf}
+ onClick={this.onChildClickHandler}
+ renderDepth={this.props.renderDepth + 1}
+ />
+ }
/**
* @returns the resolved list of rendered child documents, displayed
* at their resolved pixel widths, each separated by a resizer.
@@ -197,19 +226,14 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
+ const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
+ const width = () => this.lookupPixels(layout);
+ const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
collector.push(
- <div
- className={"document-wrapper"}
- key={Utils.GenerateGuid()}
- >
- <ContentFittingDocumentView
- {...this.props}
- Document={layout}
- DataDocument={layout.resolvedDataDoc as Doc}
- PanelWidth={() => this.lookupPixels(layout)}
- PanelHeight={() => PanelHeight() - (BoolCast(Document.showWidthLabels) ? 20 : 0)}
- getTransform={() => this.lookupIndividualTransform(layout)}
- />
+ <div className={"document-wrapper"}
+ key={"wrapper" + i}
+ style={{ width: width() }} >
+ {this.getDisplayDoc(layout, dxf, width, height)}
<WidthLabel
layout={layout}
collectionDoc={Document}
@@ -217,7 +241,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
</div>,
<ResizeBar
width={resizerWidth}
- key={Utils.GenerateGuid()}
+ key={"resizer" + i}
columnUnitLength={this.getColumnUnitLength}
toLeft={layout}
toRight={childLayoutPairs[i + 1]?.layout}
@@ -230,10 +254,11 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
render(): JSX.Element {
return (
- <div
- className={"collectionMulticolumnView_contents"}
- ref={this.createDropTarget}
- >
+ <div className={"collectionMulticolumnView_contents"}
+ style={{
+ marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
+ marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
+ }} ref={this.createDashEventsTarget}>
{this.contents}
</div>
);
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
new file mode 100644
index 000000000..64f607680
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -0,0 +1,35 @@
+.collectionMultirowView_contents {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ flex-direction: column;
+
+ .document-wrapper {
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+
+ .label-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ height: 20px;
+ }
+
+ }
+
+ .multiRowResizer {
+ cursor: ew-resize;
+ transition: 0.5s opacity ease;
+ display: flex;
+ flex-direction: row;
+
+ .multiRowResizer-hdl {
+ width: 100%;
+ height: 100%;
+ transition: 0.5s background-color ease;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
new file mode 100644
index 000000000..ff7c4998f
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -0,0 +1,269 @@
+import { observer } from 'mobx-react';
+import { makeInterface } from '../../../../new_fields/Schema';
+import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
+import * as React from "react";
+import { Doc } from '../../../../new_fields/Doc';
+import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types';
+import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
+import { Utils } from '../../../../Utils';
+import "./collectionMultirowView.scss";
+import { computed, trace, observable, action } from 'mobx';
+import { Transform } from '../../../util/Transform';
+import HeightLabel from './MultirowHeightLabel';
+import ResizeBar from './MultirowResizer';
+import { undoBatch } from '../../../util/UndoManager';
+import { DragManager } from '../../../util/DragManager';
+
+type MultirowDocument = makeInterface<[typeof documentSchema]>;
+const MultirowDocument = makeInterface(documentSchema);
+
+interface HeightSpecifier {
+ magnitude: number;
+ unit: string;
+}
+
+interface LayoutData {
+ heightSpecifiers: HeightSpecifier[];
+ starSum: number;
+}
+
+export const DimUnit = {
+ Pixel: "px",
+ Ratio: "*"
+};
+
+const resolvedUnits = Object.values(DimUnit);
+const resizerHeight = 8;
+
+@observer
+export class CollectionMultirowView extends CollectionSubView(MultirowDocument) {
+
+ /**
+ * @returns the list of layout documents whose width unit is
+ * *, denoting that it will be displayed with a ratio, not fixed pixel, value
+ */
+ @computed
+ private get ratioDefinedDocs() {
+ return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout.dimUnit, "*") === DimUnit.Ratio);
+ }
+
+ /**
+ * This loops through all childLayoutPairs and extracts the values for dimUnit
+ * and dimUnit, ignoring any that are malformed. Additionally, it then
+ * normalizes the ratio values so that one * value is always 1, with the remaining
+ * values proportionate to that easily readable metric.
+ * @returns the list of the resolved width specifiers (unit and magnitude pairs)
+ * as well as the sum of the * coefficients, i.e. the ratio magnitudes
+ */
+ @computed
+ private get resolvedLayoutInformation(): LayoutData {
+ let starSum = 0;
+ const heightSpecifiers: HeightSpecifier[] = [];
+ this.childLayoutPairs.map(pair => {
+ const unit = StrCast(pair.layout.dimUnit, "*");
+ const magnitude = NumCast(pair.layout.dimMagnitude, 1);
+ if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+ (unit === DimUnit.Ratio) && (starSum += magnitude);
+ heightSpecifiers.push({ magnitude, unit });
+ }
+ /**
+ * Otherwise, the child document is ignored and the remaining
+ * space is allocated as if the document were absent from the child list
+ */
+ });
+
+ /**
+ * Here, since these values are all relative, adjustments during resizing or
+ * manual updating can, though their ratios remain the same, cause the values
+ * themselves to drift toward zero. Thus, whenever we change any of the values,
+ * we normalize everything (dividing by the smallest magnitude).
+ */
+ setTimeout(() => {
+ const { ratioDefinedDocs } = this;
+ if (this.childLayoutPairs.length) {
+ const minimum = Math.min(...ratioDefinedDocs.map(layout => NumCast(layout.dimMagnitude, 1)));
+ if (minimum !== 0) {
+ ratioDefinedDocs.forEach(layout => layout.dimMagnitude = NumCast(layout.dimMagnitude, 1) / minimum);
+ }
+ }
+ });
+
+ return { heightSpecifiers, starSum };
+ }
+
+ /**
+ * This returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with higher priority) requested a fixed pixel width.
+ *
+ * If the underlying resolvedLayoutInformation returns null
+ * because we're waiting on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalFixedAllocation(): number | undefined {
+ return this.resolvedLayoutInformation?.heightSpecifiers.reduce(
+ (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
+ }
+
+ /**
+ * @returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with lower priority) requested a certain relative proportion of the
+ * remaining pixel width not allocated for fixed widths.
+ *
+ * If the underlying totalFixedAllocation returns undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalRatioAllocation(): number | undefined {
+ const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
+ if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
+ return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._yMargin);
+ }
+ }
+
+ /**
+ * @returns the total quantity, in pixels, that
+ * 1* (relative / star unit) is worth. For example,
+ * if the configuration has three documents, with, respectively,
+ * widths of 2*, 2* and 1*, and the panel width returns 1000px,
+ * this accessor returns 1000 / (2 + 2 + 1), or 200px.
+ * Elsewhere, this is then multiplied by each relative-width
+ * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
+ *
+ * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get rowUnitLength(): number | undefined {
+ if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
+ return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
+ }
+ }
+
+ /**
+ * This wrapper function exists to prevent mobx from
+ * needlessly rerendering the internal ContentFittingDocumentViews
+ */
+ private getRowUnitLength = () => this.rowUnitLength;
+
+ /**
+ * @param layout the document whose transform we'd like to compute
+ * Given a layout document, this function
+ * returns the resolved width it has requested, in pixels.
+ * @returns the stored row width if already in pixels,
+ * or the ratio width evaluated to a pixel value
+ */
+ private lookupPixels = (layout: Doc): number => {
+ const rowUnitLength = this.rowUnitLength;
+ if (rowUnitLength === undefined) {
+ return 0; // we're still waiting on promises to resolve
+ }
+ let height = NumCast(layout.dimMagnitude, 1);
+ if (StrCast(layout.dimUnit, "*") === DimUnit.Ratio) {
+ height *= rowUnitLength;
+ }
+ return height;
+ }
+
+ /**
+ * @returns the transform that will correctly place
+ * the document decorations box, shifted to the right by
+ * the sum of all the resolved row widths of the
+ * documents before the target.
+ */
+ private lookupIndividualTransform = (layout: Doc) => {
+ const rowUnitLength = this.rowUnitLength;
+ if (rowUnitLength === undefined) {
+ return Transform.Identity(); // we're still waiting on promises to resolve
+ }
+ let offset = 0;
+ for (const { layout: candidate } of this.childLayoutPairs) {
+ if (candidate === layout) {
+ return this.props.ScreenToLocalTransform().translate(0, -offset);
+ }
+ offset += this.lookupPixels(candidate) + resizerHeight;
+ }
+ return Transform.Identity(); // type coersion, this case should never be hit
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.drop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
+ d.dimUnit = "*";
+ d.dimMagnitude = 1;
+ }));
+ }
+ return false;
+ }
+
+
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+ return <ContentFittingDocumentView
+ {...this.props}
+ Document={layout}
+ DataDocument={layout.resolvedDataDoc as Doc}
+ CollectionDoc={this.props.Document}
+ PanelWidth={width}
+ PanelHeight={height}
+ getTransform={dxf}
+ onClick={this.onChildClickHandler}
+ renderDepth={this.props.renderDepth + 1}
+ />
+ }
+ /**
+ * @returns the resolved list of rendered child documents, displayed
+ * at their resolved pixel widths, each separated by a resizer.
+ */
+ @computed
+ private get contents(): JSX.Element[] | null {
+ const { childLayoutPairs } = this;
+ const { Document, PanelWidth } = this.props;
+ const collector: JSX.Element[] = [];
+ for (let i = 0; i < childLayoutPairs.length; i++) {
+ const { layout } = childLayoutPairs[i];
+ const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
+ const height = () => this.lookupPixels(layout);
+ const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
+ collector.push(
+ <div
+ className={"document-wrapper"}
+ key={"wrapper" + i}
+ >
+ {this.getDisplayDoc(layout, dxf, width, height)}
+ <HeightLabel
+ layout={layout}
+ collectionDoc={Document}
+ />
+ </div>,
+ <ResizeBar
+ height={resizerHeight}
+ key={"resizer" + i}
+ columnUnitLength={this.getRowUnitLength}
+ toTop={layout}
+ toBottom={childLayoutPairs[i + 1]?.layout}
+ />
+ );
+ }
+ collector.pop(); // removes the final extraneous resize bar
+ return collector;
+ }
+
+ render(): JSX.Element {
+ return (
+ <div className={"collectionMultirowView_contents"}
+ style={{
+ marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
+ marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
+ }} ref={this.createDashEventsTarget}>
+ {this.contents}
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 11e210958..6b89402e6 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { observable, action } from "mobx";
import { Doc } from "../../../../new_fields/Doc";
import { NumCast, StrCast } from "../../../../new_fields/Types";
-import { WidthUnit } from "./CollectionMulticolumnView";
+import { DimUnit } from "./CollectionMulticolumnView";
interface ResizerProps {
width: number;
@@ -46,14 +46,12 @@ export default class ResizeBar extends React.Component<ResizerProps> {
const unitLength = columnUnitLength();
if (unitLength) {
if (toNarrow) {
- const { widthUnit, widthMagnitude } = toNarrow;
- const scale = widthUnit === WidthUnit.Ratio ? unitLength : 1;
- toNarrow.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / scale;
+ const scale = StrCast(toNarrow.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toNarrow.dimMagnitude = Math.max(0.05, NumCast(toNarrow.dimMagnitude, 1) - Math.abs(movementX) / scale);
}
if (this.resizeMode === ResizeMode.Pinned && toWiden) {
- const { widthUnit, widthMagnitude } = toWiden;
- const scale = widthUnit === WidthUnit.Ratio ? unitLength : 1;
- toWiden.widthMagnitude = NumCast(widthMagnitude) + Math.abs(movementX) / scale;
+ const scale = StrCast(toWiden.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toWiden.dimMagnitude = Math.max(0.05, NumCast(toWiden.dimMagnitude, 1) + Math.abs(movementX) / scale);
}
}
}
@@ -61,17 +59,17 @@ export default class ResizeBar extends React.Component<ResizerProps> {
private get isActivated() {
const { toLeft, toRight } = this.props;
if (toLeft && toRight) {
- if (StrCast(toLeft.widthUnit) === WidthUnit.Pixel && StrCast(toRight.widthUnit) === WidthUnit.Pixel) {
+ if (StrCast(toLeft.dimUnit, "*") === DimUnit.Pixel && StrCast(toRight.dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
} else if (toLeft) {
- if (StrCast(toLeft.widthUnit) === WidthUnit.Pixel) {
+ if (StrCast(toLeft.dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
} else if (toRight) {
- if (StrCast(toRight.widthUnit) === WidthUnit.Pixel) {
+ if (StrCast(toRight.dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
@@ -91,7 +89,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
render() {
return (
<div
- className={"resizer"}
+ className={"multiColumnResizer"}
style={{
width: this.props.width,
opacity: this.isActivated && this.isHoverActive ? resizerOpacity : 0
@@ -100,12 +98,12 @@ export default class ResizeBar extends React.Component<ResizerProps> {
onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
>
<div
- className={"internal"}
+ className={"multiColumnResizer-hdl"}
onPointerDown={e => this.registerResizing(e, ResizeMode.Pinned)}
style={{ backgroundColor: this.resizeMode }}
/>
<div
- className={"internal"}
+ className={"multiColumnResizer-hdl"}
onPointerDown={e => this.registerResizing(e, ResizeMode.Global)}
style={{ backgroundColor: this.resizeMode }}
/>
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index b394fed62..5b2054428 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -4,7 +4,7 @@ import { computed } from "mobx";
import { Doc } from "../../../../new_fields/Doc";
import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types";
import { EditableView } from "../../EditableView";
-import { WidthUnit } from "./CollectionMulticolumnView";
+import { DimUnit } from "./CollectionMulticolumnView";
interface WidthLabelProps {
layout: Doc;
@@ -18,8 +18,8 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
@computed
private get contents() {
const { layout, decimals } = this.props;
- const getUnit = () => StrCast(layout.widthUnit);
- const getMagnitude = () => String(+NumCast(layout.widthMagnitude).toFixed(decimals ?? 3));
+ const getUnit = () => StrCast(layout.dimUnit);
+ const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
return (
<div className={"label-wrapper"}>
<EditableView
@@ -27,7 +27,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
SetValue={value => {
const converted = Number(value);
if (!isNaN(converted) && converted > 0) {
- layout.widthMagnitude = converted;
+ layout.dimMagnitude = converted;
return true;
}
return false;
@@ -37,8 +37,8 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
<EditableView
GetValue={getUnit}
SetValue={value => {
- if (Object.values(WidthUnit).includes(value)) {
- layout.widthUnit = value;
+ if (Object.values(DimUnit).includes(value)) {
+ layout.dimUnit = value;
return true;
}
return false;
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
new file mode 100644
index 000000000..899577fd5
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+import { observer } from "mobx-react";
+import { computed } from "mobx";
+import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types";
+import { EditableView } from "../../EditableView";
+import { DimUnit } from "./CollectionMultirowView";
+
+interface HeightLabelProps {
+ layout: Doc;
+ collectionDoc: Doc;
+ decimals?: number;
+}
+
+@observer
+export default class HeightLabel extends React.Component<HeightLabelProps> {
+
+ @computed
+ private get contents() {
+ const { layout, decimals } = this.props;
+ const getUnit = () => StrCast(layout.dimUnit);
+ const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
+ return (
+ <div className={"label-wrapper"}>
+ <EditableView
+ GetValue={getMagnitude}
+ SetValue={value => {
+ const converted = Number(value);
+ if (!isNaN(converted) && converted > 0) {
+ layout.dimMagnitude = converted;
+ return true;
+ }
+ return false;
+ }}
+ contents={getMagnitude()}
+ />
+ <EditableView
+ GetValue={getUnit}
+ SetValue={value => {
+ if (Object.values(DimUnit).includes(value)) {
+ layout.dimUnit = value;
+ return true;
+ }
+ return false;
+ }}
+ contents={getUnit()}
+ />
+ </div>
+ );
+ }
+
+ render() {
+ return BoolCast(this.props.collectionDoc.showHeightLabels) ? this.contents : (null);
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
new file mode 100644
index 000000000..d00939b26
--- /dev/null
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -0,0 +1,114 @@
+import * as React from "react";
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, StrCast } from "../../../../new_fields/Types";
+import { DimUnit } from "./CollectionMultirowView";
+
+interface ResizerProps {
+ height: number;
+ columnUnitLength(): number | undefined;
+ toTop?: Doc;
+ toBottom?: Doc;
+}
+
+enum ResizeMode {
+ Global = "blue",
+ Pinned = "red",
+ Undefined = "black"
+}
+
+const resizerOpacity = 1;
+
+@observer
+export default class ResizeBar extends React.Component<ResizerProps> {
+ @observable private isHoverActive = false;
+ @observable private isResizingActive = false;
+ @observable private resizeMode = ResizeMode.Undefined;
+
+ @action
+ private registerResizing = (e: React.PointerEvent<HTMLDivElement>, mode: ResizeMode) => {
+ e.stopPropagation();
+ e.preventDefault();
+ this.resizeMode = mode;
+ window.removeEventListener("pointermove", this.onPointerMove);
+ window.removeEventListener("pointerup", this.onPointerUp);
+ window.addEventListener("pointermove", this.onPointerMove);
+ window.addEventListener("pointerup", this.onPointerUp);
+ this.isResizingActive = true;
+ }
+
+ private onPointerMove = ({ movementY }: PointerEvent) => {
+ const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props;
+ const movingDown = movementY > 0;
+ const toNarrow = movingDown ? toBottom : toTop;
+ const toWiden = movingDown ? toTop : toBottom;
+ const unitLength = columnUnitLength();
+ if (unitLength) {
+ if (toNarrow) {
+ const scale = StrCast(toNarrow.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toNarrow.dimMagnitude = Math.max(0.05, NumCast(toNarrow.dimMagnitude, 1) - Math.abs(movementY) / scale);
+ }
+ if (this.resizeMode === ResizeMode.Pinned && toWiden) {
+ const scale = StrCast(toWiden.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toWiden.dimMagnitude = Math.max(0.05, NumCast(toWiden.dimMagnitude, 1) + Math.abs(movementY) / scale);
+ }
+ }
+ }
+
+ private get isActivated() {
+ const { toTop, toBottom } = this.props;
+ if (toTop && toBottom) {
+ if (StrCast(toTop.dimUnit, "*") === DimUnit.Pixel && StrCast(toBottom.dimUnit, "*") === DimUnit.Pixel) {
+ return false;
+ }
+ return true;
+ } else if (toTop) {
+ if (StrCast(toTop.dimUnit, "*") === DimUnit.Pixel) {
+ return false;
+ }
+ return true;
+ } else if (toBottom) {
+ if (StrCast(toBottom.dimUnit, "*") === DimUnit.Pixel) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ private onPointerUp = () => {
+ this.resizeMode = ResizeMode.Undefined;
+ this.isResizingActive = false;
+ this.isHoverActive = false;
+ window.removeEventListener("pointermove", this.onPointerMove);
+ window.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ render() {
+ return (
+ <div
+ className={"multiRowResizer"}
+ style={{
+ height: this.props.height,
+ opacity: this.isActivated && this.isHoverActive ? resizerOpacity : 0
+ }}
+ onPointerEnter={action(() => this.isHoverActive = true)}
+ onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
+ >
+ <div
+ className={"multiRowResizer-hdl"}
+ onPointerDown={e => this.registerResizing(e, ResizeMode.Pinned)}
+ style={{ backgroundColor: this.resizeMode }}
+ />
+ <div
+ className={"multiRowResizer-hdl"}
+ onPointerDown={e => this.registerResizing(e, ResizeMode.Global)}
+ style={{ backgroundColor: this.resizeMode }}
+ />
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 6dffee586..019f931f9 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -25,6 +25,8 @@ $search-thumnail-size: 175;
// dragged items
$contextMenu-zindex: 100000; // context menu shows up over everything
+$radialMenu-zindex: 100000; // context menu shows up over everything
+
$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
$docDecorations-zindex: 998; // then doc decorations appear over everything else
$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index bb8a8b47b..e3bf6b5f8 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -292,7 +292,7 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
if (index > -1) keys.splice(index, 1);
const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
- const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
+ const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table" }));
const ref = React.createRef<HTMLDivElement>();
return <div ref={ref}><button className="linkEditor-button" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
}
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 29e167ff7..325c92413 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -89,7 +89,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
async resetPan() {
if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
runInAction(() => this.canPan = false);
- if (this.sourceView.props.ContainingCollectionDoc.viewType === CollectionViewType.Freeform) {
+ if (this.sourceView.props.ContainingCollectionDoc._viewType === CollectionViewType.Freeform) {
const docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []);
const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
@@ -165,11 +165,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
@undoBatch
openColFullScreen = (options: { context: Doc }) => {
if (LinkFollowBox.destinationDoc) {
- if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
- const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
- options.context.panX = newPanX;
- options.context.panY = newPanY;
+ if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
+ const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
+ options.context._panX = newPanX;
+ options.context._panY = newPanY;
}
const view = DocumentManager.Instance.getDocumentView(options.context);
view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
@@ -193,11 +193,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => {
if (LinkFollowBox.destinationDoc) {
options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
- if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
- const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
- options.context.panX = newPanX;
- options.context.panY = newPanY;
+ if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
+ const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
+ options.context._panX = newPanX;
+ options.context._panY = newPanY;
}
(LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, undefined, "onRight");
@@ -245,11 +245,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => {
if (LinkFollowBox.destinationDoc) {
options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
- if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
- const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
- options.context.panX = newPanX;
- options.context.panY = newPanY;
+ if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
+ const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
+ options.context._panX = newPanX;
+ options.context._panY = newPanY;
}
(LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, undefined, "inTab");
if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
@@ -270,13 +270,13 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
const y = NumCast(LinkFollowBox.sourceDoc.y);
const x = NumCast(LinkFollowBox.sourceDoc.x);
- const width = NumCast(LinkFollowBox.sourceDoc.width);
- const height = NumCast(LinkFollowBox.sourceDoc.height);
+ const width = NumCast(LinkFollowBox.sourceDoc._width);
+ const height = NumCast(LinkFollowBox.sourceDoc._height);
alias.x = x + width + 30;
alias.y = y;
- alias.width = width;
- alias.height = height;
+ alias._width = width;
+ alias._height = height;
this.sourceView.props.addDocument(alias);
}
@@ -361,7 +361,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
get canOpenInPlace() {
if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
const colDoc = this.sourceView.props.ContainingCollectionDoc;
- if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) return true;
+ if (colDoc._viewType === CollectionViewType.Freeform) return true;
}
return false;
}
@@ -481,7 +481,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
let contextMatch: boolean = false;
if (this.selectedContextAliases) {
this.selectedContextAliases.forEach(alias => {
- if (alias.viewType === CollectionViewType.Freeform) contextMatch = true;
+ if (alias._viewType === CollectionViewType.Freeform) contextMatch = true;
});
}
if (contextMatch) return true;
@@ -523,7 +523,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
render() {
return (
- <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
+ <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document._height), width: NumCast(this.props.Document._width) }}>
<div className="linkFollowBox-header">
<div className="topHeader">
{LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
@@ -533,7 +533,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
: "" : ""}</div>
</div>
- <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 110 }}>
+ <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document._height) - 110 }}>
<div className="linkFollowBox-item">
<div className="linkFollowBox-item title">Mode</div>
<div className="linkFollowBox-itemContent">
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 52628ba4c..1a40f0c55 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -25,7 +25,7 @@ export class LinkMenu extends React.Component<Props> {
@observable private _editingLink?: Doc;
@action
- componentWillReceiveProps() {
+ componentDidMount() {
this._editingLink = undefined;
}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index ace9a9e4c..0c38ff45c 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -58,7 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
if (index > -1) keys.splice(index, 1);
const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
- const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
+ const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table" }));
const ref = React.createRef<HTMLDivElement>();
return <div ref={ref}><button className="linkEditor-button linkEditor-tableButton" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 95c765e8a..62a479b2a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -68,7 +68,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime());
});
this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => {
- const start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart);
+ const start = DateCast(this.dataDoc[this.props.fieldKey + "-recordingStart"]);
start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000);
});
}
@@ -128,18 +128,17 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
recordAudioAnnotation = () => {
let gumStream: any;
const self = this;
- const extensionDoc = this.extensionDoc;
- extensionDoc && navigator.mediaDevices.getUserMedia({
+ navigator.mediaDevices.getUserMedia({
audio: true
}).then(function (stream) {
gumStream = stream;
self._recorder = new MediaRecorder(stream);
- extensionDoc.recordingStart = new DateField(new Date());
+ self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date());
AudioBox.ActiveRecordings.push(self.props.Document);
self._recorder.ondataavailable = async function (e: any) {
const formData = new FormData();
formData.append("file", e.data);
- const res = await fetch(Utils.prepend("/upload"), {
+ const res = await fetch(Utils.prepend("/uploadFormData"), {
method: 'POST',
body: formData
});
@@ -213,55 +212,53 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
render() {
const interactive = this.active() ? "-interactive" : "";
- return (!this.extensionDoc ? (null) :
- <div className={`audiobox-container`} onContextMenu={this.specificContextMenu}
- onClick={!this.path ? this.recordClick : undefined}>
- <div className="audiobox-handle"></div>
- {!this.path ?
- <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}>
- {this._audioState === "recording" ? "STOP" : "RECORD"}
- </button> :
- <div className="audiobox-controls">
- <div className="audiobox-player" onClick={this.onPlay}>
- <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
- <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
- <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
- onPointerDown={e => {
- if (e.button === 0 && !e.ctrlKey) {
- const rect = (e.target as any).getBoundingClientRect();
- this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
- this.pause();
- e.stopPropagation();
- }
- }} >
- {DocListCast(this.dataDoc.links).map((l, i) => {
- let la1 = l.anchor1 as Doc;
- let la2 = l.anchor2 as Doc;
- let linkTime = NumCast(l.anchor2Timecode);
- if (Doc.AreProtosEqual(la1, this.dataDoc)) {
- la1 = l.anchor2 as Doc;
- la2 = l.anchor1 as Doc;
- linkTime = NumCast(l.anchor1Timecode);
- }
- return !linkTime ? (null) :
- <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
- <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
- <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
- parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
- backgroundColor={returnTransparent} />
- </div>
- <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
- onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
- onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
- </div>;
- })}
- <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
- {this.audio}
- </div>
+ return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu}
+ onClick={!this.path ? this.recordClick : undefined}>
+ <div className="audiobox-handle"></div>
+ {!this.path ?
+ <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}>
+ {this._audioState === "recording" ? "STOP" : "RECORD"}
+ </button> :
+ <div className="audiobox-controls">
+ <div className="audiobox-player" onClick={this.onPlay}>
+ <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+ <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+ <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
+ onPointerDown={e => {
+ if (e.button === 0 && !e.ctrlKey) {
+ const rect = (e.target as any).getBoundingClientRect();
+ this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+ this.pause();
+ e.stopPropagation();
+ }
+ }} >
+ {DocListCast(this.dataDoc.links).map((l, i) => {
+ let la1 = l.anchor1 as Doc;
+ let la2 = l.anchor2 as Doc;
+ let linkTime = NumCast(l.anchor2Timecode);
+ if (Doc.AreProtosEqual(la1, this.dataDoc)) {
+ la1 = l.anchor2 as Doc;
+ la2 = l.anchor1 as Doc;
+ linkTime = NumCast(l.anchor1Timecode);
+ }
+ return !linkTime ? (null) :
+ <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
+ <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
+ <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
+ parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
+ backgroundColor={returnTransparent} />
+ </div>
+ <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
+ onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
+ onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
+ </div>;
+ })}
+ <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
+ {this.audio}
</div>
</div>
- }
- </div>
- );
+ </div>
+ }
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index d1272c266..ee48b47b7 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -36,7 +36,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
@computed get dataDoc() {
return this.props.DataDoc &&
- (this.Document.isTemplateField || BoolCast(this.props.DataDoc.isTemplateField) ||
+ (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
}
@@ -80,7 +80,10 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
return (
<div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
- <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black", fontSize: this.Document.fontSize }} >
+ <div className="buttonBox-mainButton" style={{
+ background: this.Document.backgroundColor, color: this.Document.color || "black",
+ fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: this.Document.textTransform || ""
+ }} >
<div className="buttonBox-mainButtonCenter">
{(this.Document.text || this.Document.title)}
</div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 614a68e7a..3bceec45f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -2,7 +2,6 @@ import anime from "animejs";
import { computed, IReactionDisposer, observable, reaction, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
-import { listSpec } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { Transform } from "../../util/Transform";
import { DocComponent } from "../DocComponent";
@@ -15,9 +14,12 @@ import { returnFalse } from "../../../Utils";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
+ dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined;
x?: number;
y?: number;
+ z?: number;
+ zIndex?: number;
+ highlight?: boolean;
width?: number;
height?: number;
jitterRotation: number;
@@ -27,68 +29,48 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
- _disposer: IReactionDisposer | undefined = undefined;
+ @observable _animPos: number[] | undefined = undefined;
get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; }
- get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
- get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
+ get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
+ get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
+ get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
+ get Highlight() { return this.dataProvider?.highlight; }
get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
get height() {
const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
}
@computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
- @computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); }
- @computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); }
+ @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth); }
+ @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight); }
@computed get renderScriptDim() {
if (this.Document.renderScript) {
const someView = Cast(this.props.Document.someView, Doc);
const minimap = Cast(this.props.Document.minimap, Doc);
if (someView instanceof Doc && minimap instanceof Doc) {
- const x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2;
- const y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2;
- const w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
- const h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
+ const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap._width) - NumCast(minimap._width) / 2;
+ const y = (NumCast(someView._panY) - NumCast(someView._height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2;
+ const w = NumCast(someView._width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
+ const h = NumCast(someView._height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
return { x: x, y: y, width: w, height: h };
}
}
return undefined;
}
- componentWillUnmount() {
- this._disposer && this._disposer();
- }
- componentDidMount() {
- this._disposer = reaction(() => this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined,
- target => this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]),
- { fireImmediately: true });
- }
-
- contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1;
- panelWidth = () => this.props.PanelWidth();
- panelHeight = () => this.props.PanelHeight();
+ contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect && !this.props.fitToBox ? this.width / this.nativeWidth : 1;
+ clusterColorFunc = (doc: Doc) => this.clusterColor;
+ panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth());
+ panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight());
getTransform = (): Transform => this.props.ScreenToLocalTransform()
.translate(-this.X, -this.Y)
.scale(1 / this.contentScaling())
- borderRounding = () => {
- const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
- const ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
- const br = StrCast((ld || this.props.Document).borderRounding);
- return !br && ruleRounding ? ruleRounding : br;
- }
-
@computed
get clusterColor() { return this.props.backgroundColor(this.props.Document); }
-
- clusterColorFunc = (doc: Doc) => this.clusterColor;
-
- @observable _animPos: number[] | undefined = undefined;
-
- finalPanelWidth = () => (this.dataProvider ? this.dataProvider.width : this.panelWidth());
- finalPanelHeight = () => (this.dataProvider ? this.dataProvider.height : this.panelHeight());
-
+ focusDoc = (doc: Doc) => this.props.focus(doc, false);
render() {
TraceMobx();
return <div className="collectionFreeFormDocumentView-container"
@@ -99,29 +81,32 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this.clusterColor ? (`${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big
StrCast(this.layoutDoc.boxShadow, ""),
- borderRadius: this.borderRounding(),
+ borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding),
+ outline: this.Highlight ? "orange solid 2px" : "",
transform: this.transform,
transition: this.Document.isAnimating ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
width: this.width,
height: this.height,
- zIndex: this.Document.zIndex || 0,
+ zIndex: this.ZInd,
+ display: this.ZInd === -99 ? "none" : undefined,
+ pointerEvents: this.props.Document.isBackground ? "none" : undefined
}} >
-
{!this.props.fitToBox ? <DocumentView {...this.props}
dragDivName={"collectionFreeFormDocumentView-container"}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
backgroundColor={this.clusterColorFunc}
- PanelWidth={this.finalPanelWidth}
- PanelHeight={this.finalPanelHeight}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
/> : <ContentFittingDocumentView {...this.props}
+ CollectionDoc={this.props.ContainingCollectionDoc}
DataDocument={this.props.DataDoc}
getTransform={this.getTransform}
active={returnFalse}
- focus={(doc: Doc) => this.props.focus(doc, false)}
- PanelWidth={this.finalPanelWidth}
- PanelHeight={this.finalPanelHeight}
+ focus={this.focusDoc}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
/>}
</div>;
}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.scss b/src/client/views/nodes/ContentFittingDocumentView.scss
index 2801af441..eb2d93b9a 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.scss
+++ b/src/client/views/nodes/ContentFittingDocumentView.scss
@@ -19,6 +19,6 @@
.documentView-node:first-child {
position: relative;
- background: $light-color;
+ background: "#B59B66"; //$light-color;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index e97445f27..bd1b6166f 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -24,9 +24,7 @@ interface ContentFittingDocumentViewProps {
fitToBox?: boolean;
PanelWidth: () => number;
PanelHeight: () => number;
- ruleProvider: Doc | undefined;
focus?: (doc: Doc) => void;
- showOverlays?: (doc: Doc) => { title?: string, caption?: string };
CollectionView?: CollectionView;
CollectionDoc?: Doc;
onClick?: ScriptField;
@@ -45,15 +43,16 @@ interface ContentFittingDocumentViewProps {
export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); }
- private get nativeWidth() { return NumCast(this.layoutDoc?.nativeWidth, this.props.PanelWidth()); }
- private get nativeHeight() { return NumCast(this.layoutDoc?.nativeHeight, this.props.PanelHeight()); }
- private contentScaling = () => {
+ private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); }
+ private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); }
+ @computed get scaling() {
const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1);
if (wscale * this.nativeHeight > this.props.PanelHeight()) {
return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1;
}
return wscale || 1;
}
+ private contentScaling = () => this.scaling;
@undoBatch
@action
@@ -63,16 +62,20 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
this.props.childDocs && this.props.childDocs.map(otherdoc => {
const target = Doc.GetProto(otherdoc);
target.layout = ComputedField.MakeFunction("this.image_data[0]");
- target.layoutCustom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
+ target.layout_custom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
});
e.stopPropagation();
}
return true;
}
- private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth();
- private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight();
+ private PanelWidth = () => this.panelWidth;
+ private PanelHeight = () => this.panelHeight;;
+
+ @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); }
+ @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); }
+
private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
- private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
+ private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; }
@computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
@@ -97,8 +100,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
LibraryPath={this.props.LibraryPath}
fitToBox={this.props.fitToBox}
onClick={this.props.onClick}
- ruleProvider={this.props.ruleProvider}
- showOverlays={this.props.showOverlays}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
moveDocument={this.props.moveDocument}
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index 0d4d50c59..a4a9a62aa 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -61,10 +61,12 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
}
}
onClick = (e: React.MouseEvent) => {
- if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
- DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false);
+ if (!this.props.Document.onClick) {
+ if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
+ DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false);
+ }
+ e.stopPropagation();
}
- e.stopPropagation();
}
render() {
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 863ea748b..6b7b652c6 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -96,7 +96,6 @@ export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocB
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
- ruleProvider={this.props.ruleProvider}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
getTransform={this.getTransform}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index aa553afb7..ac80e2ec1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -56,14 +56,12 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
export class DocumentContentsView extends React.Component<DocumentViewProps & {
isSelected: (outsideReaction: boolean) => boolean,
select: (ctrl: boolean) => void,
- onClick?: ScriptField,
layoutKey: string,
- hideOnLeave?: boolean
}> {
@computed get layout(): string {
TraceMobx();
if (!this.layoutDoc) return "<p>awaiting layout</p>";
- const layout = Cast(this.layoutDoc[this.props.layoutKey], "string");
+ const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");
if (layout === undefined) {
return this.props.Document.data ?
"<FieldView {...props} fieldKey='data' />" :
@@ -79,12 +77,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") {
// if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string),
// then we render the layout document as a template and use this document as the data context for the template layout.
- return this.props.Document;
+ const proto = Doc.GetProto(this.props.Document);
+ return proto instanceof Promise ? undefined : proto;
}
- return this.props.DataDoc;
+ return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc;
}
get layoutDoc() {
- return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document);
+ if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") {
+ // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string),
+ // then we render the layout document as a template and use this document as the data context for the template layout.
+ return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document);
+ }
+ return Doc.Layout(this.props.Document);
}
CreateBindings(): JsxBindings {
@@ -98,7 +102,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
render() {
TraceMobx();
- return (this.props.renderDepth > 7 || !this.layout) ? (null) :
+ return (this.props.renderDepth > 7 || !this.layout || !this.layoutDoc) ? (null) :
<ObserverJsxParser
blacklistedAttrs={[]}
components={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 60dc253f7..dcdce5fce 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -9,12 +9,12 @@ import { Id } from '../../../new_fields/FieldSymbols';
import { listSpec } from "../../../new_fields/Schema";
import { ScriptField } from '../../../new_fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { ImageField } from '../../../new_fields/URLField';
+import { ImageField, PdfField, VideoField, AudioField } from '../../../new_fields/URLField';
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from "../../DocServer";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { ClientUtils } from '../../util/ClientUtils';
import { DocumentManager } from "../../util/DocumentManager";
@@ -44,7 +44,13 @@ import { InkTool } from '../../../new_fields/InkField';
import { TraceMobx } from '../../../new_fields/util';
import { List } from '../../../new_fields/List';
import { FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { RadialMenu } from './RadialMenu';
+import { RadialMenuProps } from './RadialMenuItem';
+
import { CollectionStackingView } from '../collections/CollectionStackingView';
+import { RichTextField } from '../../../new_fields/RichTextField';
+import { HistoryUtil } from '../../util/History';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -58,15 +64,15 @@ export interface DocumentViewProps {
LibraryPath: Doc[];
fitToBox?: boolean;
onClick?: ScriptField;
+ onPointerDown?: ScriptField;
+ onPointerUp?: ScriptField;
dragDivName?: string;
addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
renderDepth: number;
- showOverlays?: (doc: Doc) => { title?: string, titleHover?: string, caption?: string };
ContentScaling: () => number;
- ruleProvider: Doc | undefined;
PanelWidth: () => number;
PanelHeight: () => number;
focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
@@ -82,9 +88,9 @@ export interface DocumentViewProps {
ChromeHeight?: () => number;
dontRegisterView?: boolean;
layoutKey?: string;
+ radialMenu?: String[];
}
-
@observer
export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
private _downX: number = 0;
@@ -94,19 +100,86 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _hitTemplateDrag = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
private _titleRef = React.createRef<EditableView>();
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
@computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@computed get topMost() { return this.props.renderDepth === 0; }
- @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
- @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
- @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
+ @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
+ @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
+ @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
+ @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
+ @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
+
+ private _firstX: number = 0;
+ private _firstY: number = 0;
+
+
+ // handle1PointerHoldStart = (e: React.TouchEvent): any => {
+ // this.onRadialMenu(e);
+ // const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
+ // this._firstX = pt.pageX;
+ // this._firstY = pt.pageY;
+ // e.stopPropagation();
+ // e.preventDefault();
+
+ // document.removeEventListener("touchmove", this.onTouch);
+ // document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.addEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+ // document.addEventListener("touchend", this.handle1PointerHoldEnd);
+ // }
+
+ // handle1PointerHoldMove = (e: TouchEvent): void => {
+ // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+ // if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
+ // this.handleRelease();
+ // }
+ // document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.addEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+ // document.addEventListener("touchend", this.handle1PointerHoldEnd);
+ // }
+
+ // handleRelease() {
+ // RadialMenu.Instance.closeMenu();
+ // document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+ // }
+
+ // handle1PointerHoldEnd = (e: TouchEvent): void => {
+ // RadialMenu.Instance.closeMenu();
+ // document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+ // }
+
+ // @action
+ // onRadialMenu = (e: React.TouchEvent): void => {
+ // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+
+ // RadialMenu.Instance.openMenu();
+
+ // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
+
+ // RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15);
+ // if (!SelectionManager.IsSelected(this, true)) {
+ // SelectionManager.SelectDoc(this, false);
+ // }
+ // e.stopPropagation();
+ // }
@action
componentDidMount() {
this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+ this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
+ this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
!this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
}
@@ -114,7 +187,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
componentDidUpdate() {
this._dropDisposer && this._dropDisposer();
+ this._gestureEventDisposer && this._gestureEventDisposer();
+ this.multiTouchDisposer && this.multiTouchDisposer();
this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+ this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
+ this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
}
@action
@@ -133,6 +210,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument;
dragData.applyAsTemplate = applyAsTemplate;
dragData.dragDivName = this.props.dragDivName;
+ this.props.Document.sourceContext = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
}
}
@@ -177,21 +255,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- onClick = async (e: React.MouseEvent) => {
+ onClick = undoBatch((e: React.MouseEvent | React.PointerEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
e.stopPropagation();
let preventDefault = true;
if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
const fullScreenAlias = Doc.MakeAlias(this.props.Document);
- if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) {
- fullScreenAlias.layoutKey = "layoutCustom";
+ if (StrCast(fullScreenAlias.layoutKey) !== "layout_custom" && fullScreenAlias.layout_custom !== undefined) {
+ fullScreenAlias.layoutKey = "layout_custom";
}
this.props.addDocTab(fullScreenAlias, undefined, "inTab");
SelectionManager.DeselectAll();
Doc.UnBrushDoc(this.props.Document);
} else if (this.onClickHandler && this.onClickHandler.script) {
- this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+ this.onClickHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log);
} else if (this.Document.type === DocumentType.BUTTON) {
ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY);
} else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script
@@ -206,7 +284,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
preventDefault && e.preventDefault();
}
- }
+ })
buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs);
@@ -236,37 +314,40 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- handle1PointerDown = (e: React.TouchEvent) => {
- if (!e.nativeEvent.cancelBubble) {
- const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+ handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ if (this.Document.onPointerDown) return;
+ const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+ console.log("down");
+ if (touch) {
this._downX = touch.clientX;
this._downY = touch.clientY;
- this._hitTemplateDrag = false;
- for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
- if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
- this._hitTemplateDrag = true;
+ if (!e.nativeEvent.cancelBubble) {
+ this._hitTemplateDrag = false;
+ for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+ if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+ this._hitTemplateDrag = true;
+ }
}
+ if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ e.stopPropagation();
}
- if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
- if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
}
}
- handle1PointerMove = (e: TouchEvent) => {
+ handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
if ((e as any).formattedHandled) { e.stopPropagation; return; }
if (e.cancelBubble && this.active) {
- document.removeEventListener("touchmove", this.onTouch);
+ this.removeMoveListeners();
}
else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
- const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+ const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
+ this.cleanUpInteractions();
this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
}
}
@@ -276,21 +357,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- handle2PointersDown = (e: React.TouchEvent) => {
+ handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
if (!e.nativeEvent.cancelBubble && !this.isSelected()) {
e.stopPropagation();
e.preventDefault();
- document.removeEventListener("touchmove", this.onTouch);
- document.addEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchend", this.onTouchEnd);
- document.addEventListener("touchend", this.onTouchEnd);
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
}
}
@action
- handle2PointersMove = (e: TouchEvent) => {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt1 = myTouches[0];
const pt2 = myTouches[1];
const oldPoint1 = this.prevPoints.get(pt1.identifier);
@@ -311,10 +392,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
const doc = PositionDocument(this.props.Document);
const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
- let nwidth = layoutDoc.nativeWidth || 0;
- let nheight = layoutDoc.nativeHeight || 0;
- const width = (layoutDoc.width || 0);
- const height = (layoutDoc.height || (nheight / nwidth * width));
+ let nwidth = layoutDoc._nativeWidth || 0;
+ let nheight = layoutDoc._nativeHeight || 0;
+ const width = (layoutDoc._width || 0);
+ const height = (layoutDoc._height || (nheight / nwidth * width));
const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling();
const actualdW = Math.max(width + (dW * scale), 20);
const actualdH = Math.max(height + (dH * scale), 20);
@@ -323,62 +404,59 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
layoutDoc.ignoreAspect = false;
- layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
- layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+
+ layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
+ layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
}
if (fixedAspect && (!nwidth || !nheight)) {
- layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
- layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
+ layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
}
if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
if (Math.abs(dW) > Math.abs(dH)) {
if (!fixedAspect) {
- layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+ layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0);
}
- layoutDoc.width = actualdW;
- if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
- else layoutDoc.height = actualdH;
+ layoutDoc._width = actualdW;
+ if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+ else layoutDoc._height = actualdH;
}
else {
if (!fixedAspect) {
- layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+ layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0);
}
- layoutDoc.height = actualdH;
- if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
- else layoutDoc.width = actualdW;
+ layoutDoc._height = actualdH;
+ if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+ else layoutDoc._width = actualdW;
}
} else {
- dW && (layoutDoc.width = actualdW);
- dH && (layoutDoc.height = actualdH);
- dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
+ dW && (layoutDoc._width = actualdW);
+ dH && (layoutDoc._height = actualdH);
+ dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
}
}
- // let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX))
- // this.props.Document.width = newWidth;
e.stopPropagation();
e.preventDefault();
}
}
onPointerDown = (e: React.PointerEvent): void => {
+ if (this.onPointerDownHandler && this.onPointerDownHandler.script) {
+ this.onPointerDownHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ return;
+ }
// console.log(e.button)
// console.log(e.nativeEvent)
// continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
- if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) {
+ if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
}
return;
}
- if ((!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart)) {
- // if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)))
- // // return if we're inking, and not selecting a button document
- // || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick)
- // // return if using pen or eraser
- // || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) {
- // return;
- // }
-
+ if (!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) {
this._downX = e.clientX;
this._downY = e.clientY;
this._hitTemplateDrag = false;
@@ -389,26 +467,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._hitTemplateDrag = true;
}
}
- if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+ if ((this.active || this.Document.onDragStart || this.onClickHandler) &&
+ !e.ctrlKey &&
+ (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
+ !this.Document.lockedPosition &&
+ !this.Document.inOverlay) {
+ e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+ }
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
+
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
}
}
onPointerMove = (e: PointerEvent): void => {
+
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
+ if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) return;
if (e.cancelBubble && this.active) {
document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
}
- else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.onClickHandler) && !this.Document.lockedPosition && !this.Document.inOverlay) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+ if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+ this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
}
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -417,54 +504,68 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerUp = (e: PointerEvent): void => {
+ if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ return;
+ }
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
this._lastTap = Date.now();
}
+ onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+ switch (ge.gesture) {
+ case GestureUtils.Gestures.Line:
+ ge.callbackFn && ge.callbackFn(this.props.Document);
+ e.stopPropagation();
+ break;
+ }
+ }
+
@undoBatch
- deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
+ deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
static makeNativeViewClicked = (doc: Doc) => {
- undoBatch(() => doc.layoutKey = "layout")();
+ undoBatch(() => Doc.setNativeView(doc))();
}
- static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>) => {
+ static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, name: string = "custom", docLayoutTemplate?: Doc) => {
const batch = UndoManager.StartBatch("CustomViewClicked");
- if (doc.layoutCustom === undefined) {
- const width = NumCast(doc.width);
- const height = NumCast(doc.height);
- const options = { title: "data", width, x: -width / 2, y: - height / 2, };
-
- let fieldTemplate: Doc;
- switch (doc.type) {
- case DocumentType.TEXT:
- fieldTemplate = Docs.Create.TextDocument(options);
- break;
- case DocumentType.PDF:
- fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
- break;
- case DocumentType.VID:
- fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
- break;
- case DocumentType.AUDIO:
- fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
- break;
- default:
- fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
+ const customName = "layout_" + name;
+ if (!StrCast(doc.title).endsWith(name)) doc.title = doc.title + "_" + name;
+ if (doc[customName] === undefined) {
+ const _width = NumCast(doc._width);
+ const _height = NumCast(doc._height);
+ const options = { title: "data", _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
+
+ const field = doc.data;
+ let fieldTemplate: Opt<Doc>;
+ if (field instanceof RichTextField || typeof (field) === "string") {
+ fieldTemplate = Docs.Create.TextDocument("", options);
+ } else if (field instanceof PdfField) {
+ fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
+ } else if (field instanceof VideoField) {
+ fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
+ } else if (field instanceof AudioField) {
+ fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
+ } else if (field instanceof ImageField) {
+ fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
}
- fieldTemplate.backgroundColor = doc.backgroundColor;
- fieldTemplate.heading = 1;
- fieldTemplate.autoHeight = true;
+ if (fieldTemplate) {
+ fieldTemplate.backgroundColor = doc.backgroundColor;
+ fieldTemplate.heading = 1;
+ fieldTemplate._autoHeight = true;
+ }
- const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) });
+ const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
- Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true);
- Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined);
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));
+ Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, customName, undefined);
} else {
- doc.layoutKey = "layoutCustom";
+ doc.layoutKey = customName;
}
batch.end();
}
@@ -503,7 +604,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
`Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
}
if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
- Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layoutCustom");
+ Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom", undefined);
e.stopPropagation();
}
if (de.complete.linkDragData) {
@@ -515,26 +616,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- @action
- onDrop = (e: React.DragEvent) => {
- const text = e.dataTransfer.getData("text/plain");
- if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
- const oldLayout = this.Document.layout || "";
- const layout = text.replace("{layout}", oldLayout);
- this.Document.layout = layout;
- e.stopPropagation();
- e.preventDefault();
- }
- }
-
@undoBatch
@action
freezeNativeDimensions = (): void => {
- this.layoutDoc.autoHeight = this.layoutDoc.autoHeight = false;
+ this.layoutDoc._autoHeight = false;
this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect;
- if (!this.layoutDoc.ignoreAspect && !this.layoutDoc.nativeWidth) {
- this.layoutDoc.nativeWidth = this.props.PanelWidth();
- this.layoutDoc.nativeHeight = this.props.PanelHeight();
+ if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) {
+ this.layoutDoc._nativeWidth = this.props.PanelWidth();
+ this.layoutDoc._nativeHeight = this.props.PanelHeight();
}
}
@@ -545,7 +634,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) {
const portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
DocServer.GetRefField(portalID).then(existingPortal => {
- const portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID });
+ const portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { _width: (this.layoutDoc._width || 0) + 10, _height: this.layoutDoc._height || 0, title: portalID });
DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link");
this.Document.isButton = true;
});
@@ -554,27 +643,27 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- setNarrativeView = (custom: boolean): void => {
- if (custom) {
- this.props.Document.layout_narrative = CollectionView.LayoutString("narrative");
- this.props.Document.nativeWidth = this.props.Document.nativeHeight = undefined;
- !this.props.Document.narrative && (Doc.GetProto(this.props.Document).narrative = new List<Doc>([]));
- this.props.Document.viewType = CollectionViewType.Stacking;
- this.props.Document.layoutKey = "layout_narrative";
- } else {
- DocumentView.makeNativeViewClicked(this.props.Document)
- }
- }
-
- @undoBatch
- @action
- setCustomView = (custom: boolean): void => {
- if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
- } else {
- custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document);
+ setCustomView =
+ (custom: boolean, layout: string): void => {
+ // if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
+ // Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
+ // } else
+ if (custom) {
+ DocumentView.makeNativeViewClicked(this.props.Document);
+
+ let foundLayout: Opt<Doc>;
+ DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data)?.concat([Cast(Doc.UserDoc().iconView, Doc, null)]).
+ map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc).forEach(tempDoc => {
+ if (StrCast(tempDoc.title) === layout) {
+ foundLayout = tempDoc;
+ }
+ })
+ DocumentView.
+ makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout);
+ } else {
+ DocumentView.makeNativeViewClicked(this.props.Document);
+ }
}
- }
@undoBatch
@action
@@ -618,7 +707,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" });
subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" });
subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });
- subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
+ subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" });
+ subitems.push({ description: "Open Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
@@ -643,21 +733,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const existing = ContextMenu.Instance.findByDescription("Layout...");
const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });
- if (this.props.DataDoc) {
- layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" });
- }
- layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
- layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" });
- layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
+ layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
+
+ layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+ layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+ layoutItems.push({ description: this.Document.ignoreAspect || !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
- if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) {
- layoutItems.push({ description: "Use Custom Layout", event: () => DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- } else {
- layoutItems.push({ description: "Use Native Layout", event: () => DocumentView.makeNativeViewClicked(this.props.Document), icon: "concierge-bell" });
- }
!existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
const more = ContextMenu.Instance.findByDescription("More...");
@@ -676,8 +760,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
- moreItems.push({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
- moreItems.push({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
moreItems.push({
description: "Download document", icon: "download", event: async () =>
console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
@@ -702,13 +784,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
DocServer.setFieldWriteMode("x", mode1);
DocServer.setFieldWriteMode("y", mode1);
- DocServer.setFieldWriteMode("width", mode1);
- DocServer.setFieldWriteMode("height", mode1);
+ DocServer.setFieldWriteMode("_width", mode1);
+ DocServer.setFieldWriteMode("_height", mode1);
- DocServer.setFieldWriteMode("panX", mode2);
- DocServer.setFieldWriteMode("panY", mode2);
+ DocServer.setFieldWriteMode("_panX", mode2);
+ DocServer.setFieldWriteMode("_panY", mode2);
DocServer.setFieldWriteMode("scale", mode2);
- DocServer.setFieldWriteMode("viewType", mode2);
+ DocServer.setFieldWriteMode("_viewType", mode2);
};
const aclsMenu: ContextMenuProps[] = [];
aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
@@ -735,6 +817,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
});
const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc().LibraryBtn as Doc).sourcePanel as Doc) ? "" : d.title), "");
+ cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" });
cm.addItem({
description: `path: ${path}`, event: () => {
this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
@@ -753,14 +836,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
chromeHeight = () => {
- const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
- const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
- const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.layoutDoc.showTitleHover);
+ const showTitle = StrCast(this.layoutDoc.showTitle);
+ const showTitleHover = StrCast(this.layoutDoc.showTitleHover);
return (showTitle && !showTitleHover ? 0 : 0) + 1;
}
- @computed get finalLayoutKey() { return this.props.layoutKey || StrCast(this.props.Document.layoutKey, "layout"); }
- childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+ @computed get finalLayoutKey() {
+ const { layoutKey } = this.props;
+ if (typeof layoutKey === "string") {
+ return layoutKey;
+ }
+ const fallback = Cast(this.props.Document.layoutKey, "string");
+ return typeof fallback === "string" ? fallback : "layout";
+ }
+ childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
@computed get contents() {
TraceMobx();
return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
@@ -774,9 +863,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moveDocument={this.props.moveDocument}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
renderDepth={this.props.renderDepth}
- showOverlays={this.props.showOverlays}
ContentScaling={this.childScaling}
- ruleProvider={this.props.ruleProvider}
PanelWidth={this.props.PanelWidth}
PanelHeight={this.props.PanelHeight}
focus={this.props.focus}
@@ -808,11 +895,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get innards() {
TraceMobx();
- const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
- const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.getLayoutPropStr("showTitle"));
- const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.getLayoutPropStr("showTitleHover"));
- const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
- const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;
+ const showTitle = StrCast(this.getLayoutPropStr("showTitle"));
+ const showTitleHover = StrCast(this.getLayoutPropStr("showTitleHover"));
+ const showCaption = this.getLayoutPropStr("showCaption");
+ const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined;
const searchHighlight = (!this.Document.searchFields ? (null) :
<div className="documentView-searchHighlight">
{this.Document.searchFields}
@@ -828,12 +914,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const titleView = (!showTitle ? (null) :
<div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
position: showTextTitle ? "relative" : "absolute",
- pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all",
+ pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : "all",
}}>
<EditableView ref={this._titleRef}
- contents={(this.props.DataDoc || this.props.Document)[showTitle]}
+ contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
display={"block"} height={72} fontSize={12}
- GetValue={() => StrCast((this.props.DataDoc || this.props.Document)[showTitle])}
+ GetValue={() => (this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)}
/>
</div>);
@@ -868,28 +954,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
render() {
if (!(this.props.Document instanceof Doc)) return (null);
- const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
- const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
const colorSet = this.setsLayoutProp("backgroundColor");
const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
const backgroundColor = (clusterCol && !colorSet) ?
this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
- ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
+ StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
- const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
+ const borderRounding = this.getLayoutPropStr("borderRounding");
const localScale = fullDegree;
- const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
- const animheight = animDims ? animDims[1] : "100%";
- const animwidth = animDims ? animDims[0] : "100%";
-
const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
- let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
+ let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
- onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+ onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
style={{
transition: this.Document.isAnimating ? ".5s linear" : StrCast(this.Document.transition),
@@ -897,14 +977,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
color: StrCast(this.Document.color),
outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
- background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
- width: animwidth,
- height: animheight,
+ boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
+ background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor,
+ width: "100%",
+ height: "100%",
opacity: this.Document.opacity
- }} onTouchStart={this.onTouchStart}>
+ }}>
{this.innards}
</div>;
}
}
-Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; }); \ No newline at end of file
+Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layout_custom" : "layout"; }); \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c56fde186..dbbb76f83 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -27,7 +27,6 @@ export interface FieldViewProps {
fitToBox?: boolean;
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
- ruleProvider: Doc | undefined;
Document: Doc;
DataDoc?: Doc;
LibraryPath: Doc[];
@@ -54,7 +53,7 @@ export interface FieldViewProps {
@observer
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string) {
- return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"dada} />"
+ return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
}
@computed
@@ -70,12 +69,12 @@ export class FieldView extends React.Component<FieldViewProps> {
// if (typeof field === "string") {
// return <p>{field}</p>;
// }
- else if (field instanceof RichTextField) {
- return <FormattedTextBox {...this.props} />;
- }
- else if (field instanceof ImageField) {
- return <ImageBox {...this.props} />;
- }
+ // else if (field instanceof RichTextField) {
+ // return <FormattedTextBox {...this.props} />;
+ // }
+ // else if (field instanceof ImageField) {
+ // return <ImageBox {...this.props} />;
+ // }
// else if (field instaceof PresBox) {
// return <PresBox {...this.props} />;
// }
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 2433251b3..a191ac4f4 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -5,10 +5,11 @@ import { createSchema, makeInterface } from '../../../new_fields/Schema';
import { DocComponent } from '../DocComponent';
import './FontIconBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
-import { StrCast } from '../../../new_fields/Types';
+import { StrCast, Cast } from '../../../new_fields/Types';
import { Utils } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
import { Doc } from '../../../new_fields/Doc';
+import { ContextMenu } from '../ContextMenu';
const FontIconSchema = createSchema({
icon: "string"
});
@@ -32,13 +33,25 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
}
}, { fireImmediately: true });
}
+
+ showTemplate = (): void => {
+ const dragFactory = Cast(this.props.Document.dragFactory, Doc, null);
+ dragFactory && this.props.addDocTab(dragFactory, undefined, "onRight");
+ }
+
+ specificContextMenu = (): void => {
+ const cm = ContextMenu.Instance;
+ cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
+ }
+
componentWillUnmount() {
- this._backgroundReaction && this._backgroundReaction();
+ this._backgroundReaction?.();
}
+
render() {
const referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
const referenceLayout = Doc.Layout(referenceDoc);
- return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref}
+ return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
style={{
background: StrCast(referenceLayout.backgroundColor),
boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 60842bcb0..9370d3745 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace, _allowStateChangesInsideComputed } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
@@ -12,7 +12,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym, DataSym, Field } from "../../../new_fields/Doc";
import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from '../../../new_fields/RichTextUtils';
@@ -27,12 +27,10 @@ import { DictationManager } from '../../util/DictationManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
-import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema";
+import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView, DashFieldView } from "../../util/RichTextSchema";
import { SelectionManager } from "../../util/SelectionManager";
-import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
-import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { DocAnnotatableComponent, DocAnnotatableProps } from "../DocComponent";
import { DocumentButtonBar } from '../DocumentButtonBar';
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from "./FieldView";
@@ -48,17 +46,12 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { InkTool } from '../../../new_fields/InkField';
import { TraceMobx } from '../../../new_fields/util';
import RichTextMenu from '../../util/RichTextMenu';
-import { DocumentDecorations } from '../DocumentDecorations';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
export interface FormattedTextBoxProps {
hideOnLeave?: boolean;
- height?: string;
- color?: string;
- outer_div?: (domminus: HTMLElement) => void;
- firstinstance?: boolean;
}
const richTextSchema = createSchema({
@@ -77,7 +70,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
- public static ToolTipTextMenu: TooltipTextMenu | undefined = undefined;
public ProseRef?: HTMLDivElement;
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -92,16 +84,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
private _reactionDisposer: Opt<IReactionDisposer>;
private _heightReactionDisposer: Opt<IReactionDisposer>;
- private _rulesReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
private _pullReactionDisposer: Opt<IReactionDisposer>;
private _pushReactionDisposer: Opt<IReactionDisposer>;
private _buttonBarReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
- @observable private _ruleFontSize = 0;
- @observable private _ruleFontFamily = "Arial";
- @observable private _fontAlign = "";
@observable private _entered = false;
public static FocusedBox: FormattedTextBox | undefined;
@@ -127,10 +115,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
return "";
}
- public static getToolTip(ev: EditorView) {
- return this.ToolTipTextMenu ? this.ToolTipTextMenu : this.ToolTipTextMenu = new TooltipTextMenu(ev);
- }
-
@undoBatch
public setFontColor(color: string) {
const view = this._editorView!;
@@ -160,7 +144,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
DocServer.GetRefField(value).then(doc => {
DocServer.GetRefField(id).then(linkDoc => {
- this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+ this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id);
@@ -201,9 +185,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
this._applyingChange = true;
- this.extensionDoc && !this.extensionDoc.lastModified && (this.extensionDoc.backgroundColor = "lightGray");
- this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ if (!this.props.Document._textTemplate || Doc.GetProto(this.props.Document) === this.dataDoc) {
+ this.dataDoc[this.props.fieldKey + "-lastModified"] && (this.dataDoc[this.props.fieldKey + "-backgroundColor"] = "lightGray");
+ this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ }
this._applyingChange = false;
this.updateTitle();
this.tryUpdateHeight();
@@ -271,15 +257,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
newLayout = Doc.MakeDelegate(draggedDoc);
newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${this.props.fieldKey}'}`);
}
- this.Document.layoutCustom = newLayout;
- this.Document.layoutKey = "layoutCustom";
+ this.Document.layout_custom = newLayout;
+ this.Document.layoutKey = "layout_custom";
e.stopPropagation();
// embed document when dragging with a userDropAction or an embedDoc flag set
} else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
const target = de.complete.docDragData.droppedDocuments[0];
// const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
// if (link) {
- target.fitToBox = true;
+ target._fitToBox = true;
const node = schema.nodes.dashDoc.create({
width: target[WidthSym](), height: target[HeightSym](),
title: "dashDoc", docid: target[Id],
@@ -393,7 +379,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.toggleSidebar(); }, icon: "expand-arrows-alt" });
+ funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar }, icon: "expand-arrows-alt" });
funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
funcs.push({
@@ -485,11 +471,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
schema,
plugins: [
inputRules(inpRules),
- this.tooltipTextMenuPlugin(),
+ this.richTextMenuPlugin(),
history(),
keymap(this._keymap),
keymap(baseKeymap),
- // this.tooltipLinkingMenuPlugin(),
new Plugin({
props: {
attributes: { class: "ProseMirror-example-setup-style" }
@@ -513,7 +498,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this._reactionDisposer = reaction(
() => {
- const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
+ const field = Cast(this.props.Document._textTemplate || this.dataDoc[this.props.fieldKey], RichTextField);
return field ? field.Data : RichTextUtils.Initialize();
},
incomingValue => {
@@ -547,46 +532,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
);
this._heightReactionDisposer = reaction(
- () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight],
+ () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
() => this.tryUpdateHeight()
);
- this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
+ this.setupEditor(this.config, this.props.fieldKey);
this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch,
search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
{ fireImmediately: true });
- this._rulesReactionDisposer = reaction(() => {
- const ruleProvider = this.props.ruleProvider;
- const heading = NumCast(this.layoutDoc.heading);
- if (ruleProvider instanceof Doc) {
- return {
- align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
- font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"),
- size: NumCast(ruleProvider["ruleSize_" + heading], 13)
- };
- }
- return undefined;
- },
- action((rules: any) => {
- this._ruleFontFamily = rules ? rules.font : "Arial";
- this._ruleFontSize = rules ? rules.size : 0;
- rules && setTimeout(() => {
- const view = this._editorView!;
- if (this.ProseRef) {
- const n = new NodeSelection(view.state.doc.resolve(0));
- if (this._editorView!.state.doc.textContent === "") {
- view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))).
- replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true));
- } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) {
- view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }));
- }
- this.tryUpdateHeight();
- }
- }, 0);
- }), { fireImmediately: true }
- );
this._scrollToRegionReactionDisposer = reaction(
() => StrCast(this.layoutDoc.scrollToLinkID),
async (scrollToLinkID) => {
@@ -743,11 +698,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
setTimeout(async () => {
- const extension = Doc.fieldExtensionDoc(pdfDoc, "data");
- if (extension) {
- const targetAnnotations = await DocListCastAsync(extension.annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations
- targetAnnotations && targetAnnotations.push(pdfRegion);
- }
+ const targetField = Doc.LayoutFieldKey(pdfDoc);
+ const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
+ targetAnnotations?.push(pdfRegion);
});
const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
@@ -785,28 +738,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
}
- private setupEditor(config: any, doc: Doc, fieldKey: string) {
- const field = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
- let startup = StrCast(doc.documentText);
- startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : "";
- if (!field && doc) {
- const text = StrCast(doc[fieldKey]);
- if (text) {
- startup = text;
- } else if (Cast(doc[fieldKey], "number")) {
- startup = NumCast(doc[fieldKey], 99).toString();
- }
- }
+ private setupEditor(config: any, fieldKey: string) {
+ const rtfField = Cast(this.props.Document._textTemplate || this.dataDoc[fieldKey], RichTextField);
if (this.ProseRef) {
const self = this;
- this._editorView && this._editorView.destroy();
+ this._editorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
- state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
+ state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
handleScrollToSelection: (editorView) => {
const ref = editorView.domAtPos(editorView.state.selection.from);
let refNode = ref.node as any;
while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
- const r1 = refNode && refNode.getBoundingClientRect();
+ const r1 = refNode?.getBoundingClientRect();
const r3 = self._ref.current!.getBoundingClientRect();
if (r1.top < r3.top || r1.top > r3.bottom) {
r1 && (self._scrollRef.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
@@ -816,6 +759,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
+ dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
@@ -826,9 +770,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
handlePaste: this.handlePaste,
});
this._editorView.state.schema.Document = this.props.Document;
- if (startup && this._editorView) {
- Doc.GetProto(doc).documentText = undefined;
- this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
+ const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
+ if (startupText) {
+ this._editorView.dispatch(this._editorView.state.tr.insertText(startupText));
}
}
@@ -837,8 +781,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
}
- const rtf = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
- (selectOnLoad || (rtf && !rtf.Text)) && this._editorView!.focus();
+ (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })];
}
@@ -857,7 +800,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
componentWillUnmount() {
this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer();
- this._rulesReactionDisposer && this._rulesReactionDisposer();
this._reactionDisposer && this._reactionDisposer();
this._proxyReactionDisposer && this._proxyReactionDisposer();
this._pushReactionDisposer && this._pushReactionDisposer();
@@ -1038,25 +980,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
}
- tooltipTextMenuPlugin() {
+ richTextMenuPlugin() {
const self = FormattedTextBox;
return new Plugin({
view(newView) {
- // return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView);
RichTextMenu.Instance.changeView(newView);
return RichTextMenu.Instance;
}
});
}
- tooltipLinkingMenuPlugin() {
- const myprops = this.props;
- return new Plugin({
- view(_editorView) {
- return new TooltipLinkingMenu(_editorView, myprops);
- }
- });
- }
onBlur = (e: any) => {
//DictationManager.Controls.stop(false);
if (this._undoTyping) {
@@ -1111,47 +1044,46 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
@action
tryUpdateHeight(limitHeight?: number) {
let scrollHeight = this._ref.current?.scrollHeight;
- if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
+ if (this.layoutDoc._autoHeight && scrollHeight &&
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
if (limitHeight && scrollHeight > limitHeight) {
scrollHeight = limitHeight;
this.layoutDoc.limitHeight = undefined;
- this.layoutDoc.autoHeight = false;
+ this.layoutDoc._autoHeight = false;
}
- const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
- const dh = NumCast(this.layoutDoc.height, 0);
+ const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
+ const dh = NumCast(this.layoutDoc._height, 0);
const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
- this.layoutDoc.height = newHeight;
- this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
+ this.layoutDoc._height = newHeight;
+ this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
}
}
}
@computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
- @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
- @computed get annotationsKey() { return "annotations"; }
+ sidebarWidth = () => { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
+ sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
+ @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
render() {
TraceMobx();
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
- // TODO: ftong --> update from dash in richtextmenu
- RichTextMenu.Instance.updateFromDash(this._editorView!, undefined, this.props);
- // FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+ this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
} else if (FormattedTextBoxComment.textBox === this) {
FormattedTextBoxComment.Hide();
}
return (
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
- height: this.layoutDoc.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
+ height: this.layoutDoc._autoHeight ? "max-content" : undefined,
background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
- color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
+ color: this.props.hideOnLeave ? "white" : "inherit",
pointerEvents: interactive ? "none" : "all",
- fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13),
- fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
+ fontSize: NumCast(this.layoutDoc.fontSize, 13),
+ fontFamily: StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyPress}
@@ -1169,14 +1101,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
<div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} ref={this._scrollRef}>
<div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
</div>
- {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
+ {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
<div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
<div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
- style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
+ style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
- PanelWidth={() => this.sidebarWidth}
- annotationsKey={this.annotationsKey}
+ PanelWidth={this.sidebarWidth}
+ annotationsKey={this.annotationKey}
isAnnotationOverlay={false}
focus={this.props.focus}
isSelected={this.props.isSelected}
@@ -1186,10 +1118,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={(doc: Doc) => { doc.hideSidebar = true; return this.addDocument(doc); }}
+ addDocument={this.addDocument}
CollectionView={undefined}
- ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)}
- ruleProvider={undefined}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index f7a530790..fda3e3285 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -86,7 +86,7 @@ export class FormattedTextBoxComment {
DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
(doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight"));
} else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
- textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight");
+ textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), undefined, "onRight");
}
keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
@@ -185,7 +185,6 @@ export class FormattedTextBoxComment {
active={returnFalse}
addDocument={returnFalse}
removeDocument={returnFalse}
- ruleProvider={undefined}
addDocTab={returnFalse}
pinToPres={returnFalse}
dontRegisterView={true}
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 9462ff024..172338eb6 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -79,7 +79,7 @@ export class IconBox extends React.Component<FieldViewProps> {
<Measure offset onResize={(r) => runInAction(() => {
if (r.offset!.width || this.props.Document.hideLabel) {
this.props.Document.iconWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
- if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.iconWidth;
+ if (this.props.Document._height === Number(MINIMIZED_ICON_SIZE)) this.props.Document._width = this.props.Document.iconWidth;
}
})}>
{({ measureRef }) =>
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index cf5d999a7..7bbf4a368 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,12 +1,14 @@
-.imageBox, .imageBox-dragging{
+.imageBox,
+.imageBox-dragging {
pointer-events: all;
border-radius: inherit;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: absolute;
transform-origin: top left;
+
.imageBox-fader {
- pointer-events: all;
+ pointer-events: inherit;
}
}
@@ -16,6 +18,14 @@
}
}
+#upload-icon {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 20px;
+ height: 20px;
+}
+
.imageBox-cont {
padding: 0vw;
position: absolute;
@@ -24,12 +34,12 @@
height: 100%;
max-width: 100%;
max-height: 100%;
- pointer-events: none;
- background:transparent;
+ pointer-events: inherit;
+ background: transparent;
+
img {
height: auto;
width: 100%;
- pointer-events: all;
}
}
@@ -55,9 +65,10 @@
padding: 3px;
background: white;
cursor: pointer;
- opacity:0.15;
+ opacity: 0.15;
}
-#google-photos:hover{
+
+#google-photos:hover {
opacity: 1;
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 634555012..896ac1685 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -4,11 +4,11 @@ import { faAsterisk, faFileAudio, faImage, faPaintBrush } from '@fortawesome/fre
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction, trace } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym, DataSym } from '../../../new_fields/Doc';
import { List } from '../../../new_fields/List';
import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
import { ComputedField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast } from '../../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { AudioField, ImageField } from '../../../new_fields/URLField';
import { Utils, returnOne, emptyFunction } from '../../../Utils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
@@ -24,9 +24,12 @@ import "./ImageBox.scss";
import React = require("react");
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id } from '../../../new_fields/FieldSymbols';
+import { Id, Copy } from '../../../new_fields/FieldSymbols';
import { TraceMobx } from '../../../new_fields/util';
import { SelectionManager } from '../../util/SelectionManager';
+import { cache } from 'sharp';
+import { ObjectField } from '../../../new_fields/ObjectField';
+import { Networking } from '../../Network';
const requestImageSize = require('../../util/request-image-size');
const path = require('path');
const { Howl } = require('howler');
@@ -39,7 +42,6 @@ library.add(faFileAudio, faAsterisk);
export const pageSchema = createSchema({
curPage: "number",
fitWidth: "boolean",
- rotation: "number",
googlePhotosUrl: "string",
googlePhotosTags: "string"
});
@@ -56,13 +58,22 @@ declare class MediaRecorder {
type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>;
const ImageDocument = makeInterface(pageSchema, documentSchema);
+const uploadIcons = {
+ idle: "downarrow.png",
+ loading: "loading.gif",
+ success: "greencheck.png",
+ failure: "redx.png"
+};
+
@observer
export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
+ protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _dropDisposer?: DragManager.DragDropDisposer;
@observable private _audioState = 0;
@observable static _showControls: boolean;
+ @observable uploadIcon = uploadIcons.idle;
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer && this._dropDisposer();
@@ -73,14 +84,21 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
- if (de.altKey && de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0].data instanceof ImageField) {
- Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.complete.docDragData.draggedDocuments[0].data.url);
- e.stopPropagation();
+ if (de.metaKey) {
+ de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
+ Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-alternates", drop);
+ e.stopPropagation();
+ }));
+ } else if (de.altKey || !this.dataDoc[this.props.fieldKey]) {
+ const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
+ const targetField = Doc.LayoutFieldKey(layoutDoc);
+ if (layoutDoc?.[DataSym][targetField] instanceof ImageField) {
+ this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(layoutDoc[DataSym][targetField] as ImageField);
+ this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(layoutDoc[DataSym][targetField + "-nativeWidth"]);
+ this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(layoutDoc[DataSym][targetField + "-nativeHeight"]);
+ e.stopPropagation();
+ }
}
- de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
- this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop);
- e.stopPropagation();
- }));
}
}
@@ -88,8 +106,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
let gumStream: any;
let recorder: any;
const self = this;
- const extensionDoc = this.extensionDoc;
- extensionDoc && navigator.mediaDevices.getUserMedia({
+ navigator.mediaDevices.getUserMedia({
audio: true
}).then(function (stream) {
gumStream = stream;
@@ -97,18 +114,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
recorder.ondataavailable = async function (e: any) {
const formData = new FormData();
formData.append("file", e.data);
- const res = await fetch(Utils.prepend("/upload"), {
+ const res = await fetch(Utils.prepend("/uploadFormData"), {
method: 'POST',
body: formData
});
const files = await res.json();
const url = Utils.prepend(files[0].path);
// upload to server with known URL
- const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", width: 200, height: 32 });
+ const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 });
audioDoc.treeViewExpandedView = "layout";
- const audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc));
+ const audioAnnos = Cast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"], listSpec(Doc));
if (audioAnnos === undefined) {
- extensionDoc.audioAnnotations = new List([audioDoc]);
+ this.dataDoc[this.props.fieldKey + "-audioAnnotations"] = new List([audioDoc]);
} else {
audioAnnos.push(audioDoc);
}
@@ -125,15 +142,15 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
@undoBatch
rotate = action(() => {
- const nw = this.Document.nativeWidth;
- const nh = this.Document.nativeHeight;
- const w = this.Document.width;
- const h = this.Document.height;
- this.Document.rotation = ((this.Document.rotation || 0) + 90) % 360;
- this.Document.nativeWidth = nh;
- this.Document.nativeHeight = nw;
- this.Document.width = h;
- this.Document.height = w;
+ const nw = NumCast(this.Document[this.props.fieldKey + "-nativeWidth"]);
+ const nh = NumCast(this.Document[this.props.fieldKey + "-nativeHeight"]);
+ const w = this.Document._width;
+ const h = this.Document._height;
+ this.dataDoc[this.props.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) + 90) % 360;
+ this.dataDoc[this.props.fieldKey + "-nativeWidth"] = nh;
+ this.dataDoc[this.props.fieldKey + "-nativeHeight"] = nw;
+ this.Document._width = h;
+ this.Document._height = w;
});
specificContextMenu = (e: React.MouseEvent): void => {
@@ -159,7 +176,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>());
return faceDocs;
};
- this.url && this.extensionDoc && CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["faces"], this.url, Service.Face, converter);
+ this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-faces"], this.url, Service.Face, converter);
}
generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
@@ -171,12 +188,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
const sanitized = tag.name.replace(" ", "_");
tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
});
- this.extensionDoc && (this.extensionDoc.generatedTags = tagsList);
+ this.dataDoc[this.props.fieldKey + "-generatedTags"] = tagsList;
tagDoc.title = "Generated Tags Doc";
tagDoc.confidence = threshold;
return tagDoc;
};
- this.url && this.extensionDoc && CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["generatedTagsDoc"], this.url, Service.ComputerVision, converter);
+ this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter);
}
@computed private get url() {
@@ -206,39 +223,54 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
if (this._curSuffix === "_m") this._mediumRetryCount++;
if (this._curSuffix === "_l") this._largeRetryCount++;
}
- @action onError = () => {
+ @action onError = (error: any) => {
const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
if (timeout < 10) {
- setTimeout(this.retryPath, Math.min(10000, timeout * 5));
+ // setTimeout(this.retryPath, 500);
+ }
+ const original = StrCast(this.dataDoc.originalUrl);
+ if (error.type === "error" && original) {
+ this.dataDoc[this.props.fieldKey] = new ImageField(original);
}
}
_curSuffix = "_m";
- _resized = "";
resize = (imgPath: string) => {
- requestImageSize(imgPath)
- .then((size: any) => {
- const rotation = NumCast(this.dataDoc.rotation) % 180;
- const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
- const aspect = realsize.height / realsize.width;
- if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) {
- setTimeout(action(() => {
- if (this.pathInfos.srcpath === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) {
- this._resized = imgPath;
- this.Document.height = this.Document[WidthSym]() * aspect;
- this.Document.nativeHeight = realsize.height;
- this.Document.nativeWidth = realsize.width;
- }
- }), 0);
- } else this._resized = imgPath;
+ const cachedNativeSize = {
+ width: NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]),
+ height: NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"])
+ };
+ const cachedImgPath = this.dataDoc[this.props.fieldKey + "-imgPath"];
+ if (!cachedNativeSize.width || !cachedNativeSize.height || imgPath !== cachedImgPath) {
+ (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) && requestImageSize(imgPath).then((inquiredSize: any) => {
+ const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180;
+ const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize;
+ const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
+ const docAspect = this.Document[HeightSym]() / this.Document[WidthSym]();
+ setTimeout(action(() => {
+ if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
+ this.Document._height = this.Document[WidthSym]() * rotatedAspect;
+ this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width;
+ this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height;
+ }
+ this.dataDoc[this.props.fieldKey + "-imgPath"] = imgPath;
+ }), 0);
})
- .catch((err: any) => console.log(err));
+ .catch((err: any) => console.log(err));
+ } else if (this.Document._nativeWidth !== cachedNativeSize.width || this.Document._nativeHeight !== cachedNativeSize.height) {
+ !(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc) && setTimeout(() => {
+ if (!(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc)) {
+ this.Document._nativeWidth = cachedNativeSize.width;
+ this.Document._nativeHeight = cachedNativeSize.height;
+ }
+ }, 0);
+ }
}
@action
onPointerEnter = () => {
const self = this;
- const audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations);
+ const audioAnnos = DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]);
if (audioAnnos && audioAnnos.length && this._audioState === 0) {
const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
anno.data instanceof AudioField && new Howl({
@@ -271,45 +303,78 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
}
+ @computed
+ private get considerDownloadIcon() {
+ const data = this.dataDoc[this.props.fieldKey];
+ if (!(data instanceof ImageField)) {
+ return (null);
+ }
+ const primary = data.url.href;
+ if (primary.includes(window.location.origin)) {
+ return (null);
+ }
+ return (
+ <img
+ id={"upload-icon"}
+ src={`/assets/${this.uploadIcon}`}
+ onClick={async () => {
+ const { dataDoc } = this;
+ const { success, failure, idle, loading } = uploadIcons;
+ runInAction(() => this.uploadIcon = loading);
+ const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
+ dataDoc.originalUrl = primary;
+ let succeeded = true;
+ let data: ImageField | undefined;
+ try {
+ data = new ImageField(Utils.prepend(clientAccessPath));
+ } catch {
+ succeeded = false;
+ }
+ runInAction(() => this.uploadIcon = succeeded ? success : failure);
+ setTimeout(action(() => {
+ this.uploadIcon = idle;
+ if (data) {
+ dataDoc[this.props.fieldKey] = data;
+ }
+ }), 2000);
+ }}
+ />
+ );
+ }
+
@computed get nativeSize() {
const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
- const nativeWidth = (this.Document.nativeWidth || pw);
- const nativeHeight = (this.Document.nativeHeight || 1);
+ const nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], pw);
+ const nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], 1);
return { nativeWidth, nativeHeight };
}
- @computed get pathInfos() {
- const extensionDoc = this.extensionDoc!;
- const { nativeWidth, nativeHeight } = this.nativeSize;
- let paths = [[Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"), nativeWidth / nativeHeight]];
+ @computed get paths() {
+ let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
// this._curSuffix = "";
// if (w > 20) {
- const alts = DocListCast(extensionDoc.Alternates);
- const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => [this.choosePath((doc.data as ImageField).url), doc[WidthSym]() / doc[HeightSym]()]);
+ const alts = DocListCast(this.dataDoc[this.props.fieldKey + "-alternates"]);
+ const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url));
const field = this.dataDoc[this.props.fieldKey];
// if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
// else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m";
// else if (this._largeRetryCount < 10) this._curSuffix = "_l";
- if (field instanceof ImageField) paths = [[this.choosePath(field.url), nativeWidth / nativeHeight]];
+ if (field instanceof ImageField) paths = [this.choosePath(field.url)];
paths.push(...altpaths);
- const srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][0] as string;
- const srcaspect = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][1] as number;
- const fadepath = paths[Math.min(paths.length - 1, 1)][0] as string;
- return { srcpath, srcaspect, fadepath };
+ return paths;
}
@computed get content() {
TraceMobx();
- const extensionDoc = this.extensionDoc;
- if (!extensionDoc) return (null);
- const { srcpath, srcaspect, fadepath } = this.pathInfos;
+ const srcpath = this.paths[NumCast(this.props.Document.curPage, 0)];
+ const fadepath = this.paths[Math.min(1, this.paths.length - 1)];
const { nativeWidth, nativeHeight } = this.nativeSize;
- const rotation = NumCast(this.Document.rotation, 0);
+ const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]);
const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
- !this.Document.ignoreAspect && this._resized !== srcpath && this.resize(srcpath);
+ !this.Document.ignoreAspect && this.resize(srcpath);
return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
<div className="imageBox-fader" >
@@ -319,7 +384,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
width={nativeWidth}
ref={this._imgRef}
onError={this.onError} />
- {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker" style={{ width: nativeWidth, height: nativeWidth / srcaspect }}>
+ {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
<img className="imageBox-fadeaway"
key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
src={fadepath}
@@ -334,10 +399,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
>
<FontAwesomeIcon className="imageBox-audioFont"
- style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
+ style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
+ icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
</div>
+ {this.considerDownloadIcon}
{this.considerGooglePhotosLink()}
- <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
+ <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
</div>;
}
@@ -349,12 +416,13 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
style={{
transform: `scale(${this.props.ContentScaling()})`,
width: `${100 / this.props.ContentScaling()}%`,
- height: `${100 / this.props.ContentScaling()}%`
+ height: `${100 / this.props.ContentScaling()}%`,
+ pointerEvents: this.props.Document.isBackground ? "none" : undefined
}} >
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationsKey}
+ annotationsKey={this.annotationKey}
isAnnotationOverlay={true}
focus={this.props.focus}
isSelected={this.props.isSelected}
@@ -367,7 +435,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
addDocument={this.addDocument}
CollectionView={undefined}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- ruleProvider={undefined}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 234a6a9d3..7aad6f90e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -179,7 +179,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
getTemplate = async () => {
- const parent = Docs.Create.StackingDocument([], { width: 800, height: 800, title: "Template" });
+ const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template" });
parent.singleColumn = false;
parent.columnWidth = 100;
for (const row of this.rows.filter(row => row.isChecked)) {
@@ -200,17 +200,17 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
if (!fieldTemplate) {
return;
}
- const previousViewType = fieldTemplate.viewType;
+ const previousViewType = fieldTemplate._viewType;
Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc));
- previousViewType && (fieldTemplate.viewType = previousViewType);
+ previousViewType && (fieldTemplate._viewType = previousViewType);
Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
}
inferType = async (data: FieldResult, metaKey: string) => {
- const options = { width: 300, height: 300, title: metaKey };
+ const options = { _width: 300, _height: 300, title: metaKey };
if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
- return Docs.Create.TextDocument(options);
+ return Docs.Create.TextDocument("", options);
} else if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 91f8bb3b0..e6b512adf 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
if (value instanceof Doc) {
e.stopPropagation();
e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" });
ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
}
}
@@ -58,7 +58,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
LibraryPath: [],
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
- ruleProvider: undefined,
fieldKey: this.props.keyName,
isSelected: returnFalse,
select: emptyFunction,
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8370df6ba..e1c5fd27f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -50,9 +50,9 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
constructor(props: any) {
super(props);
this._initialScale = this.props.ScreenToLocalTransform().Scale;
- const nw = this.Document.nativeWidth = NumCast(this.extensionDocSync.nativeWidth, NumCast(this.Document.nativeWidth, 927));
- const nh = this.Document.nativeHeight = NumCast(this.extensionDocSync.nativeHeight, NumCast(this.Document.nativeHeight, 1200));
- !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
+ const nw = this.Document._nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], NumCast(this.Document._nativeWidth, 927));
+ const nh = this.Document._nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], NumCast(this.Document._nativeHeight, 1200));
+ !this.Document._fitWidth && !this.Document.ignoreAspect && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
const backup = "oldPath";
const { Document } = this.props;
@@ -90,10 +90,10 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
}
loaded = (nw: number, nh: number, np: number) => {
- this.extensionDocSync.numPages = np;
- this.extensionDocSync.nativeWidth = this.Document.nativeWidth = nw * 96 / 72;
- this.extensionDocSync.nativeHeight = this.Document.nativeHeight = nh * 96 / 72;
- !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
+ this.dataDoc[this.props.fieldKey + "-numPages"] = np;
+ this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = nw * 96 / 72;
+ this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = nh * 96 / 72;
+ !this.Document._fitWidth && !this.Document.ignoreAspect && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
}
public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
@@ -211,7 +211,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
const funcs: ContextMenuProps[] = [];
pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" });
- funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" });
+ funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
}
@@ -220,8 +220,8 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
@computed get renderTitleBox() {
const classname = "pdfBox" + (this.active() ? "-interactive" : "");
return <div className={classname} style={{
- width: !this.props.Document.fitWidth ? this.Document.nativeWidth || 0 : `${100 / this.contentScaling}%`,
- height: !this.props.Document.fitWidth ? this.Document.nativeHeight || 0 : `${100 / this.contentScaling}%`,
+ width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`,
+ height: !this.props.Document._fitWidth ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`,
transform: `scale(${this.contentScaling})`
}} >
<div className="pdfBox-title-outer">
@@ -252,13 +252,15 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
- if (pdfUrl && this.extensionDoc && (this._everActive || (this.extensionDoc.nativeWidth && this.props.ScreenToLocalTransform().Scale < 2.5))) {
+ if (pdfUrl && (this._everActive || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
if (pdfUrl instanceof PdfField && this._pdf) {
return this.renderPdfView;
}
if (!this._pdfjsRequested) {
this._pdfjsRequested = true;
- Pdfjs.getDocument(pdfUrl.url.href).promise.then(pdf => runInAction(() => this._pdf = pdf));
+ const promise = Pdfjs.getDocument(pdfUrl.url.href).promise;
+ promise.then(pdf => { runInAction(() => { this._pdf = pdf; console.log("promise"); }) });
+
}
}
return this.renderTitleBox;
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index e5a79ab11..7618aa7e3 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -6,7 +6,7 @@
top: 0;
bottom: 0;
width: 100%;
- min-width: 200px;
+ min-width: 120px;
height: 100%;
min-height: 50px;
letter-spacing: 2px;
@@ -14,12 +14,6 @@
transition: 0.7s opacity ease;
pointer-events: all;
- .presBox-listCont {
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- }
-
.presBox-buttons {
padding: 10px;
width: 100%;
@@ -30,4 +24,17 @@
border-radius: 5px;
}
}
+ .presBox-backward, .presBox-forward {
+ width: 25px;
+ border-radius: 5px;
+ top:50%;
+ position: absolute;
+ display: inline-block;
+ }
+ .presBox-backward {
+ left:5;
+ }
+ .presBox-forward {
+ right:5;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 1e6894f37..6c4cbba12 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -2,23 +2,27 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, reaction, IReactionDisposer } from "mobx";
+import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { listSpec } from "../../../new_fields/Schema";
+import { listSpec, makeInterface } from "../../../new_fields/Schema";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { Docs } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
-import { CollectionViewType } from "../collections/CollectionView";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionView } from "../collections/CollectionView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./PresBox.scss";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { Docs } from "../../documents/Documents";
-import { ComputedField } from "../../../new_fields/ScriptField";
+import { CollectionCarouselView } from "../collections/CollectionCarouselView";
+import { returnFalse } from "../../../Utils";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { CollectionTimeView } from "../collections/CollectionTimeView";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { InkingControl } from "../InkingControl";
+import { InkTool } from "../../../new_fields/InkField";
library.add(faArrowLeft);
library.add(faArrowRight);
@@ -32,34 +36,41 @@ library.add(faEdit);
@observer
export class PresBox extends React.Component<FieldViewProps> {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
- _docListChangedReaction: IReactionDisposer | undefined;
+ _childReaction: IReactionDisposer | undefined;
+ _slideshowReaction: IReactionDisposer | undefined;
+ @observable _isChildActive = false;
+
componentDidMount() {
- this._docListChangedReaction = reaction(() => {
- const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
- return value ? value.slice() : value;
- }, () => {
- const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
- if (value) {
- value.forEach((item, i) => {
- if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) {
- const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
- Doc.GetProto(pinDoc).presentationTargetDoc = item;
- Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
- value.splice(i, 1, pinDoc);
+ const userDoc = CurrentUserUtils.UserDocument;
+ this._slideshowReaction = reaction(() => this.props.Document._slideshow,
+ (slideshow) => {
+ if (!slideshow) {
+ let presTemp = Cast(userDoc.presentationTemplate, Doc);
+ if (presTemp instanceof Promise) {
+ presTemp.then(presTemp => this.props.Document.childLayout = presTemp);
}
- });
- }
- });
+ else if (presTemp === undefined) {
+ presTemp = userDoc.presentationTemplate = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent", _xMargin: 5, isTemplateDoc: true, isTemplateForField: "data" });
+ }
+ else {
+ this.props.Document.childLayout = presTemp;
+ }
+ } else {
+ this.props.Document.childLayout = undefined;
+ }
+ }, { fireImmediately: true });
+ this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
}
-
componentWillUnmount() {
- this._docListChangedReaction && this._docListChangedReaction();
+ this._childReaction?.();
+ this._slideshowReaction?.();
}
@computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); }
next = async () => {
- const current = NumCast(this.props.Document.selectedDoc);
+ runInAction(() => Doc.UserDoc().curPresentation = this.props.Document);
+ const current = NumCast(this.props.Document._itemIndex);
//asking to get document at current index
const docAtCurrentNext = await this.getDocAtIndex(current + 1);
if (docAtCurrentNext !== undefined) {
@@ -76,7 +87,8 @@ export class PresBox extends React.Component<FieldViewProps> {
}
}
back = async () => {
- const current = NumCast(this.props.Document.selectedDoc);
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
+ const current = NumCast(this.props.Document._itemIndex);
//requesting for the doc at current index
const docAtCurrent = await this.getDocAtIndex(current);
if (docAtCurrent !== undefined) {
@@ -115,12 +127,17 @@ export class PresBox extends React.Component<FieldViewProps> {
}
}
+ whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
+ active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
+ (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+
/**
* This is the method that checks for the actions that need to be performed
* after the document has been presented, which involves 3 button options:
* Hide Until Presented, Hide After Presented, Fade After Presented
*/
showAfterPresented = (index: number) => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
this.childDocs.forEach((doc, ind) => {
//the order of cases is aligned based on priority
if (doc.hideTillShownButton && ind <= index) {
@@ -141,6 +158,7 @@ export class PresBox extends React.Component<FieldViewProps> {
* Hide Until Presented, Hide After Presented, Fade After Presented
*/
hideIfNotPresented = (index: number) => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
this.childDocs.forEach((key, ind) => {
//the order of cases is aligned based on priority
@@ -162,6 +180,7 @@ export class PresBox extends React.Component<FieldViewProps> {
* te option open, navigates to that element.
*/
navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
let docToJump = curDoc;
let willZoom = false;
@@ -188,15 +207,17 @@ export class PresBox extends React.Component<FieldViewProps> {
});
//docToJump stayed same meaning, it was not in the group or was the last element in the group
+ const aliasOf = await Cast(docToJump.aliasOf, Doc);
+ const srcContext = aliasOf && await Cast(aliasOf.sourceContext, Doc);
if (docToJump === curDoc) {
//checking if curDoc has navigation open
- const target = await curDoc.presentationTargetDoc as Doc;
- if (curDoc.navButton) {
- DocumentManager.Instance.jumpToDocument(target, false);
- } else if (curDoc.showButton) {
+ const target = await Cast(curDoc.presentationTargetDoc, Doc);
+ if (curDoc.navButton && target) {
+ DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext);
+ } else if (curDoc.showButton && target) {
const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(target, true);
+ await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext);
curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target);
//saving the scale user was on before zooming in
@@ -210,7 +231,8 @@ export class PresBox extends React.Component<FieldViewProps> {
const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(await docToJump.presentationTargetDoc as Doc, willZoom);
+ const presTargetDoc = await docToJump.presentationTargetDoc as Doc;
+ await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext);
const newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc);
curDoc.viewScale = newScale;
//saving the scale that user was on
@@ -226,7 +248,7 @@ export class PresBox extends React.Component<FieldViewProps> {
getDocAtIndex = async (index: number) => {
const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
if (list && index >= 0 && index < list.length) {
- this.props.Document.selectedDoc = index;
+ this.props.Document._itemIndex = index;
//awaiting async call to finish to get Doc instance
return list[index];
}
@@ -249,12 +271,12 @@ export class PresBox extends React.Component<FieldViewProps> {
//The function that is called when a document is clicked or reached through next or back.
//it'll also execute the necessary actions if presentation is playing.
- @action
public gotoDocument = async (index: number, fromDoc: number) => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
Doc.UnBrushAllDocs();
const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
if (list && index >= 0 && index < list.length) {
- this.props.Document.selectedDoc = index;
+ this.props.Document._itemIndex = index;
if (!this.props.Document.presStatus) {
this.props.Document.presStatus = true;
@@ -271,26 +293,33 @@ export class PresBox extends React.Component<FieldViewProps> {
}
//The function that starts or resets presentaton functionally, depending on status flag.
- @action
startOrResetPres = () => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
if (this.props.Document.presStatus) {
this.resetPresentation();
} else {
this.props.Document.presStatus = true;
this.startPresentation(0);
- this.gotoDocument(0, NumCast(this.props.Document.selectedDoc));
+ this.gotoDocument(0, NumCast(this.props.Document._itemIndex));
}
}
+ addDocument = (doc: Doc) => {
+ const newPinDoc = Doc.MakeAlias(doc);
+ newPinDoc.presentationTargetDoc = doc;
+ return Doc.AddDocToList(this.props.Document, this.props.fieldKey, newPinDoc);
+ }
+
+
//The function that resets the presentation by removing every action done by it. It also
//stops the presentaton.
- @action
resetPresentation = () => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
this.childDocs.forEach((doc: Doc) => {
doc.opacity = 1;
doc.viewScale = 1;
});
- this.props.Document.selectedDoc = 0;
+ this.props.Document._itemIndex = 0;
this.props.Document.presStatus = false;
if (this.childDocs.length !== 0) {
DocumentManager.Instance.zoomIntoScale(this.childDocs[0], 1);
@@ -300,6 +329,7 @@ export class PresBox extends React.Component<FieldViewProps> {
//The function that starts the presentation, also checking if actions should be applied
//directly at start.
startPresentation = (startIndex: number) => {
+ action(() => Doc.UserDoc().curPresentation = this.props.Document);
this.childDocs.map(doc => {
if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) {
doc.opacity = 0;
@@ -327,22 +357,25 @@ export class PresBox extends React.Component<FieldViewProps> {
}));
specificContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" });
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._slideshow = "slideshow"), icon: "asterisk" });
+ funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._slideshow = "timeline"), icon: "asterisk" });
+ funcs.push({ description: "Show as List", event: action(() => this.props.Document._slideshow = undefined), icon: "asterisk" });
+ ContextMenu.Instance.addItem({ description: "Presentation Funcs...", subitems: funcs, icon: "asterisk" });
}
/**
* Initially every document starts with a viewScale 1, which means
* that they will be displayed in a canvas with scale 1.
*/
- @action
initializeScaleViews = (docList: Doc[], viewtype: number) => {
- this.props.Document.chromeStatus = "disabled";
- const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72;
+ this.props.Document._chromeStatus = "disabled";
+ const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
docList.forEach((doc: Doc) => {
doc.presBox = this.props.Document;
doc.presBoxKey = this.props.fieldKey;
doc.collapsedHeight = hgt;
- doc.height = ComputedField.MakeFunction("this.collapsedHeight + Number(this.embedOpen ? 100:0)");
+ doc._nativeWidth = doc._nativeHeight = undefined;
const curScale = NumCast(doc.viewScale, null);
if (curScale === undefined) {
doc.viewScale = 1;
@@ -350,19 +383,37 @@ export class PresBox extends React.Component<FieldViewProps> {
});
}
-
selectElement = (doc: Doc) => {
const index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc);
- index !== -1 && this.gotoDocument(index, NumCast(this.props.Document.selectedDoc));
+ index !== -1 && this.gotoDocument(index, NumCast(this.props.Document._itemIndex));
}
getTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-10, -50);// listBox padding-left and pres-box-cont minHeight
+ return this.props.ScreenToLocalTransform().translate(0, -50);// listBox padding-left and pres-box-cont minHeight
+ }
+ panelHeight = () => {
+ return this.props.PanelHeight() - 20;
}
render() {
- this.initializeScaleViews(this.childDocs, NumCast(this.props.Document.viewType));
- return (
- <div className="presBox-cont" onContextMenu={this.specificContextMenu}>
+ this.initializeScaleViews(this.childDocs, NumCast(this.props.Document._viewType));
+ return (this.props.Document._slideshow ?
+ <div className="presBox-cont" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this.active() ? "all" : "none" }} >
+ {this.props.Document.inOverlay ? (null) :
+ <div className="presBox-listCont" >
+ {this.props.Document._slideshow === "slideshow" ?
+ <CollectionCarouselView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined}
+ moveDocument={returnFalse}
+ addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+ :
+ <CollectionTimeView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined}
+ moveDocument={returnFalse}
+ addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+ }
+ </div>}
+ <button className="presBox-backward" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
+ <button className="presBox-forward" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
+ </div>
+ : <div className="presBox-cont" onContextMenu={this.specificContextMenu}>
<div className="presBox-buttons">
<button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
<button className="presBox-button" title={"Reset Presentation" + this.props.Document.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}>
@@ -370,13 +421,10 @@ export class PresBox extends React.Component<FieldViewProps> {
</button>
<button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
<button className="presBox-button" title={this.props.Document.inOverlay ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button>
- </div>
- {this.props.Document.inOverlay ? (null) :
- <div className="presBox-listCont" >
- <CollectionView {...this.props} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
- </div>
- }
- </div>
- );
+ {this.props.Document.inOverlay ? (null) :
+ <div className="presBox-listCont">
+ <CollectionView {...this.props} whenActiveChanged={this.whenActiveChanged} PanelHeight={this.panelHeight} addDocument={this.addDocument} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+ </div>}
+ </div></div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss
new file mode 100644
index 000000000..ce0c263ef
--- /dev/null
+++ b/src/client/views/nodes/RadialMenu.scss
@@ -0,0 +1,83 @@
+@import "../globalCssVariables";
+
+.radialMenu-cont {
+ position: absolute;
+ z-index: $radialMenu-zindex;
+ flex-direction: column;
+}
+
+.radialMenu-subMenu-cont {
+ position: absolute;
+ display: flex;
+ z-index: 1000;
+ flex-direction: column;
+ border-radius: 15px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.radialMenu-item {
+ // width: 11vw; //10vw
+ display: flex; //comment out to allow search icon to be inline with search text
+ align-items: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .1s;
+ border-style: none;
+ white-space: nowrap;
+ font-size: 13px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+}
+
+s
+.radialMenu-itemSelected {
+ border-style: none;
+}
+
+.radialMenu-group {
+ // width: 11vw; //10vw
+ display: flex; //comment out to allow search icon to be inline with search text
+ justify-content: left;
+ align-items: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .1s;
+ border-width: .11px;
+ border-style: none;
+ border-color: $intermediate-color; // rgb(187, 186, 186);
+ // padding: 10px 0px 10px 0px;
+ white-space: nowrap;
+ font-size: 13px;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ padding-left: 5px;
+}
+
+
+.radialMenu-description {
+ margin-left: 5px;
+ text-align: left;
+ display: inline; //need this?
+}
+
+
+
+.icon-background {
+ pointer-events: all;
+ height:100%;
+ margin-top: 15px;
+ background-color: transparent;
+ width: 35px;
+ text-align: center;
+ font-size: 20px;
+ margin-left: 5px;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
new file mode 100644
index 000000000..9314a3899
--- /dev/null
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -0,0 +1,224 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { action, observable, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import Measure from "react-measure";
+import "./RadialMenu.scss";
+
+@observer
+export class RadialMenu extends React.Component {
+ static Instance: RadialMenu;
+ static readonly buffer = 20;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ RadialMenu.Instance = this;
+ }
+
+ @observable private _mouseX: number = -1;
+ @observable private _mouseY: number = -1;
+ @observable private _shouldDisplay: boolean = false;
+ @observable private _mouseDown: boolean = false;
+ private _reactionDisposer?: IReactionDisposer;
+
+
+ @action
+ onPointerDown = (e: PointerEvent) => {
+ this._mouseDown = true;
+ this._mouseX = e.clientX;
+ this._mouseY = e.clientY;
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
+
+ @observable
+ private _closest: number = -1;
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ const curX = e.clientX;
+ const curY = e.clientY;
+ const deltX = this._mouseX - curX;
+ const deltY = this._mouseY - curY;
+ const scale = Math.hypot(deltY, deltX);
+
+ if (scale < 150 && scale > 50) {
+ const rad = Math.atan2(deltY, deltX) + Math.PI;
+ let closest = 0;
+ let closestval = 999999999;
+ for (let x = 0; x < this._items.length; x++) {
+ const curmin = (x / this._items.length) * 2 * Math.PI;
+ if (rad - curmin < closestval && rad - curmin > 0) {
+ closestval = rad - curmin;
+ closest = x;
+ }
+ }
+ this._closest = closest;
+ }
+ else {
+ this._closest = -1;
+ }
+ }
+ @action
+ onPointerUp = (e: PointerEvent) => {
+ this._mouseDown = false;
+ const curX = e.clientX;
+ const curY = e.clientY;
+ if (this._mouseX !== curX || this._mouseY !== curY) {
+ this._shouldDisplay = false;
+ }
+ this._shouldDisplay && (this._display = true);
+ document.removeEventListener("pointermove", this.onPointerMove);
+ if (this._closest !== -1 && this._items?.length > this._closest) {
+ this._items[this._closest].event();
+ }
+ }
+ componentWillUnmount() {
+ document.removeEventListener("pointerdown", this.onPointerDown);
+
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this._reactionDisposer && this._reactionDisposer();
+ }
+
+ @action
+ componentDidMount = () => {
+ document.addEventListener("pointerdown", this.onPointerDown);
+ document.addEventListener("pointerup", this.onPointerUp);
+ this.previewcircle();
+ this._reactionDisposer = reaction(
+ () => this._shouldDisplay,
+ () => this._shouldDisplay && !this._mouseDown && runInAction(() => this._display = true)
+ );
+ }
+
+ componentDidUpdate = () => {
+ this.previewcircle();
+ }
+
+ @observable private _pageX: number = 0;
+ @observable private _pageY: number = 0;
+ @observable private _display: boolean = false;
+ @observable private _yRelativeToTop: boolean = true;
+
+
+ @observable private _width: number = 0;
+ @observable private _height: number = 0;
+
+
+ getItems() {
+ return this._items;
+ }
+
+ @action
+ addItem(item: RadialMenuProps) {
+ if (this._items.indexOf(item) === -1) {
+ this._items.push(item);
+ }
+ }
+
+ @observable
+ private _items: Array<RadialMenuProps> = [];
+
+ @action
+ displayMenu = (x: number, y: number) => {
+ //maxX and maxY will change if the UI/font size changes, but will work for any amount
+ //of items added to the menu
+
+ this._pageX = x;
+ this._pageY = y;
+ this._shouldDisplay = true;
+ }
+
+ get pageX() {
+ const x = this._pageX;
+ if (x < 0) {
+ return 0;
+ }
+ const width = this._width;
+ if (x + width > window.innerWidth - RadialMenu.buffer) {
+ return window.innerWidth - RadialMenu.buffer - width;
+ }
+ return x;
+ }
+
+ get pageY() {
+ const y = this._pageY;
+ if (y < 0) {
+ return 0;
+ }
+ const height = this._height;
+ if (y + height > window.innerHeight - RadialMenu.buffer) {
+ return window.innerHeight - RadialMenu.buffer - height;
+ }
+ return y;
+ }
+
+ @computed get menuItems() {
+ return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
+ }
+
+ @action
+ closeMenu = () => {
+ this.clearItems();
+ this._display = false;
+ this._shouldDisplay = false;
+ }
+
+ @action
+ openMenu = () => {
+ this._shouldDisplay;
+ this._display = true;
+ }
+
+ @action
+ clearItems() {
+ this._items = [];
+ }
+
+
+ previewcircle() {
+ if (document.getElementById("newCanvas") !== null) {
+ const c: any = document.getElementById("newCanvas");
+ if (c.getContext) {
+ const ctx = c.getContext("2d");
+ ctx.beginPath();
+ ctx.arc(150, 150, 50, 0, 2 * Math.PI);
+ ctx.fillStyle = "white";
+ ctx.fill();
+ ctx.font = "12px Arial";
+ ctx.fillStyle = "black";
+ ctx.textAlign = "center";
+ let description = "";
+ if (this._closest !== -1) {
+ description = this._items[this._closest].description;
+ }
+ if (description.length > 15) {
+ description = description.slice(0, 12);
+ description += "...";
+ }
+ ctx.fillText(description, 150, 150, 90);
+ }
+ }
+ }
+
+
+ render() {
+ if (!this._display) {
+ return null;
+ }
+ const style = this._yRelativeToTop ? { left: this._mouseX - 150, top: this._mouseY - 150 } :
+ { left: this._mouseX - 150, top: this._mouseY - 150 };
+
+ return (
+
+ <div className="radialMenu-cont" style={style}>
+ <canvas id="newCanvas" style={{ position: "absolute" }} height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas>
+ {this.menuItems}
+ </div>
+
+ );
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
new file mode 100644
index 000000000..fdc732d3f
--- /dev/null
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -0,0 +1,117 @@
+import React = require("react");
+import { observable, action } from "mobx";
+import { observer } from "mobx-react";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { UndoManager } from "../../util/UndoManager";
+
+library.add(faAngleRight);
+
+export interface RadialMenuProps {
+ description: string;
+ event: (stuff?: any) => void;
+ undoable?: boolean;
+ icon: IconProp;
+ closeMenu?: () => void;
+ min?: number;
+ max?: number;
+ selected: number;
+}
+
+
+@observer
+export class RadialMenuItem extends React.Component<RadialMenuProps> {
+
+ componentDidMount = () => {
+ this.setcircle();
+ }
+
+ componentDidUpdate = () => {
+ this.setcircle();
+ }
+
+ handleEvent = async (e: React.PointerEvent) => {
+ this.props.closeMenu && this.props.closeMenu();
+ let batch: UndoManager.Batch | undefined;
+ if (this.props.undoable !== false) {
+ batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`);
+ }
+ await this.props.event({ x: e.clientX, y: e.clientY });
+ batch && batch.end();
+ }
+
+
+ setcircle() {
+ let circlemin = 0;
+ let circlemax = 1
+ this.props.min ? circlemin = this.props.min : null;
+ this.props.max ? circlemax = this.props.max : null;
+ if (document.getElementById("myCanvas") !== null) {
+ var c: any = document.getElementById("myCanvas");
+ let color = "white"
+ switch (circlemin % 3) {
+ case 1:
+ color = "#c2c2c5";
+ break;
+ case 0:
+ color = "#f1efeb";
+ break;
+ case 2:
+ color = "lightgray";
+ break;
+ }
+ if (circlemax % 3 === 1 && circlemin === circlemax - 1) {
+ color = "#c2c2c5";
+ }
+
+ if (this.props.selected === this.props.min) {
+ color = "#808080";
+
+ }
+ if (c.getContext) {
+ var ctx = c.getContext("2d");
+ ctx.beginPath();
+ ctx.arc(150, 150, 150, (circlemin / circlemax) * 2 * Math.PI, ((circlemin + 1) / circlemax) * 2 * Math.PI);
+ ctx.arc(150, 150, 50, ((circlemin + 1) / circlemax) * 2 * Math.PI, (circlemin / circlemax) * 2 * Math.PI, true);
+ ctx.fillStyle = color;
+ ctx.fill()
+ }
+ }
+ }
+
+ calculatorx() {
+ let circlemin = 0;
+ let circlemax = 1
+ this.props.min ? circlemin = this.props.min : null;
+ this.props.max ? circlemax = this.props.max : null;
+ let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
+ let degrees = 360 * avg;
+ let x = 100 * Math.cos(degrees * Math.PI / 180);
+ let y = -125 * Math.sin(degrees * Math.PI / 180);
+ return x;
+ }
+
+ calculatory() {
+
+ let circlemin = 0;
+ let circlemax = 1
+ this.props.min ? circlemin = this.props.min : null;
+ this.props.max ? circlemax = this.props.max : null;
+ let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
+ let degrees = 360 * avg;
+ let x = 125 * Math.cos(degrees * Math.PI / 180);
+ let y = -100 * Math.sin(degrees * Math.PI / 180);
+ return y;
+ }
+
+
+ render() {
+ return (
+ <div className={"radialMenu-item" + (this.props.selected ? " radialMenu-itemSelected" : "")} onPointerUp={this.handleEvent}>
+ <canvas id="myCanvas" height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas>
+ <FontAwesomeIcon icon={this.props.icon} size="3x" style={{ position: "absolute", left: this.calculatorx() + 150 - 19, top: this.calculatory() + 150 - 19 }} />
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 376d27380..d12a8d151 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -55,12 +55,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
videoLoad = () => {
const aspect = this.player!.videoWidth / this.player!.videoHeight;
- const nativeWidth = (this.Document.nativeWidth || 0);
- const nativeHeight = (this.Document.nativeHeight || 0);
+ const nativeWidth = (this.Document._nativeWidth || 0);
+ const nativeHeight = (this.Document._nativeHeight || 0);
if (!nativeWidth || !nativeHeight) {
- if (!this.Document.nativeWidth) this.Document.nativeWidth = this.player!.videoWidth;
- this.Document.nativeHeight = (this.Document.nativeWidth || 0) / aspect;
- this.Document.height = (this.Document.width || 0) / aspect;
+ if (!this.Document._nativeWidth) this.Document._nativeWidth = this.player!.videoWidth;
+ this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect;
+ this.Document._height = (this.Document._width || 0) / aspect;
}
if (!this.Document.duration) this.Document.duration = this.player!.duration;
}
@@ -101,11 +101,11 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
@action public Snapshot() {
- const width = this.Document.width || 0;
- const height = this.Document.height || 0;
+ const width = this.Document._width || 0;
+ const height = this.Document._height || 0;
const canvas = document.createElement('canvas');
canvas.width = 640;
- canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
+ canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1);
const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
if (ctx) {
ctx.rect(0, 0, canvas.width, canvas.height);
@@ -117,7 +117,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
if (!this._videoRef) { // can't find a way to take snapshots of videos
const b = Docs.Create.ButtonDocument({
x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
- width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString()
+ _width: 150, _height: 50, title: (this.Document.currentTimecode || 0).toString()
});
b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
} else {
@@ -130,7 +130,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
const url = this.choosePath(Utils.prepend(returnedFilename));
const imageSummary = Docs.Create.ImageDocument(url, {
x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
- width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
+ _width: 150, _height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
});
imageSummary.isButton = true;
this.props.addDocument && this.props.addDocument(imageSummary);
@@ -151,12 +151,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
if (this.youtubeVideoId) {
const youtubeaspect = 400 / 315;
- const nativeWidth = (this.Document.nativeWidth || 0);
- const nativeHeight = (this.Document.nativeHeight || 0);
+ const nativeWidth = (this.Document._nativeWidth || 0);
+ const nativeHeight = (this.Document._nativeHeight || 0);
if (!nativeWidth || !nativeHeight) {
- if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
- this.Document.nativeHeight = (this.Document.nativeWidth || 0) / youtubeaspect;
- this.Document.height = (this.Document.width || 0) / youtubeaspect;
+ if (!this.Document._nativeWidth) this.Document._nativeWidth = 600;
+ this.Document._nativeHeight = (this.Document._nativeWidth || 0) / youtubeaspect;
+ this.Document._height = (this.Document._width || 0) / youtubeaspect;
}
}
}
@@ -321,7 +321,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
const start = untracked(() => Math.round(this.Document.currentTimecode || 0));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
- onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
+ onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document._nativeWidth || 640)} height={(this.Document._nativeHeight || 390)}
src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
}
@@ -340,7 +340,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationsKey}
+ annotationsKey={this.annotationKey}
focus={this.props.focus}
isSelected={this.props.isSelected}
isAnnotationOverlay={true}
@@ -353,7 +353,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
addDocument={this.addDocumentWithTimestamp}
CollectionView={undefined}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- ruleProvider={undefined}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b35ea0bb0..a48dc286e 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -34,17 +34,17 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
@observable private collapsed: boolean = true;
@observable private url: string = "";
- componentWillMount() {
+ componentDidMount() {
const field = Cast(this.props.Document[this.props.fieldKey], WebField);
if (field && field.url.href.indexOf("youtube") !== -1) {
const youtubeaspect = 400 / 315;
- const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
- const nativeHeight = NumCast(this.layoutDoc.nativeHeight);
+ const nativeWidth = NumCast(this.layoutDoc._nativeWidth);
+ const nativeHeight = NumCast(this.layoutDoc._nativeHeight);
if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
- if (!nativeWidth) this.layoutDoc.nativeWidth = 600;
- this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / youtubeaspect;
- this.layoutDoc.height = NumCast(this.layoutDoc.width) / youtubeaspect;
+ if (!nativeWidth) this.layoutDoc._nativeWidth = 600;
+ this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect;
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
}
}
@@ -83,13 +83,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
const field = Cast(this.props.Document[this.props.fieldKey], WebField);
if (field) url = field.url.href;
- const newBox = Docs.Create.TextDocument({
+ const newBox = Docs.Create.TextDocument(url, {
x: NumCast(this.props.Document.x),
y: NumCast(this.props.Document.y),
title: url,
- width: 200,
- height: 70,
- documentText: "@@@" + url
+ _width: 200,
+ _height: 70,
});
SelectionManager.SelectedDocuments().map(dv => {
@@ -198,7 +197,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationsKey}
+ annotationsKey={this.annotationKey}
focus={this.props.focus}
isSelected={this.props.isSelected}
isAnnotationOverlay={true}
@@ -211,7 +210,6 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
addDocument={this.addDocument}
CollectionView={undefined}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- ruleProvider={undefined}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 6599c0e3c..d8b340db6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -11,10 +11,11 @@ import "./Annotation.scss";
interface IAnnotationProps {
anno: Doc;
- extensionDoc: Doc;
addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;
pinToPres: (document: Doc) => void;
focus: (doc: Doc) => void;
+ dataDoc: Doc;
+ fieldKey: string;
}
export default class Annotation extends React.Component<IAnnotationProps> {
@@ -29,10 +30,11 @@ interface IRegionAnnotationProps {
y: number;
width: number;
height: number;
- extensionDoc: Doc;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
document: Doc;
+ dataDoc: Doc;
+ fieldKey: string;
}
@observer
@@ -62,12 +64,12 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
deleteAnnotation = () => {
- const annotation = DocListCast(this.props.extensionDoc.annotations);
+ const annotation = DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]);
const group = FieldValue(Cast(this.props.document.group, Doc));
if (group) {
if (annotation.indexOf(group) !== -1) {
const newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
- this.props.extensionDoc.annotations = new List<Doc>(newAnnotations);
+ this.props.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>(newAnnotations);
}
DocListCast(group.annotations).forEach(anno => anno.delete = true);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 62467ce4d..a7c1990e9 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -113,8 +113,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
private _coverPath: any;
@computed get allAnnotations() {
- return this.extensionDoc ? DocListCast(this.extensionDoc.annotations).filter(
- anno => this._script.run({ this: anno }, console.log, true).result) : [];
+ return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).filter(
+ anno => this._script.run({ this: anno }, console.log, true).result);
}
@computed get nonDocAnnotations() {
@@ -208,8 +208,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
await this.initialLoad();
this._annotationReactionDisposer = reaction(
- () => this.extensionDoc && DocListCast(this.extensionDoc.annotations),
- annotations => annotations && annotations.length && (this._annotations = annotations),
+ () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]),
+ annotations => annotations?.length && (this._annotations = annotations),
{ fireImmediately: true });
this._filterReactionDisposer = reaction(
@@ -271,8 +271,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + this.Document.title });
if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
- if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
- if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
+ if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+ if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
annoDoc.group = mainAnnoDoc;
annoDoc.isButton = true;
annoDocs.push(annoDoc);
@@ -286,8 +286,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
const annoDoc = new Doc();
if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
- if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
- if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
+ if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+ if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
annoDoc.group = mainAnnoDoc;
annoDoc.backgroundColor = color;
annoDocs.push(annoDoc);
@@ -558,7 +558,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
startDrag = (e: PointerEvent, ele: HTMLElement): void => {
e.preventDefault();
e.stopPropagation();
- const targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title });
+ const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
if (annotationDoc) {
DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
@@ -573,11 +573,11 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
const data = Doc.MakeDelegate(Doc.GetProto(this.props.Document));
data.title = StrCast(data.title) + "_snippet";
view.proto = data;
- view.nativeHeight = marquee.height;
- view.height = (this.Document[WidthSym]() / (this.Document.nativeWidth || 1)) * marquee.height;
- view.nativeWidth = this.Document.nativeWidth;
+ view._nativeHeight = marquee.height;
+ view._height = (this.Document[WidthSym]() / (this.Document._nativeWidth || 1)) * marquee.height;
+ view._nativeWidth = this.Document._nativeWidth;
view.startY = marquee.top;
- view.width = this.Document[WidthSym]();
+ view._width = this.Document[WidthSym]();
DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
}
@@ -598,12 +598,12 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
getCoverImage = () => {
if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
setTimeout((() => {
- this.Document.height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
- this.Document.nativeHeight = (this.Document.nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
+ this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
+ this.Document._nativeHeight = (this.Document._nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
}).bind(this), 0);
}
- const nativeWidth = (this.Document.nativeWidth || 0);
- const nativeHeight = (this.Document.nativeHeight || 0);
+ const nativeWidth = (this.Document._nativeWidth || 0);
+ const nativeHeight = (this.Document._nativeHeight || 0);
const resolved = Utils.prepend(this._coverPath.path);
return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)}
style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
@@ -621,19 +621,19 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
@computed get annotationLayer() {
TraceMobx();
- return <div className="pdfViewer-annotationLayer" style={{ height: (this.Document.nativeHeight || 0), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
+ return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document.nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
{this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
- <Annotation {...this.props} focus={this.props.focus} extensionDoc={this.extensionDoc!} anno={anno} key={`${anno[Id]}-annotation`} />)}
+ <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc!} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
</div>;
}
overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
- panelWidth = () => (this.Document.scrollHeight || this.Document.nativeHeight || 0);
- panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0);
+ panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0);
+ panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
@computed get overlayLayer() {
return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
<CollectionFreeFormView {...this.props}
LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? []}
- annotationsKey={this.annotationsKey}
+ annotationsKey={this.annotationKey}
setPreviewCursor={this.setPreviewCursor}
PanelHeight={this.panelWidth}
PanelWidth={this.panelHeight}
@@ -650,7 +650,6 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
addDocument={this.addDocument}
CollectionView={undefined}
ScreenToLocalTransform={this.overlayTransform}
- ruleProvider={undefined}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}
chromeCollapsed={true}>
@@ -676,20 +675,19 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
contentZoom = () => this._zoomed;
render() {
TraceMobx();
- return !this.extensionDoc ? (null) :
- <div className={"pdfViewer" + (this._zoomed !== 1 ? "-zoomed" : "")} ref={this._mainCont}
- onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
- style={{
- width: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeWidth) : `${100 / this.contentScaling}%`,
- height: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeHeight) : `${100 / this.contentScaling}%`,
- transform: `scale(${this.props.ContentScaling()})`
- }} >
- {this.pdfViewerDiv}
- {this.overlayLayer}
- {this.annotationLayer}
- {this.standinViews}
- <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
- </div >;
+ return <div className={"pdfViewer" + (this._zoomed !== 1 ? "-zoomed" : "")} ref={this._mainCont}
+ onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
+ style={{
+ width: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
+ height: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
+ transform: `scale(${this.props.ContentScaling()})`
+ }} >
+ {this.pdfViewerDiv}
+ {this.overlayLayer}
+ {this.annotationLayer}
+ {this.standinViews}
+ <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
+ </div >;
}
}
diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss
index 34c170be2..8370af490 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/presentationview/PresElementBox.scss
@@ -4,17 +4,20 @@
background-color: #eeeeee;
pointer-events: all;
width: 100%;
+ height: 100%;
outline-color: maroon;
outline-style: dashed;
- border-radius: 12px;
+ border-radius: 6px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
-
+ transition: all .1s;
+ padding: 0px;
+ padding-left: 5px;
+ padding-bottom: 3px;
.documentView-node {
position: absolute;
z-index: 1;
@@ -32,34 +35,43 @@
.presElementBox-item:hover {
transition: all .1s;
background: #AAAAAA;
- border-radius: 12px;
+ border-radius: 6px;
}
.presElementBox-selected {
background: gray;
color: black;
- border-radius: 12px;
+ border-radius: 6px;
box-shadow: black 2px 2px 5px;
}
.presElementBox-closeIcon {
- float: right;
border-radius: 20px;
transform:scale(0.7);
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 8px;
}
.presElementBox-interaction {
color: gray;
float: left;
+ padding: 0px;
+ width: 20px;
+ height: 20px;
}
.presElementBox-interaction-selected {
color: white;
float: left;
+ padding: 0px;
+ width: 22px;
+ height: 22px;
}
.presElementBox-name {
- font-size: 15px;
+ font-size: 12pxππ;
position: absolute;
display: inline-block;
width: calc(100% - 45px);
@@ -70,7 +82,14 @@
.presElementBox-embedded {
position: relative;
- margin-top: 30;
+ margin-top: 22;
+ display: flex;
+ width: auto;
+ justify-content: center;
+ .contentFittingDocumentView {
+ position: absolute;
+ height: 100%;
+ }
}
.presElementBox-embeddedMask {
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index c02042380..52773d466 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
import { faArrowDown, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed } from "mobx";
+import { action, computed, reaction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../new_fields/Doc";
import { documentSchema } from '../../../new_fields/documentSchemas';
@@ -14,7 +14,7 @@ import { DocumentType } from "../../documents/DocumentTypes";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
-import { DocComponent } from '../DocComponent';
+import { DocComponent, DocExtendableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
import React = require("react");
@@ -46,13 +46,23 @@ const PresDocument = makeInterface(presSchema, documentSchema);
* It involves some functionality for its buttons and options.
*/
@observer
-export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(PresDocument) {
+export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresDocument>(PresDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
- @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); }
- @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; }
- @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc; }
- @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); }
+ _heightDisposer: IReactionDisposer | undefined;
+ @computed get indexInPres() { return NumCast(this.originalLayout?.presentationIndex); }
+ @computed get presentationDoc() { return Cast(this.originalLayout?.presBox, Doc) as Doc; }
+ @computed get originalLayout() { return this.props.Document.expandedTemplate as Doc; }
+ @computed get targetDoc() { return this.originalLayout?.presentationTargetDoc as Doc; }
+ @computed get currentIndex() { return NumCast(this.presentationDoc?._itemIndex); }
+
+ componentDidMount() {
+ this._heightDisposer = reaction(() => [this.originalLayout.embedOpen, this.originalLayout.collapsedHeight],
+ params => this.originalLayout._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._heightDisposer?.();
+ }
/**
* The function that is called on click to turn Hiding document till press option on/off.
@@ -61,8 +71,8 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
@action
onHideDocumentUntilPressClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.Document.hideTillShownButton = !this.Document.hideTillShownButton;
- if (!this.Document.hideTillShownButton) {
+ this.originalLayout.hideTillShownButton = !this.originalLayout.hideTillShownButton;
+ if (!this.originalLayout.hideTillShownButton) {
if (this.indexInPres >= this.currentIndex && this.targetDoc) {
this.targetDoc.opacity = 1;
}
@@ -81,13 +91,13 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
@action
onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.Document.hideAfterButton = !this.Document.hideAfterButton;
- if (!this.Document.hideAfterButton) {
+ this.originalLayout.hideAfterButton = !this.originalLayout.hideAfterButton;
+ if (!this.originalLayout.hideAfterButton) {
if (this.indexInPres <= this.currentIndex && this.targetDoc) {
this.targetDoc.opacity = 1;
}
} else {
- if (this.Document.fadeButton) this.Document.fadeButton = false;
+ if (this.originalLayout.fadeButton) this.originalLayout.fadeButton = false;
if (this.presentationDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) {
this.targetDoc.opacity = 0;
}
@@ -102,13 +112,13 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
@action
onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.Document.fadeButton = !this.Document.fadeButton;
- if (!this.Document.fadeButton) {
+ this.originalLayout.fadeButton = !this.originalLayout.fadeButton;
+ if (!this.originalLayout.fadeButton) {
if (this.indexInPres <= this.currentIndex && this.targetDoc) {
this.targetDoc.opacity = 1;
}
} else {
- this.Document.hideAfterButton = false;
+ this.originalLayout.hideAfterButton = false;
if (this.presentationDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) {
this.targetDoc.opacity = 0.5;
}
@@ -121,11 +131,11 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
@action
onNavigateDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.Document.navButton = !this.Document.navButton;
- if (this.Document.navButton) {
- this.Document.showButton = false;
+ this.originalLayout.navButton = !this.originalLayout.navButton;
+ if (this.originalLayout.navButton) {
+ this.originalLayout.showButton = false;
if (this.currentIndex === this.indexInPres) {
- this.props.focus(this.props.Document);
+ this.props.focus(this.originalLayout);
}
}
}
@@ -137,13 +147,13 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
onZoomDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.Document.showButton = !this.Document.showButton;
- if (!this.Document.showButton) {
- this.props.Document.viewScale = 1;
+ this.originalLayout.showButton = !this.originalLayout.showButton;
+ if (!this.originalLayout.showButton) {
+ this.originalLayout.viewScale = 1;
} else {
- this.Document.navButton = false;
+ this.originalLayout.navButton = false;
if (this.currentIndex === this.indexInPres) {
- this.props.focus(this.props.Document);
+ this.props.focus(this.originalLayout);
}
}
}
@@ -152,68 +162,58 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
*/
ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
+ embedHeight = () => this.props.PanelHeight() - NumCast(this.originalLayout.collapsedHeight);
+ embedWidth = () => this.props.PanelWidth() - 20;
/**
* The function that is responsible for rendering the a preview or not for this
* presentation element.
*/
renderEmbeddedInline = () => {
- if (!this.Document.embedOpen || !this.targetDoc) {
- return (null);
- }
-
- const propDocWidth = NumCast(this.layoutDoc.nativeWidth);
- const propDocHeight = NumCast(this.layoutDoc.nativeHeight);
- const scale = () => 175 / NumCast(this.layoutDoc.nativeWidth, 175);
- return (
- <div className="presElementBox-embedded" style={{
- height: propDocHeight === 0 ? NumCast(this.layoutDoc.height) - NumCast(this.layoutDoc.collapsedHeight) : propDocHeight * scale(),
- width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
- }}>
+ return !this.originalLayout.embedOpen || !this.targetDoc ? (null) :
+ <div className="presElementBox-embedded" style={{ height: this.embedHeight() }}>
<ContentFittingDocumentView
Document={this.targetDoc}
LibraryPath={emptyPath}
- fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
+ fitToBox={true}
addDocument={returnFalse}
removeDocument={returnFalse}
- ruleProvider={undefined}
addDocTab={returnFalse}
pinToPres={returnFalse}
- PanelWidth={() => this.props.PanelWidth() - 20}
- PanelHeight={() => 100}
+ PanelWidth={this.embedWidth}
+ PanelHeight={this.embedHeight}
getTransform={Transform.Identity}
active={this.props.active}
moveDocument={this.props.moveDocument!}
- renderDepth={1}
+ renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
whenActiveChanged={returnFalse}
/>
<div className="presElementBox-embeddedMask" />
- </div>
- );
+ </div>;
}
render() {
- const treecontainer = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.viewType === CollectionViewType.Tree;
+ const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree;
const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : "");
const pbi = "presElementBox-interaction";
- return (
+ return !this.originalLayout ? (null) : (
<div className={className} key={this.props.Document[Id] + this.indexInPres}
style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }}
- onClick={e => { this.props.focus(this.props.Document); e.stopPropagation(); }}>
+ onClick={e => { this.props.focus(this.originalLayout); e.stopPropagation(); }}>
{treecontainer ? (null) : <>
<strong className="presElementBox-name">
- {`${this.indexInPres + 1}. ${this.Document.title}`}
+ {`${this.indexInPres + 1}. ${this.targetDoc?.title}`}
</strong>
- <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.props.Document)}>X</button>
+ <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.originalLayout)}>X</button>
<br />
</>}
- <button title="Zoom" className={pbi + (this.Document.showButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
- <button title="Navigate" className={pbi + (this.Document.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
- <button title="Hide Before" className={pbi + (this.Document.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
- <button title="Fade After" className={pbi + (this.Document.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
- <button title="Hide After" className={pbi + (this.Document.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
- <button title="Group With Up" className={pbi + (this.Document.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.Document.groupButton = !this.Document.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button>
- <button title="Expand Inline" className={pbi + (this.Document.embedOpen ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.Document.embedOpen = !this.Document.embedOpen; }}><FontAwesomeIcon icon={"arrow-down"} /></button>
+ <button title="Zoom" className={pbi + (this.originalLayout.showButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
+ <button title="Navigate" className={pbi + (this.originalLayout.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
+ <button title="Hide Before" className={pbi + (this.originalLayout.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
+ <button title="Fade After" className={pbi + (this.originalLayout.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+ <button title="Hide After" className={pbi + (this.originalLayout.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+ <button title="Group With Up" className={pbi + (this.originalLayout.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.originalLayout.groupButton = !this.originalLayout.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button>
+ <button title="Expand Inline" className={pbi + (this.originalLayout.embedOpen ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.originalLayout.embedOpen = !this.originalLayout.embedOpen; }}><FontAwesomeIcon icon={"arrow-down"} /></button>
<br style={{ lineHeight: 0.1 }} />
{this.renderEmbeddedInline()}
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 0825580b7..f492ea773 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -9,6 +9,7 @@
position: absolute;
font-size: 10px;
line-height: 1;
+ overflow: hidden;
}
.searchBox-bar {
height: 32px;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index dd1ac7421..be13dae03 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -217,16 +217,16 @@ export class SearchBox extends React.Component {
doc.x = x;
doc.y = y;
const size = 200;
- const aspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
+ const aspect = NumCast(doc._nativeHeight) / NumCast(doc._nativeWidth, 1);
if (aspect > 1) {
- doc.height = size;
- doc.width = size / aspect;
+ doc._height = size;
+ doc._width = size / aspect;
} else if (aspect > 0) {
- doc.width = size;
- doc.height = size * aspect;
+ doc._width = size;
+ doc._height = size * aspect;
} else {
- doc.width = size;
- doc.height = size;
+ doc._width = size;
+ doc._height = size;
}
x += 250;
if (x > 1000) {
@@ -234,7 +234,7 @@ export class SearchBox extends React.Component {
y += 300;
}
}
- return Docs.Create.TreeDocument(docs, { width: 200, height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
+ return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
}
@action.bound
@@ -267,10 +267,11 @@ export class SearchBox extends React.Component {
@action
resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
+ if (!this.resultsRef.current) return;
const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0;
const itemHght = 53;
const startIndex = Math.floor(Math.max(0, scrollY / itemHght));
- const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current!.getBoundingClientRect().height / itemHght)));
+ const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght)));
this._endIndex = endIndex === -1 ? 12 : endIndex;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 88a4d4c50..8aea737f0 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -68,11 +68,11 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
getOnClick({ col, target }: { col: Doc, target: Doc }) {
return () => {
col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
- if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
- const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
- col.panX = newPanX;
- col.panY = newPanY;
+ if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
+ col._panX = newPanX;
+ col._panY = newPanY;
}
CollectionDockingView.AddRightSplit(col, undefined);
};
@@ -161,7 +161,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
addDocument={returnFalse}
removeDocument={returnFalse}
- ruleProvider={undefined}
addDocTab={returnFalse}
pinToPres={returnFalse}
getTransform={Transform.Identity}
@@ -260,7 +259,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
onPointerMoved = (e: PointerEvent) => {
if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD ||
Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) {
- console.log("DRAGGIGNG");
document.removeEventListener("pointermove", this.onPointerMoved);
document.removeEventListener("pointerup", this.onPointerUp);
const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;