aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/DocComponent.tsx62
-rw-r--r--src/client/views/DocumentButtonBar.tsx5
-rw-r--r--src/client/views/DocumentDecorations.tsx5
-rw-r--r--src/client/views/GestureOverlay.tsx224
-rw-r--r--src/client/views/GlobalKeyHandler.ts35
-rw-r--r--src/client/views/InkingStroke.tsx25
-rw-r--r--src/client/views/Main.tsx1
-rw-r--r--src/client/views/MainView.tsx68
-rw-r--r--src/client/views/OverlayView.tsx6
-rw-r--r--src/client/views/Palette.tsx9
-rw-r--r--src/client/views/PreviewCursor.tsx17
-rw-r--r--src/client/views/RecommendationsBox.scss9
-rw-r--r--src/client/views/RecommendationsBox.tsx6
-rw-r--r--src/client/views/ScriptBox.tsx13
-rw-r--r--src/client/views/TemplateMenu.tsx80
-rw-r--r--src/client/views/Touchable.tsx9
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss1
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx27
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx10
-rw-r--r--src/client/views/collections/CollectionLinearView.scss2
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx36
-rw-r--r--src/client/views/collections/CollectionMapView.scss30
-rw-r--r--src/client/views/collections/CollectionMapView.tsx262
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx8
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx78
-rw-r--r--src/client/views/collections/CollectionStackingView.scss3
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx111
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx15
-rw-r--r--src/client/views/collections/CollectionSubView.tsx20
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx58
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx76
-rw-r--r--src/client/views/collections/CollectionView.scss5
-rw-r--r--src/client/views/collections/CollectionView.tsx166
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss9
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx369
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx56
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx233
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx22
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx14
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx16
-rw-r--r--src/client/views/globalCssVariables.scss2
-rw-r--r--src/client/views/nodes/AudioBox.scss4
-rw-r--r--src/client/views/nodes/AudioBox.tsx40
-rw-r--r--src/client/views/nodes/ButtonBox.tsx97
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx58
-rw-r--r--src/client/views/nodes/ColorBox.tsx6
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx43
-rw-r--r--src/client/views/nodes/DocumentBox.tsx14
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx14
-rw-r--r--src/client/views/nodes/DocumentView.scss9
-rw-r--r--src/client/views/nodes/DocumentView.tsx318
-rw-r--r--src/client/views/nodes/FieldView.tsx4
-rw-r--r--src/client/views/nodes/FontIconBox.tsx2
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss9
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx26
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx26
-rw-r--r--src/client/views/nodes/ImageBox.tsx127
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx7
-rw-r--r--src/client/views/nodes/LabelBox.scss (renamed from src/client/views/nodes/ButtonBox.scss)26
-rw-r--r--src/client/views/nodes/LabelBox.tsx89
-rw-r--r--src/client/views/nodes/LinkAnchorBox.scss (renamed from src/client/views/nodes/DocuLinkBox.scss)6
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx (renamed from src/client/views/nodes/DocuLinkBox.tsx)101
-rw-r--r--src/client/views/nodes/LinkBox.tsx6
-rw-r--r--src/client/views/nodes/PDFBox.tsx9
-rw-r--r--src/client/views/nodes/PresBox.tsx164
-rw-r--r--src/client/views/nodes/QueryBox.tsx4
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx44
-rw-r--r--src/client/views/nodes/ScriptingBox.scss36
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx98
-rw-r--r--src/client/views/nodes/SliderBox.tsx51
-rw-r--r--src/client/views/nodes/VideoBox.tsx81
-rw-r--r--src/client/views/nodes/WebBox.tsx8
-rw-r--r--src/client/views/pdf/Annotation.tsx4
-rw-r--r--src/client/views/pdf/PDFMenu.tsx2
-rw-r--r--src/client/views/pdf/PDFViewer.tsx16
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx12
-rw-r--r--src/client/views/search/FilterBox.tsx2
-rw-r--r--src/client/views/search/IconBar.tsx14
-rw-r--r--src/client/views/search/IconButton.tsx8
-rw-r--r--src/client/views/search/SearchBox.tsx52
-rw-r--r--src/client/views/search/SearchItem.tsx9
85 files changed, 2174 insertions, 1716 deletions
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 454c245cc..f19f9308a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -5,10 +5,10 @@ import { Cast } from '../../new_fields/Types';
import { listSpec } from '../../new_fields/Schema';
import { InkingControl } from './InkingControl';
import { InkTool } from '../../new_fields/InkField';
-import { PositionDocument } from '../../new_fields/documentSchemas';
+import { InteractionUtils } from '../util/InteractionUtils';
-/// DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, ButtonBox)
+/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
interface DocComponentProps {
Document: Doc;
LayoutDoc?: () => Opt<Doc>;
@@ -17,51 +17,77 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
class Component extends Touchable<P> {
//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 PositionDocument(Doc.Layout(this.props.Document, this.props.LayoutDoc?.())); }
+ // This is the "The Document" -- it encapsulates, data, layout, and any templates
+ @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
+ @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+ // This is the data part of a document -- ie, the data that is constant across all views of the document
+ @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
+
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
-/// DocStaticProps return a base class for React document views that have data extensions but aren't annotatable (e.g. AudioBox, FormattedTextBox)
-interface DocExtendableProps {
+/// FieldViewBoxProps - a generic base class for field views that are not annotatable (e.g. AudioBox, FormattedTextBox)
+interface ViewBoxBaseProps {
Document: Doc;
DataDoc?: Doc;
fieldKey: string;
isSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
- rootSelected: () => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
}
-export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
+export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor: (doc: Doc) => T) {
class Component extends Touchable<P> {
//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 Document(): T { return schemaCtor(this.props.Document); }
+
+ // This is the "The Document" -- it encapsulates, data, layout, and any templates
+ @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
@computed get layoutDoc() { return Doc.Layout(this.props.Document); }
- @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.rootSelected()) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
+ // This is the data part of a document -- ie, the data that is constant across all views of the document
+ @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
+
+ // key where data is stored
+ @computed get fieldKey() { return this.props.fieldKey; }
+
+ active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
-/// DocAnnotatbleComponent return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
-export interface DocAnnotatableProps {
+/// DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
+export interface ViewBoxAnnotatableProps {
Document: Doc;
DataDoc?: Doc;
fieldKey: string;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
isSelected: (outsideReaction?: boolean) => boolean;
- rootSelected: () => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
}
-export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
+export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
class Component extends Touchable<P> {
@observable _isChildActive = false;
//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); }
+
+ // This is the "The Document" -- it encapsulates, data, layout, and any templates
+ @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
+ @computed get layoutDoc() { return schemaCtor(Doc.Layout(this.props.Document)); }
+ // This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
+ // key where data is stored
+ @computed get fieldKey() { return this.props.fieldKey; }
+
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
_annotationKey: string = "annotations";
public set annotationKey(val: string) { this._annotationKey = val; }
@@ -82,14 +108,14 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
}
@action.bound
addDocument(doc: Doc): boolean {
- Doc.GetProto(doc).annotationOn = this.props.Document;
+ doc.context = Doc.GetProto(doc).annotationOn = this.props.Document;
return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false;
}
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.rootSelected()) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
- annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None ||
+ (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+ annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
(this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
}
return Component;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index b95cc6627..5b78008ab 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -119,6 +119,11 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
if (this.view0 && linkDoc) {
Doc.GetProto(linkDoc).linkRelationship = "hyperlink";
+
+ // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
+ // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
+ // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
+ // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
runInAction(() => this.view0!._link = linkDoc);
setTimeout(action(() => this.view0!._link = undefined), 0);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c35263237..c49fe157c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -69,7 +69,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
get Bounds(): { x: number, y: number, b: number, r: number } {
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.renderDepth === 0 ||
- documentView.dontDecorateSelection ||
Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
return bounds;
}
@@ -77,7 +76,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
var [sptX, sptY] = transform.transformPoint(0, 0);
let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
if (documentView.props.Document.type === DocumentType.LINK) {
- const docuBox = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont");
+ const docuBox = documentView.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
if (docuBox.length) {
const rect = docuBox[0].getBoundingClientRect();
sptX = rect.left;
@@ -394,7 +393,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (SelectionManager.SelectedDocuments().length === 1) {
const selected = SelectionManager.SelectedDocuments()[0];
if (this._titleControlString.startsWith("=")) {
- return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ this: selected.props.Document }, console.log).result?.toString() || "";
+ return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
}
if (this._titleControlString.startsWith("#")) {
return selected.props.Document[this._titleControlString.substring(1)]?.toString() || "-unset-";
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 26bee9a6f..1977f2406 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -15,7 +15,7 @@ import { Scripting } from "../util/Scripting";
import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types";
import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
import HorizontalPalette from "./Palette";
-import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange } from "../../Utils";
+import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils";
import { DocumentView } from "./nodes/DocumentView";
import { Transform } from "../util/Transform";
import { DocumentContentsView } from "./nodes/DocumentContentsView";
@@ -82,6 +82,9 @@ export default class GestureOverlay extends Touchable {
this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
}
+ /**
+ * Ignores all touch events that belong to a hand being held down.
+ */
getNewTouches(e: React.TouchEvent | TouchEvent) {
const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches);
const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches);
@@ -121,6 +124,8 @@ export default class GestureOverlay extends Touchable {
return;
}
+ // this chunk adds new touch targets to a map of pointer events; this helps us keep track of individual fingers
+ // so that we can know, for example, if two fingers are pinching out or in.
const actualPts: React.Touch[] = [];
for (let i = 0; i < te.touches.length; i++) {
const pt: any = te.touches.item(i);
@@ -128,9 +133,6 @@ export default class GestureOverlay extends Touchable {
// 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);
}
}
@@ -144,6 +146,7 @@ export default class GestureOverlay extends Touchable {
ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
const nts = this.getNewTouches(te);
+ // if there are fewer than five touch events, handle as a touch event
if (nts.nt.length < 5) {
const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
target?.dispatchEvent(
@@ -161,7 +164,7 @@ export default class GestureOverlay extends Touchable {
)
);
if (nts.nt.length === 1) {
- console.log("started");
+ // -- radial menu code --
this._holdTimer = setTimeout(() => {
console.log("hold");
const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
@@ -193,13 +196,14 @@ export default class GestureOverlay extends Touchable {
}, (500));
}
else {
- clearTimeout(this._holdTimer);
+ this._holdTimer && clearTimeout(this._holdTimer);
}
document.removeEventListener("touchmove", this.onReactTouchMove);
document.removeEventListener("touchend", this.onReactTouchEnd);
document.addEventListener("touchmove", this.onReactTouchMove);
document.addEventListener("touchend", this.onReactTouchEnd);
}
+ // otherwise, handle as a hand event
else {
this.handleHandDown(te);
document.removeEventListener("touchmove", this.onReactTouchMove);
@@ -207,70 +211,9 @@ export default class GestureOverlay extends Touchable {
}
}
- onReactHoldTouchMove = (e: TouchEvent) => {
- document.removeEventListener("touchmove", this.onReactTouchMove);
- document.removeEventListener("touchend", this.onReactTouchEnd);
- document.removeEventListener("touchmove", this.onReactHoldTouchMove);
- document.removeEventListener("touchend", this.onReactHoldTouchEnd);
- document.addEventListener("touchmove", this.onReactHoldTouchMove);
- document.addEventListener("touchend", this.onReactHoldTouchEnd);
- const nts: any = this.getNewTouches(e);
- if (this.prevPoints.size === 1 && this._holdTimer) {
- clearTimeout(this._holdTimer);
- }
- document.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldMove",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: e
- }
- })
- );
- }
-
- onReactHoldTouchEnd = (e: TouchEvent) => {
- const nts: any = this.getNewTouches(e);
- if (this.prevPoints.size === 1 && this._holdTimer) {
- clearTimeout(this._holdTimer);
- this._holdTimer = undefined;
- }
- document.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldEnd",
- {
- 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);
- }
- }
- }
-
- document.removeEventListener("touchmove", this.onReactHoldTouchMove);
- document.removeEventListener("touchend", this.onReactHoldTouchEnd);
-
- e.stopPropagation();
- }
-
-
onReactTouchMove = (e: TouchEvent) => {
const nts: any = this.getNewTouches(e);
- clearTimeout(this._holdTimer);
+ this._holdTimer && clearTimeout(this._holdTimer);
this._holdTimer = undefined;
document.dispatchEvent(
@@ -290,7 +233,7 @@ export default class GestureOverlay extends Touchable {
onReactTouchEnd = (e: TouchEvent) => {
const nts: any = this.getNewTouches(e);
- clearTimeout(this._holdTimer);
+ this._holdTimer && clearTimeout(this._holdTimer);
this._holdTimer = undefined;
document.dispatchEvent(
@@ -306,6 +249,8 @@ export default class GestureOverlay extends Touchable {
}
})
);
+
+ // cleanup any lingering pointers
for (let i = 0; i < e.changedTouches.length; i++) {
const pt = e.changedTouches.item(i);
if (pt) {
@@ -323,7 +268,11 @@ export default class GestureOverlay extends Touchable {
}
handleHandDown = async (e: React.TouchEvent) => {
- clearTimeout(this._holdTimer!);
+ this._holdTimer && clearTimeout(this._holdTimer);
+
+ // this chunk of code helps us keep track of which touch events are associated with a hand event
+ // so that if a hand is held down, but a second hand is interacting with dash, the second hand's events
+ // won't interfere with the first hand's events.
const fingers = new Array<React.Touch>();
for (let i = 0; i < e.touches.length; i++) {
const pt: any = e.touches.item(i);
@@ -338,6 +287,8 @@ export default class GestureOverlay extends Touchable {
}
}
}
+
+ // this chunk of code determines whether this is a left hand or a right hand, as well as which pointer is the thumb and pointer
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));
@@ -354,6 +305,7 @@ export default class GestureOverlay extends Touchable {
console.log("not hand");
}
this.pointerIdentifier = pointer?.identifier;
+
runInAction(() => {
this._pointerY = pointer?.clientY;
if (thumb.identifier === this.thumbIdentifier) {
@@ -370,6 +322,7 @@ export default class GestureOverlay extends Touchable {
const minX = Math.min(...others.map(f => f.clientX));
const minY = Math.min(...others.map(f => f.clientY));
+ // load up the palette collection around the thumb
const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc);
if (thumbDoc) {
runInAction(() => {
@@ -393,6 +346,7 @@ export default class GestureOverlay extends Touchable {
@action
handleHandMove = (e: TouchEvent) => {
+ // update pointer trackers
const fingers = new Array<React.Touch>();
for (let i = 0; i < e.touches.length; i++) {
const pt: any = e.touches.item(i);
@@ -411,15 +365,19 @@ export default class GestureOverlay extends Touchable {
}
}
}
+ // update hand trackers
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);
}
+ // loop through every changed pointer
for (let i = 0; i < e.changedTouches.length; i++) {
const pt = e.changedTouches.item(i);
+ // if the thumb was moved
if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) {
if (this._thumbX && this._thumbY) {
+ // moving a thumb horiz. changes the palette collection selection, moving vert. changes the selection of any menus on the current palette item
const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY);
if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) {
if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) {
@@ -433,19 +391,8 @@ export default class GestureOverlay extends Touchable {
}
}
}
-
- // if (this._thumbX && this._thumbDoc) {
- // if (Math.abs(pt.clientX - this._thumbX) > 30) {
- // this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
- // this._thumbX = pt.clientX;
- // }
- // }
- // if (this._thumbY && this._inkToTextDoc) {
- // if (Math.abs(pt.clientY - this._thumbY) > 20) {
- // this._selectedIndex = Math.min(Math.max(0, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1);
- // }
- // }
}
+ // if the pointer finger was moved
if (pt && pt.identifier === this.pointerIdentifier) {
this._pointerY = pt.clientY;
}
@@ -454,27 +401,31 @@ export default class GestureOverlay extends Touchable {
@action
handleHandUp = (e: TouchEvent) => {
+ // sometimes, users may lift up their thumb or index finger if they can't stretch far enough to scroll an entire menu,
+ // so we don't want to just remove the palette when that happens
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;
+ // this chunk of code is for handling the ink to text toolglass
let scriptWorked = false;
if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
+ // if there is a text option selected, activate it
const selectedButton = this._possibilities[this._selectedIndex];
if (selectedButton) {
selectedButton.props.onClick();
scriptWorked = true;
}
}
-
+ // if there isn't a text option selected, dry the ink strokes into ink documents
if (!scriptWorked) {
this._strokes.forEach(s => {
this.dispatchGesture(GestureUtils.Gestures.Stroke, s);
});
}
+
this._strokes = [];
this._points = [];
this._possibilities = [];
@@ -482,6 +433,72 @@ export default class GestureOverlay extends Touchable {
}
}
+ /**
+ * Code for radial menu
+ */
+ onReactHoldTouchMove = (e: TouchEvent) => {
+ document.removeEventListener("touchmove", this.onReactTouchMove);
+ document.removeEventListener("touchend", this.onReactTouchEnd);
+ document.removeEventListener("touchmove", this.onReactHoldTouchMove);
+ document.removeEventListener("touchend", this.onReactHoldTouchEnd);
+ document.addEventListener("touchmove", this.onReactHoldTouchMove);
+ document.addEventListener("touchend", this.onReactHoldTouchEnd);
+ const nts: any = this.getNewTouches(e);
+ if (this.prevPoints.size === 1 && this._holdTimer) {
+ clearTimeout(this._holdTimer);
+ }
+ document.dispatchEvent(
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldMove",
+ {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e
+ }
+ })
+ );
+ }
+
+ /**
+ * Code for radial menu
+ */
+ onReactHoldTouchEnd = (e: TouchEvent) => {
+ const nts: any = this.getNewTouches(e);
+ if (this.prevPoints.size === 1 && this._holdTimer) {
+ clearTimeout(this._holdTimer);
+ this._holdTimer = undefined;
+ }
+ document.dispatchEvent(
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldEnd",
+ {
+ 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);
+ }
+ }
+ }
+
+ document.removeEventListener("touchmove", this.onReactHoldTouchMove);
+ document.removeEventListener("touchend", this.onReactHoldTouchEnd);
+
+ e.stopPropagation();
+ }
+
@action
onPointerDown = (e: React.PointerEvent) => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
@@ -524,22 +541,28 @@ export default class GestureOverlay extends Touchable {
handleLineGesture = (): boolean => {
let actionPerformed = false;
const B = this.svgBounds;
+
+ // get the two targets at the ends of the line
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);
+
+ // callback function to be called by each target
const callback = (doc: Doc) => {
if (!this._d1) {
this._d1 = doc;
}
+ // we don't want to create a link of both endpoints are the same document (doing so makes drawing an l very hard)
else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
+ // we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
if (this._d1.type !== "ink" && doc.type !== "ink") {
DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link");
actionPerformed = true;
}
}
};
+
const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
{
bubbles: true,
@@ -575,6 +598,7 @@ export default class GestureOverlay extends Touchable {
const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height);
const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+ // if a toolglass is selected and the stroke starts within the toolglass boundaries
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
switch (this.Tool) {
case ToolglassTools.InkToText:
@@ -583,20 +607,19 @@ export default class GestureOverlay extends Touchable {
this._strokes.push(new Array(...this._points));
this._points = [];
CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
- console.log(results);
const wordResults = results.filter((r: any) => r.category === "line");
const possibilities: string[] = [];
for (const wR of wordResults) {
- console.log(wR);
if (wR?.recognizedText) {
possibilities.push(wR?.recognizedText);
}
possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString));
}
- console.log(possibilities);
const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
+
+ // if we receive any word results from cognitive services, display them
runInAction(() => {
this._possibilities = possibilities.map(p =>
<TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />);
@@ -609,6 +632,7 @@ export default class GestureOverlay extends Touchable {
break;
}
}
+ // if we're not drawing in a toolglass try to recognize as gesture
else {
const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
let actionPerformed = false;
@@ -623,7 +647,7 @@ export default class GestureOverlay extends Touchable {
actionPerformed = true;
break;
case GestureUtils.Gestures.EndBracket:
- this.dispatchGesture(GestureUtils.Gestures.EndBracket);
+ this.dispatchGesture("endbracket");
actionPerformed = true;
break;
case GestureUtils.Gestures.Line:
@@ -638,6 +662,7 @@ export default class GestureOverlay extends Touchable {
}
}
+ // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
@@ -648,7 +673,7 @@ export default class GestureOverlay extends Touchable {
document.removeEventListener("pointerup", this.onPointerUp);
}
- dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => {
+ dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
target?.dispatchEvent(
new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
@@ -656,7 +681,7 @@ export default class GestureOverlay extends Touchable {
bubbles: true,
detail: {
points: stroke ?? this._points,
- gesture: gesture,
+ gesture: gesture as any,
bounds: this.getBounds(stroke ?? this._points),
text: data
}
@@ -695,7 +720,8 @@ export default class GestureOverlay extends Touchable {
</svg>]
];
}
-
+ screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1);
+ return300 = () => 300;
@action
public openFloatingDoc = (doc: Doc) => {
this._clipboardDoc =
@@ -709,10 +735,12 @@ export default class GestureOverlay extends Touchable {
pinToPres={emptyFunction}
onClick={undefined}
removeDocument={undefined}
- ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
+ ScreenToLocalTransform={this.screenToLocalTransform}
ContentScaling={returnOne}
- PanelWidth={() => 300}
- PanelHeight={() => 300}
+ PanelWidth={this.return300}
+ PanelHeight={this.return300}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
renderDepth={0}
backgroundColor={returnEmptyString}
focus={emptyFunction}
@@ -721,8 +749,6 @@ export default class GestureOverlay extends Touchable {
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}
/>;
}
@@ -737,7 +763,6 @@ export default class GestureOverlay extends Touchable {
}
render() {
- trace();
return (
<div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
{this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
@@ -762,9 +787,6 @@ export default class GestureOverlay extends Touchable {
}}>
</div>
<TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
- {/* <div className="pointerBubbles">
- {this._pointers.map(p => <div className="bubble" style={{ translate: `transform(${p.clientX}px, ${p.clientY}px)` }}></div>)}
- </div> */}
</div>);
}
}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 52801b570..d01f3f1e5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -8,7 +8,7 @@ import { Doc } from "../../new_fields/Doc";
import { DictationManager } from "../util/DictationManager";
import SharingManager from "../util/SharingManager";
import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-import { Cast, PromiseValue } from "../../new_fields/Types";
+import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
import { ScriptField } from "../../new_fields/ScriptField";
import { InkingControl } from "./InkingControl";
import { InkTool } from "../../new_fields/InkField";
@@ -89,13 +89,20 @@ export default class KeyManager {
return { stopPropagation: false, preventDefault: false };
}
}
- UndoManager.RunInBatch(() => {
- SelectionManager.SelectedDocuments().map(docView => {
- const doc = docView.props.Document;
- const remove = docView.props.removeDocument;
- remove && remove(doc);
- });
- }, "delete");
+ UndoManager.RunInBatch(() =>
+ SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
+ break;
+ case "arrowleft":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-1, 0)), "nudge left");
+ break;
+ case "arrowright":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(1, 0)), "nudge right");
+ break;
+ case "arrowup":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -1)), "nudge up");
+ break;
+ case "arrowdown":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 1)), "nudge down");
break;
}
@@ -114,6 +121,18 @@ export default class KeyManager {
// DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
// stopPropagation = true;
// preventDefault = true;
+ case "arrowleft":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-10, 0)), "nudge left");
+ break;
+ case "arrowright":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(10, 0)), "nudge right");
+ break;
+ case "arrowup":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -10)), "nudge up");
+ break;
+ case "arrowdown":
+ UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 10)), "nudge down");
+ break;
}
return {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a791eed40..f66c04e1f 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -3,8 +3,8 @@ import { observer } from "mobx-react";
import { documentSchema } from "../../new_fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../new_fields/InkField";
import { makeInterface } from "../../new_fields/Schema";
-import { Cast } from "../../new_fields/Types";
-import { DocExtendableComponent } from "./DocComponent";
+import { Cast, StrCast, NumCast } from "../../new_fields/Types";
+import { ViewBoxBaseComponent } from "./DocComponent";
import { InkingControl } from "./InkingControl";
import "./InkingStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
@@ -22,31 +22,30 @@ type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@observer
-export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
+export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
- @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]);
+ const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
}
render() {
TraceMobx();
- const data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? [];
+ const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
const left = Math.min(...xs);
const top = Math.min(...ys);
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- const points = InteractionUtils.CreatePolyline(data, left, top, this.Document.color ?? InkingControl.Instance.selectedColor, this.Document.strokeWidth ?? parseInt(InkingControl.Instance.selectedWidth));
+ const points = InteractionUtils.CreatePolyline(data, left, top,
+ StrCast(this.layoutDoc.color, InkingControl.Instance.selectedColor),
+ NumCast(this.layoutDoc.strokeWidth, parseInt(InkingControl.Instance.selectedWidth)));
const width = right - left;
const height = bottom - top;
- const scaleX = this.PanelWidth / width;
- const scaleY = this.PanelHeight / height;
+ const scaleX = this.props.PanelWidth() / width;
+ const scaleY = this.props.PanelHeight() / height;
return (
<svg
width={width}
@@ -54,7 +53,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
style={{
transformOrigin: "top left",
transform: `scale(${scaleX}, ${scaleY})`,
- mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
pointerEvents: "all"
}}
onContextMenu={() => {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 6d705aa44..b21eb9c8f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,7 +5,6 @@ import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { DocServer } from "../DocServer";
import { AssignAllExtensions } from "../../extensions/General/Extensions";
-process.env.HANDWRITING = "61088486d76c4b12ba578775a5f55422";
AssignAllExtensions();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fae520e40..aec1f960a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,54 +1,46 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import {
- 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 { faTerminal, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import Measure from 'react-measure';
-import { Doc, DocListCast, Field, FieldResult, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
import { List } from '../../new_fields/List';
import { listSpec } from '../../new_fields/Schema';
-import { Cast, FieldValue, StrCast, BoolCast } from '../../new_fields/Types';
+import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { TraceMobx } from '../../new_fields/util';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils, emptyPath } from '../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
import { HistoryUtil } from '../util/History';
+import RichTextMenu from '../util/RichTextMenu';
+import { Scripting } from '../util/Scripting';
+import SettingsManager from '../util/SettingsManager';
import SharingManager from '../util/SharingManager';
import { Transform } from '../util/Transform';
-import { CollectionLinearView } from './collections/CollectionLinearView';
-import { CollectionViewType, CollectionView } from './collections/CollectionView';
import { CollectionDockingView } from './collections/CollectionDockingView';
+import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import { CollectionLinearView } from './collections/CollectionLinearView';
+import { CollectionView, CollectionViewType } from './collections/CollectionView';
import { ContextMenu } from './ContextMenu';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
+import GestureOverlay from './GestureOverlay';
import KeyManager from './GlobalKeyHandler';
import "./MainView.scss";
import { MainViewNotifs } from './MainViewNotifs';
+import { AudioBox } from './nodes/AudioBox';
import { DocumentView } from './nodes/DocumentView';
+import { RadialMenu } from './nodes/RadialMenu';
+import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
-import { FilterBox } from './search/FilterBox';
-import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
-//import { DocumentManager } from '../util/DocumentManager';
-import { RecommendationsBox } from './RecommendationsBox';
-import { PresBox } from './nodes/PresBox';
-import { OverlayView } from './OverlayView';
-import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-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';
-import { DocumentType } from '../documents/DocumentTypes';
@observer
export class MainView extends React.Component {
@@ -109,6 +101,7 @@ export class MainView extends React.Component {
}
}
+ library.add(faTerminal);
library.add(faFileAlt);
library.add(faStickyNote);
library.add(faFont);
@@ -283,7 +276,7 @@ export class MainView extends React.Component {
defaultBackgroundColors = (doc: Doc) => {
if (this.darkScheme) {
switch (doc.type) {
- case DocumentType.TEXT || DocumentType.BUTTON: return "#2d2d2d";
+ case DocumentType.RTF || DocumentType.LABEL || DocumentType.BUTTON: return "#2d2d2d";
case DocumentType.LINK:
case DocumentType.COL: {
if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
@@ -292,8 +285,9 @@ export class MainView extends React.Component {
}
} else {
switch (doc.type) {
- case DocumentType.TEXT: return "#f1efeb";
- case DocumentType.BUTTON: return "lightgray";
+ case DocumentType.RTF: return "#f1efeb";
+ case DocumentType.BUTTON:
+ case DocumentType.LABEL: return "lightgray";
case DocumentType.LINK:
case DocumentType.COL: {
if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray";
@@ -315,6 +309,8 @@ export class MainView extends React.Component {
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={this.getPWidth}
PanelHeight={this.getPHeight}
renderDepth={0}
@@ -324,8 +320,6 @@ export class MainView extends React.Component {
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}
/>;
}
@computed get dockingContent() {
@@ -415,6 +409,8 @@ export class MainView extends React.Component {
onClick={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={this.flyoutWidthFunc}
PanelHeight={this.getPHeight}
renderDepth={0}
@@ -424,10 +420,7 @@ export class MainView extends React.Component {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}>
- </DocumentView>
+ ContainingCollectionDoc={undefined} />
</div>
<div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${this._buttonBarHeight}px)`, width: "100%", overflow: "visible" }}>
<DocumentView
@@ -437,6 +430,8 @@ export class MainView extends React.Component {
addDocument={undefined}
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
rootSelected={returnTrue}
removeDocument={returnFalse}
onClick={undefined}
@@ -451,10 +446,7 @@ export class MainView extends React.Component {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}>
- </DocumentView>
+ ContainingCollectionDoc={undefined} />
<button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
Settings
</button>
@@ -540,6 +532,8 @@ export class MainView extends React.Component {
onClick={undefined}
ScreenToLocalTransform={this.buttonBarXf}
ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={this.flyoutWidthFunc}
PanelHeight={this.getContentsHeight}
renderDepth={0}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7587071db..4000cade5 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -182,6 +182,8 @@ export class OverlayView extends React.Component {
addDocument={undefined}
removeDocument={undefined}
ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={returnOne}
PanelHeight={returnOne}
ScreenToLocalTransform={Transform.Identity}
@@ -193,9 +195,7 @@ export class OverlayView extends React.Component {
addDocTab={returnFalse}
pinToPres={emptyFunction}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne} />
+ ContainingCollectionDoc={undefined}/>
</div>;
});
}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 674b27918..63744cb50 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc } from "../../new_fields/Doc";
import { NumCast } from "../../new_fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils";
import { Transform } from "../util/Transform";
import { DocumentView } from "./nodes/DocumentView";
import "./Palette.scss";
@@ -49,6 +49,8 @@ export default class Palette extends React.Component<PaletteProps> {
onClick={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={() => window.screen.width}
PanelHeight={() => window.screen.height}
renderDepth={0}
@@ -58,10 +60,7 @@ export default class Palette extends React.Component<PaletteProps> {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}>
- </DocumentView>
+ ContainingCollectionDoc={undefined} />
<div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
</div>
</div>
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index c011adb20..df30c1215 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -13,6 +13,7 @@ export class PreviewCursor extends React.Component<{}> {
static _getTransform: () => Transform;
static _addLiveTextDoc: (doc: Doc) => void;
static _addDocument: (doc: Doc) => boolean;
+ static _nudge: (x: number, y: number) => boolean;
@observable static _clickPoint = [0, 0];
@observable public static Visible = false;
constructor(props: any) {
@@ -85,9 +86,19 @@ export class PreviewCursor extends React.Component<{}> {
!e.key.startsWith("Arrow") &&
!e.defaultPrevented) {
if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
- PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+ PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
PreviewCursor.Visible = false;
}
+ } else if (PreviewCursor.Visible) {
+ if (e.key === "ArrowRight") {
+ PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+ } else if (e.key === "ArrowLeft") {
+ PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+ } else if (e.key === "ArrowUp") {
+ PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+ } else if (e.key === "ArrowDown") {
+ PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+ }
}
}
@@ -101,12 +112,14 @@ export class PreviewCursor extends React.Component<{}> {
onKeyPress: (e: KeyboardEvent) => void,
addLiveText: (doc: Doc) => void,
getTransform: () => Transform,
- addDocument: (doc: Doc) => boolean) {
+ addDocument: (doc: Doc) => boolean,
+ nudge: (nudgeX: number, nudgeY: number) => boolean) {
this._clickPoint = [x, y];
this._onKeyPress = onKeyPress;
this._addLiveTextDoc = addLiveText;
this._getTransform = getTransform;
this._addDocument = addDocument;
+ this._nudge = nudge;
this.Visible = true;
}
render() {
diff --git a/src/client/views/RecommendationsBox.scss b/src/client/views/RecommendationsBox.scss
index dd8a105f6..7d89042a4 100644
--- a/src/client/views/RecommendationsBox.scss
+++ b/src/client/views/RecommendationsBox.scss
@@ -54,10 +54,11 @@
margin-left: 5px;
}
-img{
- width: 100%;
- height: 100%;
-}
+// bcz: UGH!! Can't have global settings like this!!!
+// img{
+// width: 100%;
+// height: 100%;
+// }
.score {
// margin-left: 15px;
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index bf8de36b4..e66fd3eb4 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -6,7 +6,7 @@ import "./RecommendationsBox.scss";
import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
import { DocumentIcon } from "./nodes/DocumentIcon";
import { StrCast, NumCast } from "../../new_fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath } from "../../Utils";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils";
import { Transform } from "../util/Transform";
import { ObjectField } from "../../new_fields/ObjectField";
import { DocumentView } from "./nodes/DocumentView";
@@ -73,6 +73,8 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
addDocTab={returnFalse}
pinToPres={returnFalse}
renderDepth={1}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={returnXDimension}
PanelHeight={returnYDimension}
focus={emptyFunction}
@@ -80,8 +82,6 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
- zoomToScale={emptyFunction}
- getScale={returnOne}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={scale}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index cc5d7640e..153b81876 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -12,6 +12,7 @@ import { CompileScript } from "../util/Scripting";
import { ScriptField } from "../../new_fields/ScriptField";
import { DragManager } from "../util/DragManager";
import { EditableView } from "./EditableView";
+import { getEffectiveTypeRoots } from "typescript";
export interface ScriptBoxProps {
onSave: (text: string, onError: (error: string) => void) => void;
@@ -43,14 +44,12 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
overlayDisposer?: () => void;
onFocus = () => {
- if (this.overlayDisposer) {
- this.overlayDisposer();
- }
+ this.overlayDisposer?.();
this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
}
onBlur = () => {
- this.overlayDisposer && this.overlayDisposer();
+ this.overlayDisposer?.();
}
render() {
@@ -94,7 +93,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
const setParams = (p: string[]) => params.splice(0, params.length, ...p);
const scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={overlayDisposer} onSave={(text, onError) => {
if (!text) {
- doc[fieldKey] = undefined;
+ Doc.GetProto(doc)[fieldKey] = undefined;
} else {
const script = CompileScript(text, {
params: { this: Doc.name, ...contextParams },
@@ -117,10 +116,10 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
div.innerHTML = "button";
params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY);
- doc[fieldKey] = new ScriptField(script);
+ Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
overlayDisposer();
}
}} showDocumentIcons />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: title });
+ overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title });
}
}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 87ffb432d..b76137f06 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast } from "../../new_fields/Doc";
import { Docs, } from "../documents/Documents";
import { StrCast, Cast } from "../../new_fields/Types";
import { CollectionTreeView } from "./collections/CollectionTreeView";
-import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath } from "../../Utils";
+import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils";
import { Transform } from "../util/Transform";
import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
import { Scripting } from "../util/Scripting";
@@ -76,7 +76,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@undoBatch
@action
toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
- this.props.docViews.forEach(d => Doc.Layout(d.Document)["_show" + template.Name] = event.target.checked ? template.Name.toLowerCase() : "");
+ this.props.docViews.forEach(d => Doc.Layout(d.layoutDoc)["_show" + template.Name] = event.target.checked ? template.Name.toLowerCase() : "");
}
@action
@@ -87,7 +87,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@undoBatch
@action
toggleChrome = (): void => {
- this.props.docViews.map(dv => Doc.Layout(dv.Document)).forEach(layout =>
+ this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout =>
layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : StrCast(layout._replacedChrome, "enabled")));
}
@@ -124,62 +124,62 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
if (noteTypesDoc) {
- addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(this, "${StrCast(firstDoc.title)}")`, { firstDoc: "string" }));
+ addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self)`));
this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
<OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
- templateMenu.push(
- <CollectionTreeView
- Document={Doc.UserDoc().templateDocs as Doc}
- CollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- onCheckedClick={this.scriptField!}
- onChildClick={this.scriptField!}
- LibraryPath={emptyPath}
- dropAction={undefined}
- active={returnTrue}
- ContentScaling={returnOne}
- bringToFront={emptyFunction}
- focus={emptyFunction}
- whenActiveChanged={emptyFunction}
- ScreenToLocalTransform={Transform.Identity}
- isSelected={returnFalse}
- pinToPres={emptyFunction}
- select={emptyFunction}
- renderDepth={1}
- addDocTab={returnFalse}
- PanelWidth={this.return100}
- PanelHeight={this.return100}
- treeViewHideHeaderFields={true}
- annotationsKey={""}
- dontRegisterView={true}
- fieldKey={"data"}
- moveDocument={(doc: Doc) => false}
- removeDocument={(doc: Doc) => false}
- addDocument={(doc: Doc) => false} />
- );
}
return <ul className="template-list" style={{ display: "block" }}>
- <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress}></input>
+ <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />
{templateMenu}
+ <CollectionTreeView
+ Document={Doc.UserDoc().templateDocs as Doc}
+ CollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ rootSelected={returnFalse}
+ onCheckedClick={this.scriptField!}
+ onChildClick={this.scriptField!}
+ LibraryPath={emptyPath}
+ dropAction={undefined}
+ active={returnTrue}
+ ContentScaling={returnOne}
+ bringToFront={emptyFunction}
+ focus={emptyFunction}
+ whenActiveChanged={emptyFunction}
+ ScreenToLocalTransform={Transform.Identity}
+ isSelected={returnFalse}
+ pinToPres={emptyFunction}
+ select={emptyFunction}
+ renderDepth={1}
+ addDocTab={returnFalse}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ PanelWidth={this.return100}
+ PanelHeight={this.return100}
+ treeViewHideHeaderFields={true}
+ annotationsKey={""}
+ dontRegisterView={true}
+ fieldKey={"data"}
+ moveDocument={(doc: Doc) => false}
+ removeDocument={(doc: Doc) => false}
+ addDocument={(doc: Doc) => false} />
</ul>;
}
}
-Scripting.addGlobal(function switchView(doc: Doc, template: Doc) {
- if (template.dragFactory) {
+Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
+ if (template?.dragFactory) {
template = Cast(template.dragFactory, Doc, null);
}
const templateTitle = StrCast(template?.title);
return templateTitle && DocumentView.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
});
-Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, firstDocTitle: string) {
+Scripting.addGlobal(function templateIsUsed(templateDoc: Doc) {
const firstDoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0].props.Document : undefined;
if (firstDoc) {
const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
return StrCast(firstDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
}
return false;
- // return SelectionManager.SelectedDocuments().some(view => StrCast(view.props.Document.layoutKey) === "layout_" + template) ? 'check' : 'unchecked'
}); \ No newline at end of file
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index bc3d07130..10d023d83 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -42,7 +42,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
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) {
+ if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) {
this.prevPoints.set(pt.identifier, pt);
}
}
@@ -64,20 +64,15 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
case 1:
this.handle1PointerDown(te, me);
te.persist();
+ // -- code for radial menu --
// if (this.holdTimer) {
// clearTimeout(this.holdTimer)
// this.holdTimer = undefined;
// }
- // console.log(this.holdTimer);
- // console.log(this.holdTimer);
break;
case 2:
this.handle2PointersDown(te, me);
- // e.stopPropagation();
break;
- // case 5:
- // this.handleHandDown(te);
- // break;
}
}
}
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index fd1296286..a9a1898f5 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -13,6 +13,7 @@
height: calc(100% - 50px);
display: inline-block;
width: 100%;
+ user-select: none;
}
}
.carouselView-back, .carouselView-fwd {
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index a0cb1fe19..9e7248db2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -80,10 +80,33 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
});
}
}
+ _downX = 0;
+ _downY = 0;
+ onPointerDown = (e: React.PointerEvent) => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ console.log("CAROUSEL down");
+ document.addEventListener("pointerup", this.onpointerup);
+ }
+ private _lastTap: number = 0;
+ private _doubleTap = false;
+ onpointerup = (e: PointerEvent) => {
+ console.log("CAROUSEL up");
+ 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();
+ }
+
+ onClick = (e: React.MouseEvent) => {
+ if (this._doubleTap) {
+ e.stopPropagation();
+ this.props.Document.isLightboxOpen = true;
+ }
+ }
+
render() {
- return <div className="collectionCarouselView-outer" ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
{this.content}
- {this.buttons}
+ {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4209bd574..d77ef812f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -15,7 +15,7 @@ import { FieldId } from "../../../new_fields/RefField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { TraceMobx } from '../../../new_fields/util';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { emptyFunction, returnOne, returnTrue, Utils } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -773,7 +773,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
return CollectionDockingView.AddRightSplit(doc, libraryPath);
} else if (location === "close") {
return CollectionDockingView.CloseRightSplit(doc);
- } else {
+ } else {// if (location === "inPlace") {
return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
}
}
@@ -794,6 +794,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
ContentScaling={this.contentScaling}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
ScreenToLocalTransform={this.ScreenToLocalTransform}
renderDepth={0}
parentActive={returnTrue}
@@ -803,9 +805,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
addDocTab={this.addDocTab}
pinToPres={DockedFrameRenderer.PinDoc}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne} />;
+ ContainingCollectionDoc={undefined} />;
}
render() {
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index eae9e0220..123a27deb 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -8,6 +8,8 @@
display:flex;
height: 100%;
>label {
+ margin-top: "auto";
+ margin-bottom: "auto";
background: $dark-color;
color: $light-color;
display: inline-block;
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index a6ada75b2..cb0206260 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -3,8 +3,8 @@ 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, Cast } from '../../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse } from '../../../Utils';
+import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../new_fields/Types';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { Transform } from '../../util/Transform';
import "./CollectionLinearView.scss";
@@ -13,7 +13,6 @@ 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,]>;
@@ -28,12 +27,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
private _selectedDisposer?: IReactionDisposer;
componentWillUnmount() {
- this._dropDisposer && this._dropDisposer();
- this._widthDisposer && this._widthDisposer();
- this._selectedDisposer && this._selectedDisposer();
- this.childLayoutPairs.map((pair, ind) => {
- Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
- });
+ this._dropDisposer?.();
+ this._widthDisposer?.();
+ this._selectedDisposer?.();
+ this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
}
componentDidMount() {
@@ -54,11 +51,11 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
selected = pair;
}
else {
- Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
+ ScriptCast(pair.layout.proto?.onPointerUp)?.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);
+ ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
}
}),
{ fireImmediately: true }
@@ -81,14 +78,16 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
render() {
const guid = Utils.GenerateGuid();
const flexDir: any = StrCast(this.Document.flexDirection);
+ const backgroundColor = StrCast(this.props.Document.backgroundColor, "black");
+ const color = StrCast(this.props.Document.color, "white");
return <div className="collectionLinearView-outer">
<div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
- onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
- <label htmlFor={`${guid}`} title="Close Menu" 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") }} >
+ <label htmlFor={`${guid}`} title="Close Menu" style={{ background: backgroundColor === color ? "black" : backgroundColor }}
+ onPointerDown={e => e.stopPropagation()} >
<p>+</p>
</label>
+ <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
+ onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
<div className="collectionLinearView-content" style={{ height: this.dimension(), flexDirection: flexDir }}>
{this.childLayoutPairs.map((pair, ind) => {
@@ -115,6 +114,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
onClick={undefined}
ScreenToLocalTransform={this.getTransform(dref)}
ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
renderDepth={this.props.renderDepth + 1}
@@ -124,10 +125,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}>
- </DocumentView>
+ ContainingCollectionDoc={undefined} />
</div>;
})}
</div>
diff --git a/src/client/views/collections/CollectionMapView.scss b/src/client/views/collections/CollectionMapView.scss
new file mode 100644
index 000000000..870b7fda8
--- /dev/null
+++ b/src/client/views/collections/CollectionMapView.scss
@@ -0,0 +1,30 @@
+.collectionMapView {
+ width: 100%;
+ height: 100%;
+
+ .collectionMapView-contents {
+ width: 100%;
+ height: 100%;
+ > div {
+ position: unset !important; // when the sidebar filter flys out, this prevents the map from extending outside the document box
+ }
+ }
+}
+
+.loadingWrapper {
+ width: 100%;
+ height: 100%;
+ background-color: pink;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+
+ .loadingGif {
+ align-self: center;
+ justify-self: center;
+ width: 50px;
+ height: 50px;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
new file mode 100644
index 000000000..c80555a2e
--- /dev/null
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -0,0 +1,262 @@
+import { GoogleApiWrapper, Map as GeoMap, MapProps, Marker } from "google-maps-react";
+import { observer } from "mobx-react";
+import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../new_fields/Doc";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { makeInterface } from "../../../new_fields/Schema";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
+import "./CollectionMapView.scss";
+import { CollectionSubView } from "./CollectionSubView";
+import React = require("react");
+import { DocumentManager } from "../../util/DocumentManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
+import { computed, runInAction, Lambda, action } from "mobx";
+import requestPromise = require("request-promise");
+
+type MapSchema = makeInterface<[typeof documentSchema]>;
+const MapSchema = makeInterface(documentSchema);
+
+export type LocationData = google.maps.LatLngLiteral & {
+ address?: string
+ resolvedAddress?: string;
+ zoom?: number;
+};
+
+interface DocLatLng {
+ lat: FieldResult<Field>;
+ lng: FieldResult<Field>;
+}
+
+// Nowhere, Oklahoma
+const defaultLocation = { lat: 35.1592238, lng: -98.444512, zoom: 15 };
+const noResults = "ZERO_RESULTS";
+
+const query = async (data: string | google.maps.LatLngLiteral) => {
+ const contents = typeof data === "string" ? `address=${data.replace(/\s+/g, "+")}` : `latlng=${data.lat},${data.lng}`;
+ const target = `https://maps.googleapis.com/maps/api/geocode/json?${contents}&key=${process.env.GOOGLE_MAPS_GEO}`;
+ try {
+ return JSON.parse(await requestPromise.get(target));
+ } catch {
+ return undefined;
+ }
+};
+
+@observer
+class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> & { google: any }>(MapSchema) {
+
+ private _cancelAddrReq = new Map<string, boolean>();
+ private _cancelLocReq = new Map<string, boolean>();
+ private _initialLookupPending = new Map<string, boolean>();
+ private responders: { location: Lambda, address: Lambda }[] = [];
+
+ /**
+ * Note that all the uses of runInAction below are not included
+ * as a way to update observables (documents handle this already
+ * in their property setters), but rather to create a single bulk
+ * update and thus prevent uneeded invocations of the location-
+ * and address–updating reactions.
+ */
+
+ private getLocation = (doc: Opt<Doc>, fieldKey: string, returnDefault: boolean = true): Opt<LocationData> => {
+ if (doc) {
+ const titleLoc = StrCast(doc.title).startsWith("@") ? StrCast(doc.title).substring(1) : undefined;
+ const lat = Cast(doc[`${fieldKey}-lat`], "number", null) || (Cast(doc[`${fieldKey}-lat`], "string", null) && Number(Cast(doc[`${fieldKey}-lat`], "string", null))) || undefined;
+ const lng = Cast(doc[`${fieldKey}-lng`], "number", null) || (Cast(doc[`${fieldKey}-lng`], "string", null) && Number(Cast(doc[`${fieldKey}-lng`], "string", null))) || undefined;
+ const zoom = Cast(doc[`${fieldKey}-zoom`], "number", null) || (Cast(doc[`${fieldKey}-zoom`], "string", null) && Number(Cast(doc[`${fieldKey}-zoom`], "string", null))) || undefined;
+ const address = titleLoc || StrCast(doc[`${fieldKey}-address`], StrCast(doc.title).replace(/^-/, ""));
+ if (titleLoc || (address && (lat === undefined || lng === undefined))) {
+ const id = doc[Id];
+ if (!this._initialLookupPending.get(id)) {
+ this._initialLookupPending.set(id, true);
+ setTimeout(() => {
+ titleLoc && Doc.SetInPlace(doc, "title", titleLoc, true);
+ this.respondToAddressChange(doc, fieldKey, address).then(() => this._initialLookupPending.delete(id));
+ });
+ }
+ }
+ return (lat === undefined || lng === undefined) ? (returnDefault ? defaultLocation : undefined) : { lat, lng, zoom };
+ }
+ return undefined;
+ }
+
+ private markerClick = async (layout: Doc, { lat, lng, zoom }: LocationData) => {
+ const batch = UndoManager.StartBatch("marker click");
+ const { fieldKey } = this.props;
+ runInAction(() => {
+ this.layoutDoc[`${fieldKey}-mapCenter-lat`] = lat;
+ this.layoutDoc[`${fieldKey}-mapCenter-lng`] = lng;
+ zoom && (this.layoutDoc[`${fieldKey}-mapCenter-zoom`] = zoom);
+ });
+ if (layout.isLinkButton && DocListCast(layout.links).length) {
+ await DocumentManager.Instance.FollowLink(undefined, layout, (doc: Doc, where: string, finished?: () => void) => {
+ this.props.addDocTab(doc, where);
+ finished?.();
+ }, false, this.props.ContainingCollectionDoc, batch.end, undefined);
+ } else {
+ ScriptCast(layout.onClick)?.script.run({ this: layout, self: Cast(layout.rootDocument, Doc, null) || layout });
+ batch.end();
+ }
+ }
+
+ private renderMarkerIcon = (layout: Doc) => {
+ const { Document } = this.props;
+ const fieldKey = Doc.LayoutFieldKey(layout);
+ const iconUrl = StrCast(layout.mapIconUrl, StrCast(Document.mapIconUrl));
+ if (iconUrl) {
+ const iconWidth = NumCast(layout[`${fieldKey}-iconWidth`], 45);
+ const iconHeight = NumCast(layout[`${fieldKey}-iconHeight`], 45);
+ const iconSize = new google.maps.Size(iconWidth, iconHeight);
+ return {
+ size: iconSize,
+ scaledSize: iconSize,
+ url: iconUrl
+ };
+ }
+ }
+
+ private renderMarker = (layout: Doc) => {
+ const location = this.getLocation(layout, Doc.LayoutFieldKey(layout));
+ return !location ? (null) :
+ <Marker
+ key={layout[Id]}
+ label={StrCast(layout.title)}
+ position={location}
+ onClick={() => this.markerClick(layout, location)}
+ icon={this.renderMarkerIcon(layout)}
+ />;
+ }
+
+ private respondToAddressChange = async (doc: Doc, fieldKey: string, newAddress: string, oldAddress?: string) => {
+ if (newAddress === oldAddress) {
+ return false;
+ }
+ const response = await query(newAddress);
+ const id = doc[Id];
+ if (!response || response.status === noResults) {
+ this._cancelAddrReq.set(id, true);
+ doc[`${fieldKey}-address`] = oldAddress;
+ return false;
+ }
+ const { geometry, formatted_address } = response.results[0];
+ const { lat, lng } = geometry.location;
+ runInAction(() => {
+ if (doc[`${fieldKey}-lat`] !== lat || doc[`${fieldKey}-lng`] !== lng) {
+ this._cancelLocReq.set(id, true);
+ Doc.SetInPlace(doc, `${fieldKey}-lat`, lat, true);
+ Doc.SetInPlace(doc, `${fieldKey}-lng`, lng, true);
+ }
+ if (formatted_address !== newAddress) {
+ this._cancelAddrReq.set(id, true);
+ Doc.SetInPlace(doc, `${fieldKey}-address`, formatted_address, true);
+ }
+ });
+ return true;
+ }
+
+ private respondToLocationChange = async (doc: Doc, fieldKey: string, newLatLng: DocLatLng, oldLatLng: Opt<DocLatLng>) => {
+ if (newLatLng === oldLatLng) {
+ return false;
+ }
+ const response = await query({ lat: NumCast(newLatLng.lat), lng: NumCast(newLatLng.lng) });
+ const id = doc[Id];
+ if (!response || response.status === noResults) {
+ this._cancelLocReq.set(id, true);
+ runInAction(() => {
+ doc[`${fieldKey}-lat`] = oldLatLng?.lat;
+ doc[`${fieldKey}-lng`] = oldLatLng?.lng;
+ });
+ return false;
+ }
+ const { formatted_address } = response.results[0];
+ if (formatted_address !== doc[`${fieldKey}-address`]) {
+ this._cancelAddrReq.set(doc[Id], true);
+ Doc.SetInPlace(doc, `${fieldKey}-address`, formatted_address, true);
+ }
+ return true;
+ }
+
+ @computed get reactiveContents() {
+ this.responders.forEach(({ location, address }) => { location(); address(); });
+ this.responders = [];
+ return this.childLayoutPairs.map(({ layout }) => {
+ const fieldKey = Doc.LayoutFieldKey(layout);
+ const id = layout[Id];
+ this.responders.push({
+ location: computed(() => ({ lat: layout[`${fieldKey}-lat`], lng: layout[`${fieldKey}-lng`] }))
+ .observe(({ oldValue, newValue }) => {
+ if (this._cancelLocReq.get(id)) {
+ this._cancelLocReq.set(id, false);
+ } else if (newValue.lat !== undefined && newValue.lng !== undefined) {
+ this.respondToLocationChange(layout, fieldKey, newValue, oldValue);
+ }
+ }),
+ address: computed(() => Cast(layout[`${fieldKey}-address`], "string", null))
+ .observe(({ oldValue, newValue }) => {
+ if (this._cancelAddrReq.get(id)) {
+ this._cancelAddrReq.set(id, false);
+ } else if (newValue?.length) {
+ this.respondToAddressChange(layout, fieldKey, newValue, oldValue);
+ }
+ })
+ });
+ return this.renderMarker(layout);
+ });
+ }
+
+ render() {
+ const { childLayoutPairs } = this;
+ const { Document, fieldKey, active, google } = this.props;
+ let center = this.getLocation(Document, `${fieldKey}-mapCenter`, false);
+ if (center === undefined) {
+ center = childLayoutPairs.map(({ layout }) => this.getLocation(layout, Doc.LayoutFieldKey(layout), false)).find(layout => layout);
+ if (center === undefined) {
+ center = defaultLocation;
+ }
+ }
+ return <div className="collectionMapView" ref={this.createDashEventsTarget}>
+ <div className={"collectionMapView-contents"}
+ style={{ pointerEvents: active() ? undefined : "none" }}
+ onWheel={e => e.stopPropagation()}
+ onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()} >
+ <GeoMap
+ google={google}
+ zoom={center.zoom || 10}
+ initialCenter={center}
+ center={center}
+ onIdle={(_props?: MapProps, map?: google.maps.Map) => {
+ if (this.layoutDoc.lockedTransform) {
+ map?.setZoom(center?.zoom || 10); // reset zoom (probably can tell the map to disallow zooming somehow)
+ } else {
+ const zoom = map?.getZoom();
+ center?.zoom !== zoom && undoBatch(action(() => {
+ Document[`${fieldKey}-mapCenter-zoom`] = zoom;
+ }))();
+ }
+ }}
+ onDragend={(_props?: MapProps, map?: google.maps.Map) => {
+ if (this.layoutDoc.lockedTransform) {
+ map?.setCenter({ lat: center?.lat!, lng: center?.lng! }); // reset the drag (probably can tell the map to disallow dragging somehow)
+ } else {
+ undoBatch(action(({ lat, lng }) => {
+ Document[`${fieldKey}-mapCenter-lat`] = lat();
+ Document[`${fieldKey}-mapCenter-lng`] = lng();
+ }))(map?.getCenter());
+ }
+ }}
+ >
+ {this.reactiveContents}
+ </GeoMap>
+ </div>
+ </div>;
+ }
+
+}
+
+export default GoogleApiWrapper({
+ apiKey: process.env.GOOGLE_MAPS!,
+ LoadingContainer: () => (
+ <div className={"loadingWrapper"}>
+ <img className={"loadingGif"} src={"/assets/loading.gif"} />
+ </div>
+ )
+})(CollectionMapView) as any; \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index af3e18a4b..b272151c1 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -36,6 +36,9 @@ interface CMVFieldRowProps {
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
setDocHeight: (key: string, thisHeight: number) => void;
+ observeHeight: (myref: any) => void;
+ unobserveHeight: (myref: any) => void;
+ showHandle: boolean;
}
@observer
@@ -53,14 +56,19 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
private _contRef: React.RefObject<HTMLDivElement> = React.createRef();
private _sensitivity: number = 16;
private _counter: number = 0;
-
+ private _ele: any;
createRowDropRef = (ele: HTMLDivElement | null) => {
this._dropDisposer && this._dropDisposer();
if (ele) {
+ this._ele = ele;
+ this.props.observeHeight(ele);
this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
+ componentWillUnmount() {
+ this.props.unobserveHeight(this._ele);
+ }
getTrueHeight = () => {
if (this._collapsed) {
@@ -293,7 +301,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ""),
}}>
{this.props.parent.children(this.props.docList)}
- {this.props.parent.columnDragger}
+ {this.props.showHandle && this.props.parent.props.active() ? this.props.parent.columnDragger : (null)}
</div>
</div>;
}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index f124fe21b..82204ca7b 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -4,8 +4,9 @@ import { observer } from "mobx-react";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
+import { KeyCodes } from "../../util/KeyCodes";
import { SetupDrag, DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
@@ -21,9 +22,7 @@ import { SelectionManager } from "../../util/SelectionManager";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faExpand } from '@fortawesome/free-solid-svg-icons';
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { KeyCodes } from "../../northstar/utils/KeyCodes";
import { undoBatch } from "../../util/UndoManager";
-import { List } from "lodash";
library.add(faExpand);
@@ -159,6 +158,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
LibraryPath: [],
dropAction: "alias",
bringToFront: emptyFunction,
+ rootSelected: returnFalse,
fieldKey: this.props.rowProps.column.id as string,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
@@ -171,6 +171,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
whenActiveChanged: emptyFunction,
PanelHeight: returnZero,
PanelWidth: returnZero,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
addDocTab: this.props.addDocTab,
pinToPres: this.props.pinToPres,
ContentScaling: returnOne
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index a1b541f74..380d91d2f 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -12,9 +12,8 @@ import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
import { Docs, DocumentOptions } from "../../documents/Documents";
-import { Gateway } from "../../northstar/manager/Gateway";
import { CompileScript, Transformer, ts } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -28,7 +27,8 @@ import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionView } from "./CollectionView";
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
+import { setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
+import { DocumentView } from "../nodes/DocumentView";
library.add(faCog, faPlus, faSortUp, faSortDown);
library.add(faTable);
@@ -117,27 +117,32 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed
get previewPanel() {
- return <div ref={this.createTarget}>
- <ContentFittingDocumentView
- Document={this.previewDocument}
- DataDocument={undefined}
- LibraryPath={this.props.LibraryPath}
- childDocs={this.childDocs}
- renderDepth={this.props.renderDepth}
- rootSelected={this.rootSelected}
- PanelWidth={this.previewWidth}
- PanelHeight={this.previewHeight}
- getTransform={this.getPreviewTransform}
- CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
- CollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- active={this.props.active}
- whenActiveChanged={this.props.whenActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- />
+ return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
+ {!this.previewDocument ? (null) :
+ <ContentFittingDocumentView
+ Document={this.previewDocument}
+ DataDocument={undefined}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ fitToBox={true}
+ FreezeDimensions={true}
+ focus={emptyFunction}
+ LibraryPath={this.props.LibraryPath}
+ renderDepth={this.props.renderDepth}
+ rootSelected={this.rootSelected}
+ PanelWidth={this.previewWidth}
+ PanelHeight={this.previewHeight}
+ getTransform={this.getPreviewTransform}
+ CollectionDoc={this.props.CollectionView?.props.Document}
+ CollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ />}
</div>;
}
@@ -180,7 +185,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
render() {
return <div className="collectionSchemaView-container">
- <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
+ <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
{this.schemaTable}
</div>
{this.dividerDragger}
@@ -667,27 +672,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
}
- @action
- makeDB = async () => {
- let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
- csv = csv.substr(0, csv.length - 1) + "\n";
- const self = this;
- this.childDocs.map(doc => {
- csv += self.columns.reduce((val, col) => val + (doc[col.heading] ? doc[col.heading]!.toString() : "0") + ",", "");
- csv = csv.substr(0, csv.length - 1) + "\n";
- });
- csv.substring(0, csv.length - 1);
- const dbName = StrCast(this.props.Document.title);
- const res = await Gateway.Instance.PostSchema(csv, dbName);
- if (self.props.CollectionView && self.props.CollectionView.props.addDocument) {
- const schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
- if (schemaDoc) {
- //self.props.CollectionView.props.addDocument(schemaDoc, false);
- self.props.Document.schemaDoc = schemaDoc;
- }
- }
- }
-
getField = (row: number, col?: number) => {
const docs = this.childDocs;
@@ -752,7 +736,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return (doc as any)[key][row + ${row}][(doc as any).schemaColumns[col + ${col}].heading];
}
return ${script}`;
- const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: true, transformer: this.createTransformer(row, col) });
+ const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
if (compiled.compiled) {
doc[field] = new ComputedField(compiled);
return true;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index bfa5ea278..47faa9239 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -26,6 +26,9 @@
position: relative;
display: block;
}
+ .collectionStackingViewFieldColumn {
+ height:max-content;
+ }
.collectionSchemaView-previewDoc {
height: 100%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d21e17bbc..dd84c4d6e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction } from "../../../Utils";
+import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
import { DragManager, dropActionType } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -24,13 +24,13 @@ import "./CollectionStackingView.scss";
import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
-import { Docs } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
+const _global = (window /* browser */ || global /* node */) as any;
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
_masonryGridRef: HTMLDivElement | null = null;
_draggerRef = React.createRef<HTMLDivElement>();
- _heightDisposer?: IReactionDisposer;
_pivotFieldDisposer?: IReactionDisposer;
_docXfs: any[] = [];
_columnStart: number = 0;
@@ -47,12 +47,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
@computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
@computed get columnWidth() {
+ TraceMobx();
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[], columns?: number) {
+ TraceMobx();
this._docXfs.length = 0;
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
@@ -112,34 +114,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return fields;
}
+ getSimpleDocHeight(d?: Doc) {
+ if (!d) return 0;
+ const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
+ let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
+ if (!layoutDoc._fitWidth && nw && nh) {
+ const aspect = nw && nh ? nh / nw : 1;
+ if (!(this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+ return wid * aspect;
+ }
+ return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym]();
+ }
componentDidMount() {
super.componentDidMount();
- this._heightDisposer = reaction(() => {
- 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) => {
- const r1 = Math.max(maxHght,
- (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
- const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
- return val;
- }, this.yMargin));
- return r1;
- }, 0);
- return res;
- } else {
- const sum = Array.from(this._heightMap.values()).reduce((acc: number, curr: number) => acc += curr, 0);
- return this.props.ContentScaling() * (sum + (this.Sections.size ? (this.props.Document.miniHeaders ? 20 : 85) : -15));
- }
- }
- return -1;
- },
- (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);
- },
- { fireImmediately: true }
- );
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
@@ -149,7 +138,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
componentWillUnmount() {
super.componentWillUnmount();
- this._heightDisposer?.();
this._pivotFieldDisposer?.();
}
@@ -165,6 +153,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
@computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ addDocTab = (doc: Doc, where: string) => {
+ if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ return true;
+ }
+ return this.props.addDocTab(doc, where);
+ }
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
const height = () => this.getDocHeight(doc);
@@ -174,23 +169,26 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
backgroundColor={this.props.backgroundColor}
LayoutDoc={this.props.childLayoutTemplate}
LibraryPath={this.props.LibraryPath}
+ FreezeDimensions={this.props.freezeChildDimensions}
renderDepth={this.props.renderDepth + 1}
- fitToBox={this.props.fitToBox}
+ PanelWidth={width}
+ PanelHeight={height}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
rootSelected={this.rootSelected}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
- PanelWidth={width}
- PanelHeight={height}
getTransform={dxf}
focus={this.props.focus}
- CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
+ CollectionDoc={this.props.CollectionView?.props.Document}
CollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
- addDocTab={this.props.addDocTab}
+ addDocTab={this.addDocTab}
pinToPres={this.props.pinToPres}>
</ContentFittingDocumentView>;
}
@@ -287,6 +285,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
});
}
headings = () => Array.from(this.Sections);
+ refList: any[] = [];
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
@@ -297,6 +296,19 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const cols = () => this.isStackingView ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
return <CollectionStackingViewFieldColumn
+ unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)}
+ observeHeight={(ref) => {
+ if (ref) {
+ this.refList.push(ref);
+ const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
+ this.observer = new _global.ResizeObserver(action((entries: any) => {
+ if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
+ Doc.Layout(doc)._height = Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))));
+ }
+ }));
+ this.observer.observe(ref);
+ }
+ }}
key={heading ? heading.heading : ""}
cols={cols}
headings={this.headings}
@@ -315,14 +327,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
- const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]());
const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2;
const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0);
- return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling);
+ return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety);
}
- sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
+ sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
const key = this.pivotField;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
@@ -332,6 +342,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length,
Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
return <CollectionMasonryViewFieldRow
+ showHandle={first}
+ unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)}
+ observeHeight={(ref) => {
+ if (ref) {
+ this.refList.push(ref);
+ const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
+ this.observer = new _global.ResizeObserver(action((entries: any) => {
+ if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
+ Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
+ }
+ }));
+ this.observer.observe(ref);
+ }
+ }}
key={heading ? heading.heading : ""}
rows={rows}
headings={this.headings}
@@ -384,11 +408,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const entries = Array.from(this.Sections.entries());
sections = entries.sort(this.sortFunc);
}
- return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
+ return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0));
}
- @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); }
+ @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth) || this.props.NativeWidth() || 0; }
+ @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight) || this.props.NativeHeight() || 0; }
+
+ @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
+
+ observer: any;
render() {
TraceMobx();
const editableViewProps = {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 0a48c95e4..5d926b7c7 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -39,6 +39,8 @@ interface CSVFieldColumnProps {
type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
+ observeHeight: (myref: any) => void;
+ unobserveHeight: (myref: any) => void;
}
@observer
@@ -50,13 +52,19 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
@observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+ _ele: HTMLElement | null = null;
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
if (ele) {
+ this._ele = ele;
+ this.props.observeHeight(ele);
this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
}
+ componentWillUnmount() {
+ this.props.unobserveHeight(this._ele);
+ }
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
@@ -353,7 +361,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
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 }}
+ <div className="collectionStackingViewFieldColumn" key={heading}
+ style={{
+ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
+ height: SelectionManager.GetIsDragging() ? "100%" : undefined,
+ background: this._background
+ }}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
{this.props.parent.Document.hideHeadings ? (null) : headingView}
{
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1d6ae3c1..37cf942c6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,14 +34,17 @@ export interface CollectionViewProps extends FieldViewProps {
PanelHeight: () => number;
VisibleHeight?: () => number;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
- rootSelected: () => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
fieldKey: string;
+ NativeWidth: () => number;
+ NativeHeight: () => number;
}
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
children?: never | (() => JSX.Element[]) | React.ReactNode;
- overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explict list (see LinkBox)
+ freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
+ overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
isAnnotationOverlay?: boolean;
annotationsKey: string;
@@ -96,8 +99,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
}
- rootSelected = () => {
- return this.props.isSelected() || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected() : false);
+ rootSelected = (outsideReaction?: boolean) => {
+ return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction));
}
// 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.
@@ -117,8 +120,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
return Cast(this.dataField, listSpec(Doc));
}
@computed get childDocs() {
- const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
- const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+ const docFilters = this.props.ignoreFields?.includes("_docFilters") ? [] : Cast(this.props.Document._docFilters, listSpec("string"), []);
+ const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
for (let i = 0; i < docFilters.length; i += 3) {
const [key, value, modifiers] = docFilters.slice(i, i + 3);
@@ -213,9 +216,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this
if (docDragData) {
let added = false;
- if (this.props.Document._freezeOnDrop) {
- de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
- }
if (docDragData.dropAction || docDragData.userDropAction) {
added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
} else if (docDragData.moveDocument) {
@@ -381,7 +381,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
alert(`Upload failed: ${result.message}`);
return;
}
- const full = { ...options, _width: 300, title: name };
+ const full = { ...options, _width: 400, title: name };
const pathname = Utils.prepend(result.accessPaths.agnostic.client);
const doc = await Docs.Get.DocumentFromType(type, pathname, full);
if (!doc) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4f77e8b0e..e05223ca0 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,11 +1,11 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt, DocCastAsync } from "../../../new_fields/Doc";
import { List } from "../../../new_fields/List";
import { ObjectField } from "../../../new_fields/ObjectField";
import { RichTextField } from "../../../new_fields/RichTextField";
import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { NumCast, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
import { Scripting } from "../../util/Scripting";
import { ContextMenu } from "../ContextMenu";
@@ -19,24 +19,22 @@ const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
import React = require("react");
+import { DocumentView } from "../nodes/DocumentView";
@observer
export class CollectionTimeView extends CollectionSubView(doc => doc) {
_changing = false;
@observable _layoutEngine = "pivot";
@observable _collapsed: boolean = false;
- componentWillUnmount() {
- this.props.Document.onChildClick = undefined;
- }
- componentDidMount() {
- this.props.Document._freezeOnDrop = true;
- 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;
- const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.layoutKey='layout_detailedView'; alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
- this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", containingCollection: Doc.name, shiftKey: "boolean" });
- this.props.Document._fitToBox = true;
- if (!this.props.Document.onViewDefClick) {
- this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
- }
+ @observable _childClickedScript: Opt<ScriptField>;
+ @observable _viewDefDivClick: Opt<ScriptField>;
+ async componentDidMount() {
+ const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+ const childText = "const alias = getAlias(this); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+ runInAction(() => {
+ this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
+ this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
+ });
}
layoutEngine = () => this._layoutEngine;
@@ -71,9 +69,23 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
}), returnFalse, emptyFunction);
}
+ contentsDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+ if (prevFilterIndex > 0) {
+ prevFilterIndex--;
+ this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+ this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+ this.props.Document._prevFilterIndex = prevFilterIndex;
+ } else {
+ this.props.Document._docFilters = new List([]);
+ }
+ }), false);
+ }
+
@computed get contents() {
- return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
- <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
+ return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
+ <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
</div>;
}
@@ -131,20 +143,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
};
return <div className={"pivotKeyEntry"}>
- <button className="collectionTimeView-backBtn"
- onClick={action(() => {
- let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
- if (prevFilterIndex > 0) {
- prevFilterIndex--;
- this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
- this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
- this.props.Document._prevFilterIndex = prevFilterIndex;
- } else {
- this.props.Document._docFilters = new List([]);
- }
- })}>
- back
- </button>
<EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
</div>;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index fc612c66d..d2c8cc3ad 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -19,7 +19,7 @@ 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';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from "../EditableView";
@@ -103,7 +103,7 @@ class TreeView extends React.Component<TreeViewProps> {
set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
@computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
@computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
- @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
+ @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
@computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
@computed get fieldKey() {
const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'");
@@ -182,14 +182,17 @@ class TreeView extends React.Component<TreeViewProps> {
GetValue={() => StrCast(this.props.document[key])}
SetValue={undoBatch((value: string) => {
Doc.SetInPlace(this.props.document, key, value, false) || true;
- this.props.document.editTitle = undefined;
+ Doc.SetInPlace(this.props.document, "editTitle", undefined, false);
+ //this.props.document.editTitle = undefined;
})}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.props.document, key, value, false);
const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
//EditableView.loadId = doc[Id];
- this.props.document.editTitle = undefined;
- doc.editTitle = true;
+ Doc.SetInPlace(this.props.document, "editTitle", undefined, false);
+ // this.props.document.editTitle = undefined;
+ Doc.SetInPlace(this.props.document, "editTitle", true, false);
+ //doc.editTitle = true;
return this.props.addDocument(doc);
})}
onClick={() => {
@@ -267,8 +270,9 @@ class TreeView extends React.Component<TreeViewProps> {
docTransform = () => {
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!);
const outerXf = this.props.outerXf();
- const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0));
+ const offset = this.props.ScreenToLocalTransform().transformDirection((outerXf.translateX - translateX), outerXf.translateY - translateY);
+ const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+
return finalXf;
}
getTransform = () => {
@@ -280,7 +284,7 @@ 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, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
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;
}
@@ -288,7 +292,7 @@ class TreeView extends React.Component<TreeViewProps> {
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, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
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) :
@@ -305,7 +309,7 @@ class TreeView extends React.Component<TreeViewProps> {
const rows: JSX.Element[] = [];
for (const key of Object.keys(ids).slice().sort()) {
- if (this.props.ignoreFields?.includes(key)) continue;
+ if (this.props.ignoreFields?.includes(key) || key === "title" || key === "treeViewOpen") continue;
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
@@ -376,6 +380,7 @@ class TreeView extends React.Component<TreeViewProps> {
rootSelected={returnTrue}
backgroundColor={this.props.backgroundColor}
fitToBox={this.boundsOfCollectionDocument !== undefined}
+ FreezeDimensions={true}
PanelWidth={this.docWidth}
PanelHeight={this.docHeight}
getTransform={this.docTransform}
@@ -392,11 +397,13 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
+ get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.props.document.onCheckedClick); }
+
@action
bulletClick = (e: React.MouseEvent) => {
- if (this.props.onCheckedClick && this.props.document.type !== DocumentType.COL) {
+ if (this.onCheckedClick && this.props.document.type !== DocumentType.COL) {
// this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
- ScriptCast(this.props.onCheckedClick).script.run({
+ this.onCheckedClick.script.run({
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" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check",
@@ -410,7 +417,7 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
- const checked = this.props.document.type === DocumentType.COL ? undefined : this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
+ const checked = this.props.document.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}>
{<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
</div>;
@@ -421,7 +428,7 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderTitle() {
const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
- const editTitle = ScriptField.MakeFunction("this.editTitle=true", { this: Doc.name });
+ const editTitle = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)");
const headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
@@ -444,10 +451,11 @@ class TreeView extends React.Component<TreeViewProps> {
style={{
background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
fontWeight: this.props.document.searchMatch ? "bold" : undefined,
+ textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
}} >
- {this.props.document.editTitle ?
+ {Doc.GetT(this.props.document, "editTitle", "boolean", true) ?
this.editableView("title") :
<DocumentView
Document={this.props.document}
@@ -465,6 +473,8 @@ class TreeView extends React.Component<TreeViewProps> {
ContentScaling={returnOne}
PanelWidth={returnZero}
PanelHeight={returnZero}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
renderDepth={1}
focus={emptyFunction}
parentActive={returnTrue}
@@ -473,8 +483,6 @@ class TreeView extends React.Component<TreeViewProps> {
dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}
/>}
</div >
{this.props.treeViewHideHeaderFields() ? (null) : headerElements}
@@ -733,20 +741,22 @@ export class CollectionTreeView extends CollectionSubView(Document, undefined as
heroView._showTitle = "title";
heroView._showTitleHover = "titlehover";
- Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+ Doc.AddDocToList(Doc.UserDoc().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"
+ title: "hero view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
+ dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), icon: "portrait",
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
}));
- Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+ Doc.AddDocToList(Doc.UserDoc().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"
+ title: "detail view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
+ dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), icon: "file-alt",
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
}));
Document.childLayout = heroView;
- Document.childDetailed = detailView;
+ Document.childDetailView = detailView;
Document._viewType = CollectionViewType.Time;
Document._forceActive = true;
Document._pivotField = "company";
@@ -756,8 +766,7 @@ export class CollectionTreeView extends CollectionSubView(Document, undefined as
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", treeViewContainer: Doc.name })
+ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
});
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
@@ -810,7 +819,7 @@ export class CollectionTreeView extends CollectionSubView(Document, undefined as
TreeView.GetChildElements(childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.props.Document.treeViewHideHeaderFields),
- BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick || ScriptCast(this.props.Document.onCheckedClick),
+ BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
this.props.onChildClick || ScriptCast(this.props.Document.onChildClick), this.props.ignoreFields)
}
</ul>
@@ -831,13 +840,12 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
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 })
- }));
+ const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
+ const doc = new Doc();
+ doc.title = facetValue.toString();
+ doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue });
+ return doc;
+ });
return new List<Doc>(facetValueDocSet);
});
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index b92c5fdd1..d43dd387a 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -11,7 +11,6 @@
height: 100%;
overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
-
.collectionTimeView-dragger {
background-color: lightgray;
height: 40px;
@@ -21,7 +20,7 @@
top: 55%;
border: 1px black solid;
z-index: 2;
- left: -10px;
+ right: -10px;
}
.collectionTimeView-treeView {
display: flex;
@@ -29,7 +28,7 @@
width: 200px;
height: 100%;
position: absolute;
- left: 0;
+ right: 0;
top: 0;
.collectionTimeView-addfacet {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 23d701ffd..19e235ff2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEye, faEdit } from '@fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
+import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree, faGlobeAmericas } from '@fortawesome/free-solid-svg-icons';
import { action, observable, computed } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
@@ -10,10 +10,10 @@ import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be im
import { DateField } from '../../../new_fields/DateField';
import { DataSym, Doc, DocListCast, Field, Opt } from '../../../new_fields/Doc';
import { List } from '../../../new_fields/List';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
import { ImageField } from '../../../new_fields/URLField';
import { TraceMobx } from '../../../new_fields/util';
-import { Utils, setupMoveUpEvents, returnFalse } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -42,47 +42,32 @@ import { Id } from '../../../new_fields/FieldSymbols';
import { listSpec } from '../../../new_fields/Schema';
import { Docs } from '../../documents/Documents';
import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
+import { InteractionUtils } from '../../util/InteractionUtils';
+import { ObjectField } from '../../../new_fields/ObjectField';
+import CollectionMapView from './CollectionMapView';
+import { Transform } from 'prosemirror-transform';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
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);
+library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faGlobeAmericas, faEllipsisV, faImage, faEye as any, faCopy);
export enum CollectionViewType {
- Invalid,
- Freeform,
- Schema,
- Docking,
- Tree,
- Stacking,
- Masonry,
- Multicolumn,
- Multirow,
- Time,
- Carousel,
- Linear,
- Staff
-}
-
-export namespace CollectionViewType {
- const stringMapping = new Map<string, CollectionViewType>([
- ["invalid", CollectionViewType.Invalid],
- ["freeform", CollectionViewType.Freeform],
- ["schema", CollectionViewType.Schema],
- ["docking", CollectionViewType.Docking],
- ["tree", CollectionViewType.Tree],
- ["stacking", CollectionViewType.Stacking],
- ["masonry", CollectionViewType.Masonry],
- ["multicolumn", CollectionViewType.Multicolumn],
- ["multirow", CollectionViewType.Multirow],
- ["time", CollectionViewType.Time],
- ["carousel", CollectionViewType.Carousel],
- ["linear", CollectionViewType.Linear],
- ]);
-
- export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
- export const stringFor = (value: number) => Array.from(stringMapping.entries()).find(entry => entry[1] === value)?.[0];
+ Invalid = "invalid",
+ Freeform = "freeform",
+ Schema = "schema",
+ Docking = "docking",
+ Tree = 'tree',
+ Stacking = "stacking",
+ Masonry = "masonry",
+ Multicolumn = "multicolumn",
+ Multirow = "multirow",
+ Time = "time",
+ Carousel = "carousel",
+ Linear = "linear",
+ Staff = "staff",
+ Map = "map"
}
export interface CollectionRenderProps {
@@ -99,13 +84,16 @@ export class CollectionView extends Touchable<FieldViewProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
private _isChildActive = false; //TODO should this be observable?
- @observable private _isLightboxOpen = false;
+ get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); }
+ set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; }
@observable private _curLightboxImg = 0;
@observable private static _safeMode = false;
public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
get collectionViewType(): CollectionViewType | undefined {
- const viewField = NumCast(this.props.Document._viewType);
+ const viewField = StrCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
if (viewField === CollectionViewType.Freeform) {
return CollectionViewType.Tree;
@@ -114,12 +102,12 @@ export class CollectionView extends Touchable<FieldViewProps> {
return CollectionViewType.Freeform;
}
}
- return viewField;
+ return viewField as any as CollectionViewType;
}
- active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected() && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
+ active = (outsideReaction?: boolean) => (this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.props.Document.forceActive || this._isChildActive || this.props.renderDepth === 0) ? true : false;
- whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); };
+ whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
@action.bound
addDocument(doc: Doc): boolean {
@@ -185,6 +173,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
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.Time: { return (<CollectionTimeView key="collview" {...props} />); }
+ case CollectionViewType.Map: return (<CollectionMapView key="collview" {...props} />);
case CollectionViewType.Freeform:
default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
@@ -226,6 +215,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
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" });
+ subItems.push({ description: "Map", event: () => this.props.Document._viewType = CollectionViewType.Map, icon: "globe-americas" });
switch (this.props.Document._viewType) {
case CollectionViewType.Freeform: {
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
@@ -241,9 +231,11 @@ export class CollectionView extends Touchable<FieldViewProps> {
if (this.props.Document.childLayout instanceof Doc) {
layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "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, "onRight"), icon: "project-diagram" });
+ if (this.props.Document.childDetailView instanceof Doc) {
+ layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailView as Doc, "onRight"), icon: "project-diagram" });
}
+ layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+
!existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
const open = ContextMenu.Instance.findByDescription("Open...");
@@ -252,7 +244,15 @@ export class CollectionView extends Touchable<FieldViewProps> {
const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
+ const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }];
+ DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick =>
+ funcs.push({ key: "onChildClick", name: StrCast(childClick.title), script: ScriptCast(childClick.script) }));
+ funcs.map(func => onClicks.push({
+ description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
+ func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script));
+ ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
+ }
+ }));
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
const more = ContextMenu.Instance.findByDescription("More...");
@@ -278,11 +278,11 @@ export class CollectionView extends Touchable<FieldViewProps> {
onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
}
- @observable _facetWidth = 0;
+ get _facetWidth() { return NumCast(this.props.Document._facetWidth); }
+ set _facetWidth(value) { this.props.Document._facetWidth = value; }
bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
- getTransform = () => this.props.ScreenToLocalTransform().translate(-this.facetWidth(), 0);
- facetWidth = () => Math.min(this.props.PanelWidth() - 25, this._facetWidth);
+ facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth));
@computed get dataDoc() {
return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
@@ -307,8 +307,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
get childDocs() {
const dfield = this.dataField;
const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.rootDocument, Doc, null) ? [Cast(this.props.Document.rootDocument, Doc, null)] : []);
- const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
- const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+ const docs = rawdocs.filter(d => d && !(d instanceof Promise)).map(d => d as Doc);
+ const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript);
return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
}
@computed get _allFacets() {
@@ -359,37 +359,42 @@ export class CollectionView extends Touchable<FieldViewProps> {
let newFacet: Opt<Doc>;
if (nonNumbers / allCollectionDocs.length < .1) {
newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+ const newFacetField = Doc.LayoutFieldKey(newFacet);
const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
newFacet.treeViewExpandedView = "layout";
newFacet.treeViewOpen = true;
- newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
- newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
- newFacet._sliderMinThumb = minVal;
- newFacet._sliderMaxThumb = maxVal;
+ const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05);
+ const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05);
+ newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
+ newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
+ Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal;
+ Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
newFacet.target = this.props.Document;
const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
} else {
- newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+ newFacet = new Doc();
+ newFacet.title = facetHeader;
+ newFacet.treeViewOpen = true;
+ newFacet.type = DocumentType.COL;
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);
+ newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables);
}
- Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
+ newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
}
}
-
onPointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
- this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+ this._facetWidth = this.props.PanelWidth() - Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
return false;
}), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
}
- filterBackground = () => "dimGray";
+ filterBackground = () => "rgba(105, 105, 105, 0.432)";
+ get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
@computed get scriptField() {
const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
@@ -410,25 +415,44 @@ export class CollectionView extends Touchable<FieldViewProps> {
<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"} />
+ <span className="collectionTimeView-span">Facet Filters</span>
</div>
</Flyout>
</div>
<div className="collectionTimeView-tree" key="tree">
- <CollectionTreeView {...this.props}
+ <CollectionTreeView
+ Document={facetCollection}
+ DataDoc={facetCollection}
+ fieldKey={`${this.props.fieldKey}-filter`}
CollectionView={this}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ PanelWidth={this.facetWidth}
+ PanelHeight={this.props.PanelHeight}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ LibraryPath={emptyPath}
+ rootSelected={this.props.rootSelected}
+ renderDepth={1}
+ dropAction={this.props.dropAction}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ isSelected={returnFalse}
+ select={returnFalse}
+ bringToFront={emptyFunction}
+ active={this.props.active}
+ whenActiveChanged={returnFalse}
treeViewHideTitle={true}
+ ContentScaling={returnOne}
+ focus={returnFalse}
treeViewHideHeaderFields={true}
onCheckedClick={this.scriptField!}
- ignoreFields={["_facetCollection", "_docFilters"]}
+ ignoreFields={this.ignoreFields}
annotationsKey={""}
dontRegisterView={true}
- PanelWidth={this.facetWidth}
- DataDoc={facetCollection}
- Document={facetCollection}
backgroundColor={this.filterBackground}
- fieldKey={`${this.props.fieldKey}-filter`}
moveDocument={returnFalse}
removeDocument={returnFalse}
addDocument={returnFalse} />
@@ -454,7 +478,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
}}
onContextMenu={this.onContextMenu}>
{this.showIsTagged()}
- <div style={{ width: `calc(100% - ${this.facetWidth()}px)`, marginLeft: `${this.facetWidth()}px` }}>
+ <div style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
</div>
{this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
@@ -464,9 +488,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
:
""))}
{!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (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>
+ <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
}
{this.filterView}
</div>);
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index a691b4805..5203eb55f 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -61,16 +61,21 @@
pointer-events: all;
// margin-top: 10px;
}
- .collectionViewBaseChrome-template {
+ .collectionViewBaseChrome-template,
+ .collectionViewBaseChrome-viewModes {
display: grid;
background: rgb(238, 238, 238);
color:grey;
margin-top:auto;
margin-bottom:auto;
+ margin-left: 5px;
+ }
+ .collectionViewBaseChrome-viewModes {
+ margin-left: 25px;
}
.collectionViewBaseChrome-viewSpecs {
- margin-left: 10px;
+ margin-left: 5px;
display: grid;
.collectionViewBaseChrome-filterIcon {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 2d565d9db..7315d2c4e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -6,7 +6,6 @@ import { Doc, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
import { DragManager } from "../../util/DragManager";
@@ -16,8 +15,6 @@ import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
import { CollectionViewType } from "./CollectionView";
import { CollectionView } from "./CollectionView";
import "./CollectionViewChromes.scss";
-import * as Autosuggest from 'react-autosuggest';
-import KeyRestrictionRow from "./KeyRestrictionRow";
const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
@@ -42,20 +39,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
get target() { return this.props.CollectionView.props.Document; }
_templateCommand = {
params: ["target", "source"], title: "=> item view",
- script: "setChildLayout(this.target, this.source?.[0])",
- immediate: (source: Doc[]) => Doc.setChildLayout(this.target, source?.[0]),
+ script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
+ immediate: (source: Doc[]) => this.target.childLayout = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
_narrativeCommand = {
params: ["target", "source"], title: "=> click item view",
- script: "setChildDetailedLayout(this.target, this.source?.[0])",
- immediate: (source: Doc[]) => Doc.setChildDetailedLayout(this.target, source?.[0]),
+ script: "this.target.childDetailView = getDocTemplate(this.source?.[0])",
+ immediate: (source: Doc[]) => this.target.childDetailView = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
_contentCommand = {
params: ["target", "source"], title: "=> content",
- script: "getProto(this.target).data = aliasDocs(this.source);",
- immediate: (source: Doc[]) => Doc.GetProto(this.target).data = Doc.aliasDocs(source),
+ script: "getProto(this.target).data = copyField(this.source);",
+ immediate: (source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source), // Doc.aliasDocs(source),
initialize: emptyFunction,
};
_viewCommand = {
@@ -84,62 +81,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
private _picker: any;
private _commandRef = React.createRef<HTMLInputElement>();
private _viewRef = React.createRef<HTMLInputElement>();
- private _autosuggestRef = React.createRef<Autosuggest>();
@observable private _currentKey: string = "";
- @observable private _viewSpecsOpen: boolean = false;
- @observable private _dateWithinValue: string = "";
- @observable private _dateValue: Date | string = "";
- @observable private _keyRestrictions: [JSX.Element, string][] = [];
- @observable private suggestions: string[] = [];
- @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
-
- getFilters = (script: string) => {
- const re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g;
- const arr: any[] = re.exec(script);
- const toReturn: Filter[] = [];
- if (arr !== null) {
- const filter: Filter = {
- key: arr[2],
- value: arr[3],
- contains: (arr[1] === "!") ? false : true,
- };
- toReturn.push(filter);
- script = script.replace(arr[0], "");
- if (re.exec(script) !== null) {
- toReturn.push(...this.getFilters(script));
- }
- else { return toReturn; }
- }
- return toReturn;
- }
-
- addKeyRestrictions = (fields: Filter[]) => {
-
- if (fields.length !== 0) {
- for (let i = 0; i < fields.length; i++) {
- this._keyRestrictions.push([<KeyRestrictionRow field={fields[i].key} value={fields[i].value} key={Utils.GenerateGuid()} contains={fields[i].contains} script={(value: string) => runInAction(() => this._keyRestrictions[i][1] = value)} />, ""]);
-
- }
- if (this._keyRestrictions.length === 1) {
- this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
- }
- }
- else {
- this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]);
- this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
- }
- }
componentDidMount = () => {
-
- let fields: Filter[] = [];
- if (this.filterValue) {
- const string = this.filterValue.script.originalScript;
- fields = this.getFilters(string);
- }
-
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;
if (chromeStatus) {
@@ -158,7 +103,7 @@ 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.document._viewType = e.target.selectedOptions[0].value;
}
commandChanged = (e: React.ChangeEvent) => {
@@ -167,104 +112,93 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@action
- openViewSpecs = (e: React.SyntheticEvent) => {
- if (this._viewSpecsOpen) this.closeViewSpecs();
- else {
- this._viewSpecsOpen = true;
-
- //@ts-ignore
- if (!e.target?.classList[0]?.startsWith("qs")) {
- this.closeDatePicker();
- }
-
- e.stopPropagation();
- document.removeEventListener("pointerdown", this.closeViewSpecs);
- document.addEventListener("pointerdown", this.closeViewSpecs);
- }
+ toggleViewSpecs = (e: React.SyntheticEvent) => {
+ this.document._facetWidth = this.document._facetWidth ? 0 : 200;
+ e.stopPropagation();
}
@action closeViewSpecs = () => {
- this._viewSpecsOpen = false;
- document.removeEventListener("pointerdown", this.closeViewSpecs);
- }
-
- @action
- openDatePicker = (e: React.PointerEvent) => {
- this.openViewSpecs(e);
- if (this._picker) {
- this._picker.alwaysShow = true;
- this._picker.show();
- // TODO: calendar is offset when zoomed in/out
- // this._picker.calendar.style.position = "absolute";
- // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
- // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
- // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
- // this._picker.calendar.style.left = x;
- // this._picker.calendar.style.top = y;
+ this.document._facetWidth = 0;
+ }
+
+ // @action
+ // openDatePicker = (e: React.PointerEvent) => {
+ // if (this._picker) {
+ // this._picker.alwaysShow = true;
+ // this._picker.show();
+ // // TODO: calendar is offset when zoomed in/out
+ // // this._picker.calendar.style.position = "absolute";
+ // // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
+ // // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
+ // // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
+ // // this._picker.calendar.style.left = x;
+ // // this._picker.calendar.style.top = y;
+
+ // e.stopPropagation();
+ // }
+ // }
+
+ // <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+ // id={Utils.GenerateGuid()}
+ // ref={this.datePickerRef}
+ // value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
+ // onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
+ // onPointerDown={this.openDatePicker}
+ // placeholder="Value" />
+ // @action.bound
+ // applyFilter = (e: React.MouseEvent) => {
+ // const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
+ // const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
+ // const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
+ // const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
+ // const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
+ // let dateRestrictionScript = "";
+ // if (this._dateValue instanceof Date) {
+ // const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
+ // const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
+ // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
+ // }
+ // else {
+ // const createdDate = new Date(this._dateValue);
+ // if (!isNaN(createdDate.getTime())) {
+ // const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
+ // const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
+ // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
+ // }
+ // }
+ // const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
+ // `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
+ // `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
+ // "true";
+
+ // this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
+ // }
+
+ // datePickerRef = (node: HTMLInputElement) => {
+ // if (node) {
+ // try {
+ // this._picker = datepicker("#" + node.id, {
+ // disabler: (date: Date) => date > new Date(),
+ // onSelect: (instance: any, date: Date) => runInAction(() => {}), // this._dateValue = date),
+ // dateSelected: new Date()
+ // });
+ // } catch (e) {
+ // console.log("date picker exception:" + e);
+ // }
+ // }
+ // }
- e.stopPropagation();
- }
- }
-
- @action
- addKeyRestriction = (e: React.MouseEvent) => {
- const index = this._keyRestrictions.length;
- this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[index][1] = value)} />, ""]);
-
- this.openViewSpecs(e);
- }
-
- @action.bound
- applyFilter = (e: React.MouseEvent) => {
-
- this.openViewSpecs(e);
-
- const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
- const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
- const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
- const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
- const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
- let dateRestrictionScript = "";
- if (this._dateValue instanceof Date) {
- const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
- const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
- dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- }
- else {
- const createdDate = new Date(this._dateValue);
- if (!isNaN(createdDate.getTime())) {
- const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
- const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
- dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- }
- }
- const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
- `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
- `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
- "true";
-
- this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
- }
-
- @action
- closeDatePicker = () => {
- if (this._picker) {
- this._picker.alwaysShow = false;
- this._picker.hide();
- }
- document.removeEventListener("pointerdown", this.closeDatePicker);
- }
@action
toggleCollapse = () => {
- this.props.CollectionView.props.Document._chromeStatus = this.props.CollectionView.props.Document._chromeStatus === "enabled" ? "collapsed" : "enabled";
+ this.document._chromeStatus = this.document._chromeStatus === "enabled" ? "collapsed" : "enabled";
if (this.props.collapse) {
this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
}
}
subChrome = () => {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+ const collapsed = this.document._chromeStatus !== "enabled";
if (collapsed) return null;
switch (this.props.type) {
case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
@@ -279,13 +213,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
return this.props.CollectionView.props.Document;
}
- @action.bound
- clearFilter = () => {
- this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name });
- this._keyRestrictions = [];
- this.addKeyRestrictions([]);
- }
-
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer && this.dropDisposer();
@@ -304,55 +231,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
return true;
}
- datePickerRef = (node: HTMLInputElement) => {
- if (node) {
- try {
- this._picker = datepicker("#" + node.id, {
- disabler: (date: Date) => date > new Date(),
- onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
- dateSelected: new Date()
- });
- } catch (e) {
- console.log("date picker exception:" + e);
- }
- }
- }
-
- renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
- }
- getSuggestionValue = (suggestion: string) => suggestion;
-
- @action
- onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
- this._currentKey = newValue;
- }
- onSuggestionFetch = async ({ value }: { value: string }) => {
- const sugg = await this.getKeySuggestions(value);
- runInAction(() => this.suggestions = sugg);
- }
- @action
- onSuggestionClear = () => {
- this.suggestions = [];
- }
- getKeySuggestions = async (value: string): Promise<string[]> => {
- return this._buttonizableCommands.filter(c => c.title.indexOf(value) !== -1).map(c => c.title);
- }
-
- autoSuggestDown = (e: React.PointerEvent) => {
- e.stopPropagation();
- }
-
- private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
- private _sensitivity: number = 16;
-
dragViewDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
- const vtype = NumCast(this.props.CollectionView.props.Document._viewType) as CollectionViewType;
+ const vtype = this.props.CollectionView.collectionViewType;
const c = {
- params: ["target"], title: CollectionViewType.stringFor(vtype),
- script: `this.target._viewType = ${NumCast(this.props.CollectionView.props.Document._viewType)}`,
- immediate: (source: Doc[]) => Doc.setChildLayout(this.target, source?.[0]),
+ params: ["target"], title: vtype,
+ script: `this.target._viewType = ${StrCast(this.props.CollectionView.props.Document._viewType)}`,
+ immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
@@ -361,28 +246,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}, emptyFunction, emptyFunction);
}
dragCommandDown = (e: React.PointerEvent) => {
- this._startDragPosition = { x: e.clientX, y: e.clientY };
- document.addEventListener("pointermove", this.dragPointerMove);
- document.addEventListener("pointerup", this.dragPointerUp);
- e.stopPropagation();
- e.preventDefault();
- }
-
- dragPointerMove = (e: PointerEvent) => {
- e.stopPropagation();
- e.preventDefault();
- const [dx, dy] = [e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y];
- if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+ setupMoveUpEvents(this, e, (e, down, delta) => {
this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
{ target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
- document.removeEventListener("pointermove", this.dragPointerMove);
- document.removeEventListener("pointerup", this.dragPointerUp);
- }
- }
- dragPointerUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.dragPointerMove);
- document.removeEventListener("pointerup", this.dragPointerUp);
+ return true;
+ }, emptyFunction, emptyFunction);
}
render() {
@@ -407,7 +276,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
title="Collapse collection chrome" onClick={this.toggleCollapse}>
<FontAwesomeIcon icon="caret-up" size="2x" />
</button>
- <div className="collectionViewBaseChrome-template" style={{ marginLeft: 25, display: collapsed ? "none" : undefined }}>
+ <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
<div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
<div className="commandEntry-drop">
<FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
@@ -416,61 +285,23 @@ 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</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>
+ value={StrCast(this.props.CollectionView.props.Document._viewType)}>
+ {Object.values(CollectionViewType).map(type => ["invalid", "docking"].includes(type) ? (null) : (
+ <option
+ key={Utils.GenerateGuid()}
+ className="collectionViewBaseChrome-viewOption"
+ onPointerDown={stopPropagation}
+ value={type}>
+ {type[0].toUpperCase() + type.substring(1)}
+ </option>
+ ))}
</select>
</div>
</div>
<div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
- <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} >
+ <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
<FontAwesomeIcon icon="filter" size="2x" />
</div>
- <div className="collectionViewBaseChrome-viewSpecsMenu"
- onPointerDown={this.openViewSpecs}
- style={{
- height: this._viewSpecsOpen ? "fit-content" : "0px",
- overflow: this._viewSpecsOpen ? "initial" : "hidden"
- }}>
- {this._keyRestrictions.map(i => i[0])}
- <div className="collectionViewBaseChrome-viewSpecsMenu-row">
- <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
- CREATED WITHIN:
- </div>
- <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
- style={{ textTransform: "uppercase", textAlign: "center" }}
- value={this._dateWithinValue}
- onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
- <option value="1d">1 day of</option>
- <option value="3d">3 days of</option>
- <option value="1w">1 week of</option>
- <option value="2w">2 weeks of</option>
- <option value="1m">1 month of</option>
- <option value="2m">2 months of</option>
- <option value="6m">6 months of</option>
- <option value="1y">1 year of</option>
- </select>
- <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- id={Utils.GenerateGuid()}
- ref={this.datePickerRef}
- value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
- onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
- onPointerDown={this.openDatePicker}
- placeholder="Value" />
- </div>
- <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}> ADD KEY RESTRICTION </button>
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}> APPLY FILTER </button>
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.clearFilter}> CLEAR </button>
- </div>
- </div>
</div>
<div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
<div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 35e3a8958..5c9d2b0dd 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -54,7 +54,7 @@ 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) {
+ if (col._viewType === CollectionViewType.Freeform) {
const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
col._panX = newPanX;
@@ -94,8 +94,6 @@ export class ParentDocSelector extends React.Component<SelectorProps> {
@observer
export class DockingViewButtonSelector extends React.Component<{ views: DocumentView[], Stack: any }> {
- @observable hover = false;
-
customStylesheet(styles: any) {
return {
...styles,
@@ -105,19 +103,24 @@ export class DockingViewButtonSelector extends React.Component<{ views: Document
},
};
}
+ _ref = React.createRef<HTMLDivElement>();
@computed get flyout() {
- trace();
return (
- <div className="ParentDocumentSelector-flyout" title=" ">
+ <div className="ParentDocumentSelector-flyout" title=" " ref={this._ref}>
<DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
</div>
);
}
render() {
- trace();
- return <span title="Tap for menu, drag tab as document" onPointerDown={e => { this.props.views[0].select(false); e.stopPropagation(); }} className="buttonSelector">
+ return <span title="Tap for menu, drag tab as document"
+ onPointerDown={e => {
+ if (getComputedStyle(this._ref.current!).width !== "100%") {
+ e.stopPropagation(); e.preventDefault();
+ }
+ this.props.views[0]?.select(false);
+ }} className="buttonSelector">
<Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
<FontAwesomeIcon icon={"cog"} size={"sm"} />
</Flyout>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index a33146388..09fc5148e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -26,8 +26,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
action(() => {
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => this._opacity = 0.05), 750); // this will unhighlight the link line.
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
const a = adiv.getBoundingClientRect();
@@ -43,7 +43,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
- // really hacky stuff to make the DocuLinkBox display where we want it to:
+ // really hacky stuff to make the LinkAnchorBox display where we want it to:
// if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
const targetAhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]);
@@ -81,8 +81,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
}
render() {
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 49ca024a2..d12f93f15 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -43,60 +43,4 @@ export class CollectionFreeFormLinksView extends React.Component {
{this.props.children}
</div>;
}
- // _brushReactionDisposer?: IReactionDisposer;
- // componentDidMount() {
- // this._brushReactionDisposer = reaction(
- // () => {
- // let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
- // return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
- // },
- // () => {
- // let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
- // let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
- // views.forEach((dstDoc, i) => {
- // views.forEach((srcDoc, j) => {
- // let dstTarg = dstDoc;
- // let srcTarg = srcDoc;
- // let x1 = NumCast(srcDoc.x);
- // let x2 = NumCast(dstDoc.x);
- // let x1w = NumCast(srcDoc.width, -1);
- // let x2w = NumCast(dstDoc.width, -1);
- // if (x1w < 0 || x2w < 0 || i === j) { }
- // else {
- // let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
- // let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
- // return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
- // });
- // let brushAction = (field: (Doc | Promise<Doc>)[]) => {
- // let found = findBrush(field);
- // if (found !== -1) {
- // field.splice(found, 1);
- // }
- // };
- // if (Math.abs(x1 + x1w - x2) < 20) {
- // let linkDoc: Doc = new Doc();
- // linkDoc.title = "Histogram Brush";
- // linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
- // linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
-
- // brushAction = (field: (Doc | Promise<Doc>)[]) => {
- // if (findBrush(field) === -1) {
- // field.push(linkDoc);
- // }
- // };
- // }
- // if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>();
- // if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>();
- // let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
- // let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
- // brushAction(dstBrushDocs);
- // brushAction(srcBrushDocs);
- // }
- // });
- // });
- // });
- // }
- // componentWillUnmount() {
- // this._brushReactionDisposer?.();
- // }
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 730392ab5..e1516b468 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -12,7 +12,7 @@
}
.collectionfreeformview-ease {
- transition: transform 1s;
+ transition: transform 500ms;
}
.collectionfreeformview-none {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a164e1794..f19181a20 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,7 +4,7 @@ import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrows
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
@@ -15,7 +15,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
import { TraceMobx } from "../../../../new_fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
@@ -43,6 +43,8 @@ import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { CollectionViewType } from "../CollectionView";
+import { Script } from "vm";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -66,11 +68,18 @@ export const panZoomSchema = createSchema({
type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+export type collectionFreeformViewProps = {
+ forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
+ childClickScript?: ScriptField;
+ viewDefDivClick?: ScriptField;
+};
@observer
-export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
+export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, undefined as any as collectionFreeformViewProps) {
private _lastX: number = 0;
private _lastY: number = 0;
+ private _downX: number = 0;
+ private _downY: number = 0;
private _inkToTextStartX: number | undefined;
private _inkToTextStartY: number | undefined;
private _wordPalette: Map<string, string> = new Map<string, string>();
@@ -88,9 +97,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@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 : NumCast(this.Document._nativeWidth); }
- @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); }
+ @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.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
+ @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.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";
@@ -99,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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)
+
private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
@@ -134,7 +144,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (this.props.Document.isBackground) return false;
+ // if (this.props.Document.isBackground) return false;
const xf = this.getTransform();
const xfo = this.getTransformOverlay();
const [xp, yp] = xf.transformPoint(de.x, de.y);
@@ -160,7 +170,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const nh = NumCast(layoutDoc._nativeHeight);
layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
}
- this.bringToFront(d);
+ d.isBackground === undefined && this.bringToFront(d);
}));
(de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
@@ -318,17 +328,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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 not using a pen and in no ink mode
if (InkingControl.Instance.selectedTool === InkTool.None) {
- this._lastX = e.pageX;
- this._lastY = e.pageY;
+ this._downX = this._lastX = e.pageX;
+ this._downY = this._lastY = e.pageY;
}
// eraser plus anything else mode
else {
@@ -488,6 +491,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
+ _lastTap = 0;
+
@action
onPointerUp = (e: PointerEvent): void => {
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
@@ -498,6 +503,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.removeEndListeners();
}
+ onClick = (e: React.MouseEvent) => {
+ if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+ if (Date.now() - this._lastTap < 300) {
+ const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
+ this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ this._lastTap = Date.now();
+ }
+ }
+
@action
pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
// bcz: theres should be a better way of doing these than referencing these static instances directly
@@ -505,31 +522,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
PDFMenu.Instance.fadeOut(true);
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- let x = (this.Document._panX || 0) - dx;
- let y = (this.Document._panY || 0) - dy;
- if (!this.isAnnotationOverlay) {
- // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
- const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
- const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
- if (measuredDocs.length) {
- const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
- ({
- xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
- yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
- })
- , {
- xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
- });
-
- const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
- if (ranges.xrange.min > (this.panX() + panelDim[0] / 2)) x = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
- if (ranges.xrange.max < (this.panX() - panelDim[0] / 2)) x = ranges.xrange.min - panelDim[0] / 2;
- if (ranges.yrange.min > (this.panY() + panelDim[1] / 2)) y = ranges.yrange.max + panelDim[1] / 2;
- if (ranges.yrange.max < (this.panY() - panelDim[1] / 2)) y = ranges.yrange.min - panelDim[1] / 2;
- }
- }
- this.setPan(x, y);
+ this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
}
@@ -726,10 +719,33 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
e.stopPropagation();
this.zoom(e.clientX, e.clientY, e.deltaY);
}
+ this.props.Document.targetScale = NumCast(this.props.Document.scale);
}
@action
- setPan(panX: number, panY: number, panType: string = "None") {
+ setPan(panX: number, panY: number, panType: string = "None", clamp: boolean = false) {
+ if (!this.isAnnotationOverlay && clamp) {
+ // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
+ const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
+ const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
+ if (measuredDocs.length) {
+ const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
+ ({
+ xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
+ yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+ })
+ , {
+ xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
+ });
+
+ const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
+ if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
+ else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2;
+ if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2;
+ else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
+ }
+ }
if (!this.Document.lockedTransform || this.Document.inOverlay) {
this.Document.panTransformType = panType;
const scale = this.getLocalTransform().inverse().Scale;
@@ -755,6 +771,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
+ scaleAtPt(docpt: number[], scale: number) {
+ const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+ this.Document.panTransformType = "Ease";
+ this.layoutDoc.scale = scale;
+ const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+ const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
+ const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
+ this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
+ this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
+ }
+
focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
const state = HistoryUtil.getState();
@@ -793,10 +820,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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);
- this.props.focus(this.props.Document);
- willZoom && this.setScaleToZoom(layoutdoc, scale);
+ if (!willZoom) {
+ Doc.BrushDoc(this.props.Document);
+ !doc.z && this.scaleAtPt([NumCast(doc.x), NumCast(doc.y)], 1);
+ } else {
+ if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+ if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // 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);
+ this.props.focus(this.props.Document);
+ willZoom && this.setScaleToZoom(layoutdoc, scale);
+ }
Doc.linkFollowHighlight(doc);
afterFocus && setTimeout(() => {
@@ -806,7 +840,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.Document.scale = savedState.s;
this.Document.panTransformType = savedState.pt;
}
- }, 1000);
+ }, 500);
}
}
@@ -815,24 +849,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
- zoomToScale = (scale: number) => {
- this.Document.scale = scale;
- }
-
- getScale = () => this.Document.scale || 1;
-
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
backgroundHalo = () => BoolCast(this.Document.useClusters);
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
...this.props,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
+ fitToBox: false,
DataDoc: childData,
Document: childLayout,
LibraryPath: this.libraryPath,
+ FreezeDimensions: this.props.freezeChildDimensions,
layoutKey: undefined,
- rootSelected: this.rootSelected,
+ rootSelected: childData ? this.rootSelected : returnFalse,
dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
//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,
@@ -848,11 +880,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
backgroundHalo: this.backgroundHalo,
parentActive: this.props.active,
bringToFront: this.bringToFront,
- zoomToScale: this.zoomToScale,
- getScale: this.getScale
+ addDocTab: this.addDocTab,
};
}
+ addDocTab = (doc: Doc, where: string) => {
+ if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ return true;
+ }
+ return this.props.addDocTab(doc, where);
+ }
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) {
@@ -871,7 +909,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
- (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+ (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
+ e.stopPropagation();
}
private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
const { x, y, z } = viewDef;
@@ -951,11 +990,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const elements: ViewDefResult[] = computedElementData.slice();
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
elements.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+ ele: <CollectionFreeFormDocumentView
+ key={pair.layout[Id]}
+ {...this.getChildDocumentViewProps(pair.layout, pair.data)}
dataProvider={this.childDataProvider}
LayoutDoc={this.childLayoutDocFunc}
+ pointerEvents={this.props.layoutEngine?.() !== undefined ? "none" : undefined}
jitterRotation={NumCast(this.props.Document.jitterRotation)}
- fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />,
+ fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
+ FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
+ />,
bounds: this.childDataProvider(pair.layout)
}));
@@ -1003,47 +1047,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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) => {
if (this.props.children && this.props.annotationsKey) return;
const layoutItems: ContextMenuProps[] = [];
@@ -1074,7 +1077,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (doc instanceof Doc) {
const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
doc.x = xx, doc.y = yy;
- this.props.addDocument && this.props.addDocument(doc);
+ this.props.addDocument?.(doc);
}
}
}
@@ -1105,8 +1108,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
</div>;
}
+
+ _nudgeTime = 0;
+ nudge = action((x: number, y: number) => {
+ if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
+ this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+ NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+ this._nudgeTime = Date.now();
+ setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+ return true;
+ }
+ return false;
+ });
@computed get marqueeView() {
- return <MarqueeView {...this.props} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+ return <MarqueeView {...this.props} nudge={this.nudge} 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}>
@@ -1116,9 +1131,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@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;
+ if (this.props.annotationsKey && !this.props.forceScaling) return 0;
+ const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
+ const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
+ const hscale = nh ? this.props.PanelHeight() / nh : 1;
+ const wscale = nw ? this.props.PanelWidth() / nw : 1;
return wscale < hscale ? wscale : hscale;
}
render() {
@@ -1133,7 +1150,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
return <div className={"collectionfreeformview-container"}
ref={this.createDashEventsTarget}
- onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
style={{
pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 503df10c2..454c3a5f2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,6 +31,7 @@ interface MarqueeViewProps {
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
isAnnotationOverlay?: boolean;
+ nudge: (x: number, y: number) => boolean;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
}
@@ -46,7 +47,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
_commandExecuted = false;
componentDidMount() {
- this.props.setPreviewCursor && this.props.setPreviewCursor(this.setPreviewCursor);
+ this.props.setPreviewCursor?.(this.setPreviewCursor);
}
@action
@@ -243,15 +244,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+ PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
}
});
@action
onClick = (e: React.MouseEvent): void => {
- if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ if (
+ Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- this.setPreviewCursor(e.clientX, e.clientY, false);
+ !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
// let the DocumentView stopPropagation of this event when it selects this document
} else { // why do we get a click event when the cursor have moved a big distance?
// let's cut it off here so no one else has to deal with it.
@@ -307,7 +309,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = (selected: Doc[], asTemplate: boolean, isBackground: boolean = false) => {
+ getCollection = (selected: Doc[], asTemplate: boolean, isBackground?: boolean) => {
const bounds = this.Bounds;
// const inkData = this.ink ? this.ink.inkData : undefined;
const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
@@ -585,13 +587,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
* This contains the "C for collection, ..." text on marquees.
* Commented out by syip2 when the marquee menu was added.
*/
- return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
+ return <div className="marquee" style={{
+ transform: `translate(${p[0]}px, ${p[1]}px)`,
+ width: `${Math.abs(v[0])}`,
+ height: `${Math.abs(v[1])}`, zIndex: 2000
+ }} >
{/* <span className="marquee-legend" /> */}
</div>;
}
render() {
- return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+ return <div className="marqueeView"
+ style={{ overflow: StrCast(this.props.Document.overflow), }}
+ onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
{this.props.children}
</div>;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index aa8e1fb43..0e1cc2010 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -14,6 +14,7 @@ import "./collectionMulticolumnView.scss";
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
import { List } from '../../../../new_fields/List';
+import { returnZero } from '../../../../Utils';
type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
const MulticolumnDocument = makeInterface(documentSchema);
@@ -203,11 +204,24 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ addDocTab = (doc: Doc, where: string) => {
+ if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ return true;
+ }
+ return this.props.addDocTab(doc, where);
+ }
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return <ContentFittingDocumentView
{...this.props}
Document={layout}
DataDocument={layout.resolvedDataDoc as Doc}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ addDocTab={this.addDocTab}
+ fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
backgroundColor={this.props.backgroundColor}
CollectionDoc={this.props.Document}
PanelWidth={width}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 5e59f8237..1eb486c4f 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -6,7 +6,7 @@ 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 { Utils, returnZero } from '../../../../Utils';
import "./collectionMultirowView.scss";
import { computed, trace, observable, action } from 'mobx';
import { Transform } from '../../../util/Transform';
@@ -14,6 +14,7 @@ import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
import { undoBatch } from '../../../util/UndoManager';
import { DragManager } from '../../../util/DragManager';
+import { List } from '../../../../new_fields/List';
type MultirowDocument = makeInterface<[typeof documentSchema]>;
const MultirowDocument = makeInterface(documentSchema);
@@ -203,11 +204,24 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ addDocTab = (doc: Doc, where: string) => {
+ if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ return true;
+ }
+ return this.props.addDocTab(doc, where);
+ }
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return <ContentFittingDocumentView
{...this.props}
Document={layout}
DataDocument={layout.resolvedDataDoc as Doc}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ addDocTab={this.addDocTab}
+ fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
backgroundColor={this.props.backgroundColor}
CollectionDoc={this.props.Document}
PanelWidth={width}
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 019f931f9..9d3d2e592 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -21,7 +21,7 @@ serif;
// misc values
$border-radius: 0.3em;
//
-$search-thumnail-size: 175;
+$search-thumnail-size: 130;
// dragged items
$contextMenu-zindex: 100000; // context menu shows up over everything
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index fb16b8365..53b54d7e4 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -88,7 +88,7 @@
opacity:0.9;
background-color: transparent;
box-shadow: black 2px 2px 1px;
- .docuLinkBox-cont {
+ .linkAnchorBox-cont {
position: relative !important;
height: 100% !important;
width: 100% !important;
@@ -103,7 +103,7 @@
box-shadow: black 1px 1px 1px;
margin-left: -1;
margin-top: -2;
- .docuLinkBox-cont {
+ .linkAnchorBox-cont {
position: relative !important;
height: 100% !important;
width: 100% !important;
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index bd54d64ff..7078cc01c 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -4,10 +4,10 @@ import { observer } from "mobx-react";
import "./AudioBox.scss";
import { Cast, DateCast, NumCast } from "../../../new_fields/Types";
import { AudioField, nullAudio } from "../../../new_fields/URLField";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
import { makeInterface, createSchema } from "../../../new_fields/Schema";
import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
import { DateField } from "../../../new_fields/DateField";
import { SelectionManager } from "../../util/SelectionManager";
@@ -39,7 +39,7 @@ type AudioDocument = makeInterface<[typeof documentSchema, typeof audioSchema]>;
const AudioDocument = makeInterface(documentSchema, audioSchema);
@observer
-export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
+export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument>(AudioDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
public static Enabled = false;
@@ -80,10 +80,10 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(),
selected => {
const sel = selected.length ? selected[0].props.Document : undefined;
- this.Document.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime());
- this.Document.playOnSelect && this.recordingStart && !sel && this.pause();
+ this.layoutDoc.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime());
+ this.layoutDoc.playOnSelect && this.recordingStart && !sel && this.pause();
});
- this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, (time) => this.Document.playOnSelect && this.playFromTime(AudioBox._scrubTime));
+ this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
}
timecodeChanged = () => {
@@ -94,14 +94,14 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
let la1 = l.anchor1 as Doc;
let linkTime = NumCast(l.anchor2_timecode);
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
- la1 = l.anchor2 as Doc;
linkTime = NumCast(l.anchor1_timecode);
+ la1 = l.anchor2 as Doc;
}
- if (linkTime > NumCast(this.Document.currentTimecode) && linkTime < htmlEle.currentTime) {
+ if (linkTime > NumCast(this.layoutDoc.currentTimecode) && linkTime < htmlEle.currentTime) {
Doc.linkFollowHighlight(la1);
}
});
- this.Document.currentTimecode = htmlEle.currentTime;
+ this.layoutDoc.currentTimecode = htmlEle.currentTime;
}
}
@@ -135,7 +135,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
updateRecordTime = () => {
if (this.audioState === "recording") {
setTimeout(this.updateRecordTime, 30);
- this.Document.currentTimecode = (new Date().getTime() - this._recordStart) / 1000;
+ this.layoutDoc.currentTimecode = (new Date().getTime() - this._recordStart) / 1000;
}
}
@@ -157,7 +157,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (this.Document.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.Document.playOnSelect = !this.Document.playOnSelect, icon: "expand-arrows-alt" });
+ funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" });
}
@@ -184,7 +184,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
e.stopPropagation();
}
onStop = (e: any) => {
- this.Document.playOnSelect = !this.Document.playOnSelect;
+ this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect;
e.stopPropagation();
}
onFile = (e: any) => {
@@ -194,8 +194,8 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
_width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height)
});
Doc.GetProto(newDoc).recordingSource = this.dataDoc;
- Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`this.recordingSource["${this.props.fieldKey}-recordingStart"]`);
- Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("this.recordingSource.audioState");
+ Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
+ Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("self.recordingSource.audioState");
this.props.addDocument?.(newDoc);
e.stopPropagation();
}
@@ -226,7 +226,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
{!this.path ?
<div className="audiobox-buttons">
<div className="audiobox-dictation" onClick={this.onFile}>
- <FontAwesomeIcon style={{ width: "30px", background: this.Document.playOnSelect ? "yellow" : "dimGray" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
<button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "red" : "black" }}>
{this.audioState === "recording" ? "STOP" : "RECORD"}
@@ -235,13 +235,13 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
<div className="audiobox-controls">
<div className="audiobox-player" onClick={this.onPlay}>
<div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this.audioState === "paused" ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
- <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.Document.playOnSelect ? "yellow" : "dimGray" }} icon="hand-point-left" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+ <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="hand-point-left" 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();
const wasPaused = this.audioState === "paused";
- this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+ this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
wasPaused && this.pause();
e.stopPropagation();
}
@@ -260,20 +260,20 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
<div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
<DocumentView {...this.props}
Document={l}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
rootSelected={returnFalse}
layoutKey={Doc.LinkEndpoint(l, la2)}
ContainingCollectionDoc={this.props.Document}
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) { const wasPaused = this.audioState === "paused"; this.playFrom(linkTime); wasPaused && this.pause(); e.stopPropagation(); } }} />
</div>;
})}
- <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
+ <div className="audiobox-current" style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
{this.audio}
</div>
</div>
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
deleted file mode 100644
index 1b70ff824..000000000
--- a/src/client/views/nodes/ButtonBox.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-regular-svg-icons';
-import { action, computed } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
-import { List } from '../../../new_fields/List';
-import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
-import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types';
-import { DragManager } from '../../util/DragManager';
-import { undoBatch } from '../../util/UndoManager';
-import { DocComponent } from '../DocComponent';
-import './ButtonBox.scss';
-import { FieldView, FieldViewProps } from './FieldView';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ContextMenu } from '../ContextMenu';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-
-
-library.add(faEdit as any);
-
-const ButtonSchema = createSchema({
- onClick: ScriptField,
- buttonParams: listSpec("string"),
- text: "string"
-});
-
-type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>;
-const ButtonDocument = makeInterface(ButtonSchema, documentSchema);
-
-@observer
-export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); }
- private dropDisposer?: DragManager.DragDropDisposer;
-
- @computed get dataDoc() {
- return this.props.DataDoc &&
- (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
- this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
- }
-
-
- protected createDropTarget = (ele: HTMLDivElement) => {
- this.dropDisposer?.();
- if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
- }
- }
-
- specificContextMenu = (e: React.MouseEvent): void => {
- const funcs: ContextMenuProps[] = [];
- funcs.push({
- description: "Clear Script Params", event: () => {
- const params = FieldValue(this.Document.buttonParams);
- params?.map(p => this.props.Document[p] = undefined);
- }, icon: "trash"
- });
-
- ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
- }
-
- @undoBatch
- @action
- drop = (e: Event, de: DragManager.DropEvent) => {
- const docDragData = de.complete.docDragData;
- const params = this.Document.buttonParams;
- const missingParams = params?.filter(p => this.props.Document[p] === undefined);
- if (docDragData && missingParams?.includes((e.target as any).textContent)) {
- this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
- d.onDragStart ? docDragData.draggedDocuments[i] : d));
- e.stopPropagation();
- }
- }
- // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
- render() {
- const params = this.Document.buttonParams;
- const missingParams = params?.filter(p => this.props.Document[p] === undefined);
- params?.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ...
- 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 || "inherit",
- fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: (this.Document.textTransform as any) || ""
- }} >
- <div className="buttonBox-mainButtonCenter">
- {(this.Document.text || this.Document.title)}
- </div>
- </div>
- <div className="buttonBox-params" >
- {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="buttonBox-missingParam">{m}</div>)}
- </div>
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 356192797..05ad98c43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -10,7 +10,6 @@ import { DocumentView, DocumentViewProps } from "./DocumentView";
import React = require("react");
import { PositionDocument } from "../../../new_fields/documentSchemas";
import { TraceMobx } from "../../../new_fields/util";
-import { returnFalse } from "../../../Utils";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@@ -23,6 +22,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
width?: number;
height?: number;
jitterRotation: number;
+ pointerEvents?: "none";
transition?: string;
fitToBox?: boolean;
}
@@ -41,9 +41,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
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 freezeDimensions() { return this.props.FreezeDimensions; }
@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, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+ @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
@computed get renderScriptDim() {
if (this.Document.renderScript) {
@@ -59,15 +60,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
return undefined;
}
+ nudge = (x: number, y: number) => {
+ this.props.Document.x = NumCast(this.props.Document.x) + x;
+ this.props.Document.y = NumCast(this.props.Document.y) + y;
+ }
- contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox ? this.width / this.nativeWidth : 1;
- panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth());
- panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight());
+ contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
+ 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())
focusDoc = (doc: Doc) => this.props.focus(doc, false);
+ NativeWidth = () => this.nativeWidth;
+ NativeHeight = () => this.nativeHeight;
render() {
TraceMobx();
return <div className="collectionFreeFormDocumentView-container"
@@ -86,25 +93,30 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
height: this.height,
zIndex: this.ZInd,
display: this.ZInd === -99 ? "none" : undefined,
- pointerEvents: this.props.Document.isBackground ? "none" : undefined
+ pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents
}} >
- {!this.props.fitToBox ?
- <DocumentView {...this.props}
- dragDivName={"collectionFreeFormDocumentView-container"}
- ContentScaling={this.contentScaling}
- ScreenToLocalTransform={this.getTransform}
- backgroundColor={this.props.backgroundColor}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight} />
- : <ContentFittingDocumentView {...this.props}
- CollectionDoc={this.props.ContainingCollectionDoc}
- DataDocument={this.props.DataDoc}
- getTransform={this.getTransform}
- active={returnFalse}
- focus={this.focusDoc}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
+ {!this.props.fitToBox ?
+ <DocumentView {...this.props}
+ nudge={this.nudge}
+ dragDivName={"collectionFreeFormDocumentView-container"}
+ ContentScaling={this.contentScaling}
+ ScreenToLocalTransform={this.getTransform}
+ backgroundColor={this.props.backgroundColor}
+ NativeHeight={this.NativeHeight}
+ NativeWidth={this.NativeWidth}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight} />
+ : <ContentFittingDocumentView {...this.props}
+ CollectionDoc={this.props.ContainingCollectionDoc}
+ DataDocument={this.props.DataDoc}
+ getTransform={this.getTransform}
+ NativeHeight={this.NativeHeight}
+ NativeWidth={this.NativeWidth}
+ active={this.props.parentActive}
+ focus={this.focusDoc}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
/>}
</div>;
}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index d34d63d01..877dfec2d 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -6,7 +6,7 @@ import { makeInterface } from "../../../new_fields/Schema";
import { StrCast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { SelectionManager } from "../../util/SelectionManager";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
import "./ColorBox.scss";
import { FieldView, FieldViewProps } from './FieldView';
@@ -15,11 +15,11 @@ type ColorDocument = makeInterface<[typeof documentSchema]>;
const ColorDocument = makeInterface(documentSchema);
@observer
-export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocument>(ColorDocument) {
+export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument>(ColorDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
render() {
- const selDoc = SelectionManager.SelectedDocuments()?.[0]?.Document;
+ const selDoc = SelectionManager.SelectedDocuments()?.[0]?.rootDoc;
return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index fdf2a9551..641797cac 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import "react-table/react-table.css";
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { ScriptField } from "../../../new_fields/ScriptField";
import { NumCast, StrCast } from "../../../new_fields/Types";
import { TraceMobx } from "../../../new_fields/util";
@@ -15,11 +15,13 @@ import "./ContentFittingDocumentView.scss";
import { dropActionType } from "../../util/DragManager";
interface ContentFittingDocumentViewProps {
- Document?: Doc;
+ Document: Doc;
DataDocument?: Doc;
LayoutDoc?: () => Opt<Doc>;
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ FreezeDimensions?: boolean;
LibraryPath: Doc[];
- childDocs?: Doc[];
renderDepth: number;
fitToBox?: boolean;
layoutKey?: string;
@@ -40,19 +42,20 @@ interface ContentFittingDocumentViewProps {
addDocTab: (document: Doc, where: string) => boolean;
pinToPres: (document: Doc) => void;
dontRegisterView?: boolean;
- rootSelected: () => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
}
@observer
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 && (this.props.LayoutDoc?.() || 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 get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
+ @computed get freezeDimensions() { return this.props.FreezeDimensions; }
+ nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
+ nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : 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;
+ const wscale = this.props.PanelWidth() / this.nativeWidth();
+ if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
+ return (this.props.PanelHeight() / this.nativeHeight()) || 1;
}
return wscale || 1;
}
@@ -61,12 +64,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
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(); }
+ @computed get panelWidth() { return this.nativeWidth && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
+ @computed get panelHeight() { return this.nativeHeight && !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 centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; }
+ private get centeringOffset() { return this.nativeWidth() && !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); }
@@ -81,7 +84,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
style={{
transform: `translate(${this.centeringOffset}px, 0px)`,
borderRadius: this.borderRounding,
- height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
+ height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight() / this.nativeWidth() * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth()
}}>
<DocumentView {...this.props}
@@ -89,6 +92,11 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
DataDoc={this.props.DataDocument}
LayoutDoc={this.props.LayoutDoc}
LibraryPath={this.props.LibraryPath}
+ NativeWidth={this.nativeWidth}
+ NativeHeight={this.nativeHeight}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
+ ContentScaling={this.contentScaling}
fitToBox={this.props.fitToBox}
layoutKey={this.props.layoutKey}
dropAction={this.props.dropAction}
@@ -105,14 +113,9 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
parentActive={this.props.active}
ScreenToLocalTransform={this.getTransform}
renderDepth={this.props.renderDepth}
- ContentScaling={this.contentScaling}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
focus={this.props.focus || emptyFunction}
bringToFront={emptyFunction}
dontRegisterView={this.props.dontRegisterView}
- zoomToScale={emptyFunction}
- getScale={returnOne}
/>
</div>)}
</div>);
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 47118e758..ac562f19a 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -9,7 +9,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyPath } from "../../../Utils";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
import "./DocumentBox.scss";
import { FieldView, FieldViewProps } from "./FieldView";
@@ -18,12 +18,12 @@ import { TraceMobx } from "../../../new_fields/util";
import { DocumentView } from "./DocumentView";
import { Docs } from "../../documents/Documents";
-type DocBoxSchema = makeInterface<[typeof documentSchema]>;
-const DocBoxDocument = makeInterface(documentSchema);
+type DocHolderBoxSchema = makeInterface<[typeof documentSchema]>;
+const DocHolderBoxDocument = makeInterface(documentSchema);
@observer
-export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocumentBox, fieldKey); }
+export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
_prevSelectionDisposer: IReactionDisposer | undefined;
_selections: Doc[] = [];
_curSelection = -1;
@@ -54,7 +54,7 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
this.contentDoc[this.props.fieldKey] = this.props.Document[this.props.fieldKey];
}
showSelection = () => {
- this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(this,this.excludeCollections,[_last_])?.[0]`);
+ this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`);
}
isSelectionLocked = () => {
const kvpstring = Field.toKeyValueString(this.contentDoc, this.props.fieldKey);
@@ -112,7 +112,7 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) {
setTimeout(() => {
DocumentView.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
- Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null)!, containedDoc, undefined);
+ Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
}, 0);
}
const contents = !(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index dc71ba280..7522af3a3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -3,7 +3,6 @@ import { observer } from "mobx-react";
import { Doc, Opt } from "../../../new_fields/Doc";
import { Cast, StrCast } from "../../../new_fields/Types";
import { OmitKeys, Without } from "../../../Utils";
-import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -11,10 +10,11 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionView } from "../collections/CollectionView";
import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
import { AudioBox } from "./AudioBox";
-import { ButtonBox } from "./ButtonBox";
+import { LabelBox } from "./LabelBox";
import { SliderBox } from "./SliderBox";
import { LinkBox } from "./LinkBox";
-import { DocumentBox } from "./DocumentBox";
+import { ScriptingBox } from "./ScriptingBox";
+import { DocHolderBox } from "./DocumentBox";
import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FontIconBox } from "./FontIconBox";
@@ -27,7 +27,7 @@ import { PresBox } from "./PresBox";
import { QueryBox } from "./QueryBox";
import { ColorBox } from "./ColorBox";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
-import { DocuLinkBox } from "./DocuLinkBox";
+import { LinkAnchorBox } from "./LinkAnchorBox";
import { PresElementBox } from "../presentationview/PresElementBox";
import { ScreenshotBox } from "./ScreenshotBox";
import { VideoBox } from "./VideoBox";
@@ -109,10 +109,10 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
<ObserverJsxParser
blacklistedAttrs={[]}
components={{
- FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView,
+ FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
- ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox,
+ PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
+ ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
RecommendationsBox, ScreenshotBox
}}
bindings={this.CreateBindings()}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index d1d96f0a1..fc9ee1201 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -34,13 +34,18 @@
overflow-y: scroll;
height: calc(100% - 20px);
}
-
- .documentView-docuLinkWrapper {
+ .documentView-linkAnchorBoxAnchor {
+ display:flex;
+ overflow: hidden;
+ }
+ .documentView-linkAnchorBoxWrapper {
pointer-events: none;
position: absolute;
transform-origin: top left;
width: 100%;
height: 100%;
+ top:0;
+ left:0;
z-index: 1;
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 42f5fb946..c0d530160 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
import { action, computed, runInAction, trace, observable } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
import { InkTool } from '../../../new_fields/InkField';
@@ -14,7 +14,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField';
import { TraceMobx } from '../../../new_fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils, OmitKeys } from "../../../Utils";
+import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils, OmitKeys, returnZero } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
@@ -47,25 +47,31 @@ import { ClientRecommender } from '../../ClientRecommender';
import { SearchUtil } from '../../util/SearchUtil';
import { RadialMenu } from './RadialMenu';
import { KeyphraseQueryView } from '../KeyphraseQueryView';
+import { undo } from 'prosemirror-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,
fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
+export type DocFocusFunc = () => boolean;
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
+ FreezeDimensions?: boolean;
+ NativeWidth: () => number;
+ NativeHeight: () => number;
Document: Doc;
DataDoc?: Doc;
LayoutDoc?: () => Opt<Doc>;
LibraryPath: Doc[];
fitToBox?: boolean;
- rootSelected: () => boolean; // whether the root of a template has been selected
+ rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
onClick?: ScriptField;
onPointerDown?: ScriptField;
onPointerUp?: ScriptField;
dropAction?: dropActionType;
dragDivName?: string;
+ nudge?: (x: number, y: number) => void;
addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
@@ -74,16 +80,14 @@ export interface DocumentViewProps {
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
- focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
+ focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
parentActive: (outsideReaction: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
pinToPres: (document: Doc) => void;
- zoomToScale: (scale: number) => void;
backgroundHalo?: () => boolean;
backgroundColor?: (doc: Doc) => string | undefined;
- getScale: () => number;
ChromeHeight?: () => number;
dontRegisterView?: boolean;
layoutKey?: string;
@@ -108,19 +112,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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); }
+ 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.layoutDoc.onClick || this.Document.onClick; }
+ @computed get freezeDimensions() { return this.props.FreezeDimensions; }
+ @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+ @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+ @computed get onClickHandler() { return this.props.onClick || Cast(this.layoutDoc.onClick, ScriptField, null) || 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; }
+ NativeWidth = () => this.nativeWidth;
+ NativeHeight = () => this.nativeHeight;
private _firstX: number = -1;
private _firstY: number = -1;
-
-
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -193,27 +198,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
// this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
- !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
+ if (!this.props.dontRegisterView) {
+ DocumentManager.Instance.DocumentViews.push(this);
+ }
}
@action
componentDidUpdate() {
- this._dropDisposer && this._dropDisposer();
- this._gestureEventDisposer && this._gestureEventDisposer();
- this.multiTouchDisposer && this.multiTouchDisposer();
- this.holdDisposer && this.holdDisposer();
- 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._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
+ this._dropDisposer?.();
+ this._gestureEventDisposer?.();
+ this.multiTouchDisposer?.();
+ this.holdDisposer?.();
+ if (this._mainCont.current) {
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this));
+ this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
+ this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+ this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+ }
}
@action
componentWillUnmount() {
- this._dropDisposer && this._dropDisposer();
- this._gestureEventDisposer && this._gestureEventDisposer();
- this.multiTouchDisposer && this.multiTouchDisposer();
- this.holdDisposer && this.holdDisposer();
+ this._dropDisposer?.();
+ this._gestureEventDisposer?.();
+ this.multiTouchDisposer?.();
+ this.holdDisposer?.();
Doc.UnBrushDoc(this.props.Document);
if (!this.props.dontRegisterView) {
const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -273,38 +282,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- dontDecorateSelection: any = false;
onClick = (e: React.MouseEvent | React.PointerEvent) => {
- this.dontDecorateSelection = this.props.Document.dontDecorateSelection && (!e.ctrlKey || e.button < 2);
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
let stopPropagate = true;
let preventDefault = true;
- this.props.bringToFront(this.props.Document);
+ this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
- const fullScreenAlias = Doc.MakeAlias(this.props.Document);
- if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
- fullScreenAlias.layoutKey = "layout_fullScreen";
+ if (!(e.nativeEvent as any).formattedHandled) {
+ const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+ if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+ fullScreenAlias.layoutKey = "layout_fullScreen";
+ }
+ UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
+ SelectionManager.DeselectAll();
+ Doc.UnBrushDoc(this.props.Document);
}
- UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
- SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this.props.Document);
- } else if (this.onClickHandler?.script) {
- SelectionManager.DeselectAll();
- UndoManager.RunInBatch(() => this.onClickHandler!.script.run({
- this: this.props.Document,
- self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document,
- containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
- }, console.log) && !this.props.Document.dontDecorateSelection && !this.props.Document.isButton && this.select(false), "on click");
- } else if (this.Document.type === DocumentType.BUTTON) {
- UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
- } else if (this.Document.isButton) {
- SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
- UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow");
+ } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ //SelectionManager.DeselectAll();
+ const func = () => this.onClickHandler.script.run({
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ }, console.log);
+ if (this.props.Document !== Doc.UserDoc().undoBtn && this.props.Document !== Doc.UserDoc().redoBtn) {
+ UndoManager.RunInBatch(func, "on click");
+ } else func();
+ } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
+ UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
+ //ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
+ } else if (this.Document.isLinkButton) {
+ DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
- if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) {
- stopPropagate = false;
+ if ((this.props.Document.onDragStart || (this.props.Document.rootDocument && this.props.Document.isTemplateForField)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+ stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
+ this.props.focus(this.props.Document, false);
SelectionManager.SelectDoc(this, e.ctrlKey);
}
preventDefault = false;
@@ -314,14 +327,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
- const linkDocs = DocListCast(this.props.Document.links);
- if (linkDocs.length) {
- DocumentManager.Instance.FollowLink(undefined, this.props.Document,
- // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
- (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, maxLocation)),
- ctrlKey, altKey, this.props.ContainingCollectionDoc);
- }
+ // follows a link - if the target is on screen, it highlights/pans to it.
+ // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
+ // depending on the followLinkLocation property of the source (or the link itself as a fallback);
+ followLinkClick = async (altKey: boolean, ctrlKey: boolean, shiftKey: boolean) => {
+ const batch = UndoManager.StartBatch("follow link click");
+ // open up target if it's not already in view ...
+ const createViewFunc = (doc: Doc, followLoc: string, finished: Opt<() => void>) => {
+ const targetFocusAfterDocFocus = () => {
+ const where = StrCast(this.Document.followLinkLocation) || followLoc;
+ const hackToCallFinishAfterFocus = () => {
+ finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
+ return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
+ };
+ this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); // add the target and focus on it.
+ return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
+ };
+ // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
+ this.props.focus(this.props.Document, true, 1, targetFocusAfterDocFocus);
+ };
+ await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
}
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
@@ -492,8 +517,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerUp = (e: PointerEvent): void => {
this.cleanUpInteractions();
- 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);
+ if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
document.removeEventListener("pointerup", this.onPointerUp);
return;
}
@@ -517,7 +542,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
// applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
- static makeCustomViewClicked = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+ static makeCustomViewClicked = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
const batch = UndoManager.StartBatch("makeCustomViewClicked");
runInAction(() => {
doc.layoutKey = "layout_" + templateSignature;
@@ -527,16 +552,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
batch.end();
}
- static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+ static findTemplate(templateName: string, type: string, signature: string) {
+ let docLayoutTemplate: Opt<Doc>;
const iconViews = DocListCast(Cast(Doc.UserDoc().iconViews, Doc, null)?.data);
const templBtns = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data);
- const allTemplates = iconViews.concat(templBtns).concat(noteTypes).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
- const templateName = templateSignature.replace(/\(.*\)/, "");
+ const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
+ const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
// bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
// first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === doc.type + "_" + templateName && (docLayoutTemplate = tempDoc));
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === type + "_" + templateName && (docLayoutTemplate = tempDoc));
!docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+ return docLayoutTemplate;
+ }
+ static createCustomView = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+ const templateName = templateSignature.replace(/\(.*\)/, "");
+ docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, StrCast(doc.type), templateSignature);
const customName = "layout_" + templateSignature;
const _width = NumCast(doc._width);
@@ -555,20 +586,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
} else if (doc.data instanceof ImageField) {
fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
}
- const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+ const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));
- Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
}
@undoBatch
- toggleButtonBehavior = (): void => {
- if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) {
- this.Document.isButton = false;
+ toggleLinkButtonBehavior = (): void => {
+ if (this.Document.isLinkButton || this.Document.onClick || this.Document.ignoreClick) {
+ this.Document.isLinkButton = false;
this.Document.ignoreClick = false;
this.Document.onClick = undefined;
} else {
- this.Document.isButton = true;
+ this.Document.isLinkButton = true;
+ this.Document.followLinkLocation = undefined;
+ }
+ }
+
+ @undoBatch
+ toggleFollowInPlace = (): void => {
+ if (this.Document.isLinkButton) {
+ this.Document.isLinkButton = false;
+ } else {
+ this.Document.isLinkButton = true;
+ this.Document.followLinkLocation = "inPlace";
}
}
@@ -613,10 +655,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
makeIntoPortal = async () => {
const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document);
if (!portalLink) {
- const portal = Docs.Create.FreeformDocument([], { _width: (this.layoutDoc._width || 0) + 10, _height: this.layoutDoc._height || 0, title: StrCast(this.props.Document.title) + ".portal" });
+ const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
}
- this.Document.isButton = true;
+ this.Document.isLinkButton = true;
}
@undoBatch
@@ -630,8 +672,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- makeBackground = (): void => {
- this.Document.isBackground = !this.Document.isBackground;
+ toggleBackground = (temporary: boolean): void => {
+ this.Document.overflow = temporary ? "visible" : "hidden";
+ this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
this.Document.isBackground && this.props.bringToFront(this.Document, true);
}
@@ -648,27 +691,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@action
- onContextMenu = async (e: React.MouseEvent): Promise<void> => {
+ onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
- if (e.button === 0 && !e.ctrlKey) {
- e.preventDefault();
- return;
- }
- e.persist();
- e?.stopPropagation();
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
- e.isDefaultPrevented()) {
+ if (!(e instanceof Touch)) {
+ if (e.button === 0 && !e.ctrlKey) {
+ e.preventDefault();
+ return;
+ }
+ e.persist();
+ e?.stopPropagation();
+
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
+ e.isDefaultPrevented()) {
+ e.preventDefault();
+ return;
+ }
e.preventDefault();
- return;
}
- e.preventDefault();
const cm = ContextMenu.Instance;
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
const existing = cm.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" });
+ layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: (e) => this.toggleBackground(false), icon: this.Document.lockedPosition ? "unlock" : "lock" });
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" });
@@ -698,8 +744,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleButtonBehavior, icon: "concierge-bell" });
- onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
+ onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
@@ -741,7 +788,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
recommender_subitems.push({
description: "Internal recommendations",
- event: () => this.recommender(e),
+ event: () => this.recommender(),
icon: "brain"
});
@@ -802,7 +849,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
icon: "external-link-alt"
});
- if (!this.topMost) {
+ if (!this.topMost && !(e instanceof Touch)) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
}
@@ -820,7 +867,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
- recommender = async (e: React.MouseEvent) => {
+ recommender = async () => {
if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
const documents: Doc[] = [];
const allDocs = await SearchUtil.GetAllDocs();
@@ -831,7 +878,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
await Promise.all(allDocs.map((doc: Doc) => {
let isMainDoc: boolean = false;
const dataDoc = Doc.GetProto(doc);
- if (doc.type === DocumentType.TEXT) {
+ if (doc.type === DocumentType.RTF) {
if (dataDoc === Doc.GetProto(this.props.Document)) {
isMainDoc = true;
}
@@ -934,37 +981,41 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const fallback = Cast(this.props.Document.layoutKey, "string");
return typeof fallback === "string" ? fallback : "layout";
}
- rootSelected = () => {
- return this.isSelected(false) || (this.props.Document.forceActive && this.props.rootSelected?.() ? true : false);
+ rootSelected = (outsideReaction?: boolean) => {
+ return this.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected?.(outsideReaction));
}
childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+ panelWidth = () => this.props.PanelWidth();
+ panelHeight = () => this.props.PanelHeight();
+ screenToLocalTransform = () => this.props.ScreenToLocalTransform();
@computed get contents() {
TraceMobx();
return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ NativeWidth={this.NativeWidth}
+ NativeHeight={this.NativeHeight}
Document={this.props.Document}
DataDoc={this.props.DataDoc}
LayoutDoc={this.props.LayoutDoc}
makeLink={this.makeLink}
rootSelected={this.rootSelected}
+ dontRegisterView={this.props.dontRegisterView}
fitToBox={this.props.fitToBox}
LibraryPath={this.props.LibraryPath}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ ScreenToLocalTransform={this.screenToLocalTransform}
renderDepth={this.props.renderDepth}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
focus={this.props.focus}
parentActive={this.props.parentActive}
whenActiveChanged={this.props.whenActiveChanged}
bringToFront={this.props.bringToFront}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
- zoomToScale={this.props.zoomToScale}
backgroundColor={this.props.backgroundColor}
- getScale={this.props.getScale}
ContentScaling={this.childScaling}
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
@@ -974,7 +1025,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
- // used to decide whether a link document should be created or not.
+ // used to decide whether a link anchor view should be created or not.
// if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
@@ -983,36 +1034,44 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
}
- @observable _link: Opt<Doc>;
- makeLink = () => {
- return this._link;
- }
+ @observable _link: Opt<Doc>; // see DocumentButtonBar for explanation of how this works
+ makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
+
+ @undoBatch
+ hideLinkAnchor = (doc: Doc) => doc.hidden = true
+ anchorPanelWidth = () => this.props.PanelWidth() || 1;
+ anchorPanelHeight = () => this.props.PanelHeight() || 1;
+ @computed get anchors() {
+ TraceMobx();
+ return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+ <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}>
+ <DocumentView {...this.props}
+ Document={d}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox
+ PanelWidth={this.anchorPanelWidth}
+ PanelHeight={this.anchorPanelHeight}
+ layoutKey={this.linkEndpoint(d)}
+ ContentScaling={returnOne}
+ backgroundColor={returnTransparent}
+ removeDocument={this.hideLinkAnchor}
+ LayoutDoc={undefined}
+ />
+ </div>);
+ }
@computed get innards() {
TraceMobx();
- if (!this.props.PanelWidth()) {
- return <div style={{ display: "flex", overflow: "hidden" }}>
+ if (!this.props.PanelWidth()) { // this happens when the document is a tree view label
+ return <div className="documentView-linkAnchorBoxAnchor" >
{StrCast(this.props.Document.title)}
- {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-docuLinkWrapper" style={{ position: "absolute", top: 0, left: 0 }} key={`${d[Id]}`}>
- <DocumentView {...this.props}
- Document={d}
- ContainingCollectionDoc={this.props.Document}
- PanelWidth={returnOne} PanelHeight={returnOne}
- layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
- backgroundColor={returnTransparent}
- removeDocument={undoBatch(doc => doc.hidden = true)} />
- </div>)}
+ {this.anchors}
</div>;
}
const showTitle = StrCast(this.layoutDoc._showTitle);
const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
const showCaption = StrCast(this.layoutDoc._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}
- </div>);
const captionView = (!showCaption ? (null) :
<div className="documentView-captionWrapper">
<DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
@@ -1039,32 +1098,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
/>
</div>);
return <>
- {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}>
- <DocumentView {...this.props} ContentScaling={returnOne} ContainingCollectionDoc={this.props.Document} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
- </div>)}
+ {this.anchors}
{!showTitle && !showCaption ?
- this.Document.searchFields ?
- (<div className="documentView-searchWrapper">
- {this.contents}
- {searchHighlight}
- </div>)
- :
- this.contents
- :
+ this.contents :
<div className="documentView-styleWrapper" >
<div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
{this.contents}
</div>
{titleView}
{captionView}
- {searchHighlight}
</div>
}
</>;
}
@computed get ignorePointerEvents() {
- return (this.Document.isBackground && !this.isSelected()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ return (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
}
@observable _animate = 0;
@@ -1110,12 +1158,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
background: finalColor,
opacity: this.Document.opacity
}}>
- {this.Document.isBackground ? <div className="documentView-lock"> <FontAwesomeIcon icon="unlock" size="lg" /> </div> : (null)}
{this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
{this.innards}
<div className="documentView-contentBlocker" />
</> :
this.innards}
+ {(this.Document.isBackground !== undefined || this.isSelected(false)) && this.props.renderDepth > 0 ? <div className="documentView-lock" onClick={() => this.toggleBackground(true)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} size="lg" /> </div> : (null)}
</div>;
{ this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 13a1becf7..a3790d38b 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,7 +29,7 @@ export interface FieldViewProps {
dropAction: dropActionType;
isSelected: (outsideReaction?: boolean) => boolean;
select: (isCtrlPressed: boolean) => void;
- rootSelected: () => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
addDocument?: (document: Doc) => boolean;
addDocTab: (document: Doc, where: string) => boolean;
@@ -45,6 +45,8 @@ export interface FieldViewProps {
focus: (doc: Doc) => void;
PanelWidth: () => number;
PanelHeight: () => number;
+ NativeHeight: () => number;
+ NativeWidth: () => number;
setVideoBox?: (player: VideoBox) => void;
ContentScaling: () => number;
ChromeHeight?: () => number;
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index d4da21239..9329cf210 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -56,7 +56,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
background: StrCast(referenceLayout.backgroundColor),
boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
}}>
- <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
+ <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={this._foregroundColor} size="sm" />
</button>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 526939438..7d40b3149 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -39,11 +39,6 @@
position: absolute;
}
}
-
-.collectionfreeformview-container {
- position: relative;
-}
-
.formattedTextBox-outer {
position: relative;
overflow: auto;
@@ -75,6 +70,10 @@
position: absolute;
right: 0;
+ .collectionfreeformview-container {
+ position: relative;
+ }
+
>.formattedTextBox-sidebar-handle {
right: unset;
left: -5;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 241345f3e..d641dc791 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { RichTextUtils } from '../../../new_fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue, returnZero } from '../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
@@ -38,7 +38,7 @@ import { undoBatch, UndoManager } from "../../util/UndoManager";
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentButtonBar } from '../DocumentButtonBar';
import { InkingControl } from "../InkingControl";
import { AudioBox } from './AudioBox';
@@ -69,7 +69,7 @@ const RichTextDocument = makeInterface(richTextSchema, documentSchema);
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
@@ -215,7 +215,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
updateTitle = () => {
if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
- StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
+ StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
const str = this._editorView.state.doc.textContent;
const titlestr = str.substr(0, Math.min(40, str.length));
this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
@@ -262,7 +262,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if (de.complete.docDragData) {
const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
// replace text contents whend dragging with Alt
- if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
+ if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
if (draggedDoc.data instanceof RichTextField) {
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
e.stopPropagation();
@@ -292,7 +292,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
const linkDoc = data.linkDocument!;
const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
- this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id)
+ this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id);
}
getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -723,7 +723,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
}, 0);
dataDoc.title = exportState.title;
- this.Document.customTitle = true;
+ this.rootDoc.customTitle = true;
dataDoc.unchanged = true;
} else {
delete dataDoc[GoogleRef];
@@ -850,7 +850,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
}
- const selectOnLoad = (Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad;
+ const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
if (selectOnLoad && !this.props.dontRegisterView) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
@@ -911,7 +911,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.doLinkOnDeselect();
FormattedTextBox._downEvent = true;
FormattedTextBoxComment.textBox = this;
- if (this.props.onClick && e.button === 0) {
+ if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
e.preventDefault();
}
if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
@@ -1155,7 +1155,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.layoutDoc.limitHeight = undefined;
this.layoutDoc._autoHeight = false;
}
- const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
+ const nh = this.layoutDoc.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
@@ -1205,8 +1205,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
<div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
<div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
style={{
- padding: `${NumCast(this.Document._xMargin, 0)}px ${NumCast(this.Document._yMargin, 0)}px`,
- pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+ padding: `${NumCast(this.layoutDoc._xMargin, 0)}px ${NumCast(this.layoutDoc._yMargin, 0)}px`,
+ pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
}} />
</div>
{!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
@@ -1216,6 +1216,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.sidebarWidth}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
annotationsKey={this.annotationKey}
isAnnotationOverlay={false}
focus={this.props.focus}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index d1a563494..35304033f 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -2,7 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model";
import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocCastAsync } from "../../../new_fields/Doc";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils";
import { DocServer } from "../../DocServer";
@@ -15,6 +15,7 @@ import './FormattedTextBoxComment.scss';
import React = require("react");
import { Docs } from "../../documents/Documents";
import wiki from "wikijs";
+import { DocumentType } from "../../documents/DocumentTypes";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -83,8 +84,12 @@ export class FormattedTextBoxComment {
const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
const textBox = FormattedTextBoxComment.textBox;
if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"));
+ if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab":"onRight");
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ }
} 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 }), "onRight");
}
@@ -100,6 +105,7 @@ export class FormattedTextBoxComment {
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+ ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
}
public static SetState(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
@@ -167,14 +173,18 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltipText.textContent = "target not found...";
(FormattedTextBoxComment.tooltipText as any).href = "";
const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
+ try {
+ ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+ } catch (e) { }
+ docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
if (linkDoc instanceof Doc) {
(FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
FormattedTextBoxComment.linkDoc = linkDoc;
- const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
- try {
- ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
- } catch (e) { }
+ const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+ const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+ if (anchor !== target && anchor && target) {
+ target.scrollY = NumCast(anchor?.y);
+ }
if (target) {
ReactDOM.render(<ContentFittingDocumentView
Document={target}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 00057055f..815a3f7b2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -14,7 +14,7 @@ import { ComputedField } from '../../../new_fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { AudioField, ImageField } from '../../../new_fields/URLField';
import { TraceMobx } from '../../../new_fields/util';
-import { emptyFunction, returnOne, Utils } from '../../../Utils';
+import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Docs } from '../../documents/Documents';
import { Networking } from '../../Network';
@@ -24,7 +24,7 @@ import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenuProps } from '../ContextMenuItem';
-import { DocAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
import FaceRectangles from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
@@ -65,7 +65,7 @@ const uploadIcons = {
};
@observer
-export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
+export class ImageBox extends ViewBoxAnnotatableComponent<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();
@@ -79,10 +79,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
- get fieldKey() {
- return this.props.fieldKey.startsWith("@") ? StrCast(this.props.Document[this.props.fieldKey]) : this.props.fieldKey;
- }
-
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
@@ -146,19 +142,19 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
@undoBatch
rotate = action(() => {
- const nw = NumCast(this.Document[this.fieldKey + "-nativeWidth"]);
- const nh = NumCast(this.Document[this.fieldKey + "-nativeHeight"]);
- const w = this.Document._width;
- const h = this.Document._height;
+ const nw = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+ const nh = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ const w = this.layoutDoc._width;
+ const h = this.layoutDoc._height;
this.dataDoc[this.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.fieldKey + "-rotation"]) + 90) % 360;
this.dataDoc[this.fieldKey + "-nativeWidth"] = nh;
this.dataDoc[this.fieldKey + "-nativeHeight"] = nw;
- this.Document._width = h;
- this.Document._height = w;
+ this.layoutDoc._width = h;
+ this.layoutDoc._height = w;
});
specificContextMenu = (e: React.MouseEvent): void => {
- const field = Cast(this.Document[this.fieldKey], ImageField);
+ const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
@@ -190,9 +186,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
extractFaces = () => {
const converter = (results: any) => {
- const faceDocs = new List<Doc>();
- results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>());
- return faceDocs;
+ return results.map((face: CognitiveServices.Image.Face) => Docs.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
};
this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter);
}
@@ -243,12 +237,13 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
@action onError = (error: any) => {
const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
- if (timeout < 10) {
- // setTimeout(this.retryPath, 500);
- }
- const original = StrCast(this.dataDoc.originalUrl);
- if (error.type === "error" && original) {
- this.dataDoc[this.fieldKey] = new ImageField(original);
+ if (timeout < 5) {
+ setTimeout(this.retryPath, 500);
+ } else {
+ const original = StrCast(this.dataDoc[this.fieldKey + "-originalUrl"]);
+ if (error.type === "error" && original) {
+ this.dataDoc[this.fieldKey] = new ImageField(original);
+ }
}
}
_curSuffix = "_m";
@@ -258,31 +253,29 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
width: NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]),
height: NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"])
};
- const docAspect = this.Document[HeightSym]() / this.Document[WidthSym]();
+ const docAspect = this.layoutDoc[HeightSym]() / this.layoutDoc[WidthSym]();
const cachedAspect = cachedNativeSize.height / cachedNativeSize.width;
if (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(NumCast(this.layoutDoc._width) / NumCast(this.layoutDoc._height) - cachedNativeSize.width / cachedNativeSize.height) > 0.05) {
if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) {
- requestImageSize(imgPath).then((inquiredSize: any) => {
+ requestImageSize(imgPath).then(action((inquiredSize: any) => {
const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180;
const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize;
const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
- 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.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width;
- this.dataDoc[this.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height;
- }
- }), 0);
- }).catch((err: any) => console.log(err));
+ if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
+ this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect;
+ this.dataDoc[this.fieldKey + "-nativeWidth"] = this.layoutDoc._nativeWidth = this.layoutDoc._width;
+ this.dataDoc[this.fieldKey + "-nativeHeight"] = this.layoutDoc._nativeHeight = this.layoutDoc._height;
+ }
+ })).catch(console.log);
} else if (Math.abs(1 - docAspect / cachedAspect) > 0.1) {
- this.Document._width = this.Document[WidthSym]() || cachedNativeSize.width;
- this.Document._height = this.Document[WidthSym]() * cachedAspect;
+ this.layoutDoc._width = this.layoutDoc[WidthSym]() || cachedNativeSize.width;
+ this.layoutDoc._height = this.layoutDoc[WidthSym]() * cachedAspect;
}
- } 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;
+ } else if (this.layoutDoc._nativeWidth !== cachedNativeSize.width || this.layoutDoc._nativeHeight !== cachedNativeSize.height) {
+ !(this.layoutDoc[StrCast(this.layoutDoc.layoutKey)] instanceof Doc) && setTimeout(() => {
+ if (!(this.layoutDoc[StrCast(this.layoutDoc.layoutKey)] instanceof Doc)) {
+ this.layoutDoc._nativeWidth = cachedNativeSize.width;
+ this.layoutDoc._nativeHeight = cachedNativeSize.height;
}
}, 0);
}
@@ -311,8 +304,9 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
audioDown = () => this.recordAudioAnnotation();
considerGooglePhotosLink = () => {
- const remoteUrl = this.Document.googlePhotosUrl;
+ const remoteUrl = this.dataDoc.googlePhotosUrl;
return !remoteUrl ? (null) : (<img
+ style={{ transform: `scale(${this.props.ContentScaling()})`, transformOrigin: "bottom right" }}
id={"google-photos"}
src={"/assets/google_photos.png"}
onClick={() => window.open(remoteUrl)}
@@ -320,7 +314,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
considerGooglePhotosTags = () => {
- const tags = this.Document.googlePhotosTags;
+ const tags = this.dataDoc.googlePhotosTags;
return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
}
@@ -337,13 +331,14 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
return (
<img
id={"upload-icon"}
+ style={{ transform: `scale(${1 / this.props.ContentScaling()})`, transformOrigin: "bottom right" }}
src={`/assets/${this.uploadIcon}`}
onClick={async () => {
const { dataDoc } = this;
const { success, failure, idle, loading } = uploadIcons;
runInAction(() => this.uploadIcon = loading);
const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
- dataDoc.originalUrl = primary;
+ dataDoc[this.props.fieldKey + "-originalUrl"] = primary;
let succeeded = true;
let data: ImageField | undefined;
try {
@@ -370,38 +365,35 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
return { nativeWidth, nativeHeight };
}
+ // this._curSuffix = "";
+ // if (w > 20) {
+ // 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";
@computed get paths() {
- let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
- // this._curSuffix = "";
- // if (w > 20) {
- const alts = DocListCast(this.dataDoc[this.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.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)];
- paths.push(...altpaths);
- return paths;
+ const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
+ const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url.href).filter(url => url); // access the primary layout data of the alternate documents
+ const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
+ return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
}
@computed get content() {
TraceMobx();
- const srcpath = this.paths[NumCast(this.props.Document.curPage, 0)];
+ const srcpath = this.paths[0];
const fadepath = this.paths[Math.min(1, this.paths.length - 1)];
const { nativeWidth, nativeHeight } = this.nativeSize;
const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
- const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
- const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
-
+ const aspect = (rotation % 180) ? nativeHeight / nativeWidth : 1;
+ const shift = (rotation % 180) ? (nativeHeight - nativeWidth) * (1 - 1 / aspect) : 0;
this.resize(srcpath);
- return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget}>
+ return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget}>
<div className="imageBox-fader" >
<img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
src={srcpath}
- style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
+ style={{ transform: `scale(${aspect}) translate(0px, ${shift}px) rotate(${rotation}deg)` }}
width={nativeWidth}
ref={this._imgRef}
onError={this.onError} />
@@ -414,7 +406,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
ref={this._imgRef}
onError={this.onError} /></div>}
</div>
- {!this.props.Document._showAudio ? (null) :
+ {!this.layoutDoc._showAudio ? (null) :
<div className="imageBox-audioBackground"
onPointerDown={this.audioDown}
onPointerEnter={this.onPointerEnter}
@@ -436,15 +428,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
style={{
- transform: `scale(${this.props.ContentScaling()})`,
- width: `${100 / this.props.ContentScaling()}%`,
- height: `${100 / this.props.ContentScaling()}%`,
- pointerEvents: this.props.Document.isBackground ? "none" : undefined,
+ transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
+ width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
+ height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
+ pointerEvents: this.layoutDoc.isBackground ? "none" : undefined,
borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
}} >
<CollectionFreeFormView {...this.props}
+ forceScaling={true}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
annotationsKey={this.annotationKey}
isAnnotationOverlay={true}
focus={this.props.focus}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3d59ea61a..6dc4ae578 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -59,15 +59,18 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
fieldKey: this.props.keyName,
+ rootSelected: returnFalse,
isSelected: returnFalse,
select: emptyFunction,
- dropAction:"alias",
- bringToFront:emptyFunction,
+ dropAction: "alias",
+ bringToFront: emptyFunction,
renderDepth: 1,
active: returnFalse,
whenActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
PanelWidth: this.props.PanelWidth,
PanelHeight: this.props.PanelHeight,
addDocTab: returnFalse,
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/LabelBox.scss
index 7c3825978..ab5b2c6b3 100644
--- a/src/client/views/nodes/ButtonBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -1,4 +1,4 @@
-.buttonBox-outerDiv {
+.labelBox-outerDiv {
width: 100%;
height: 100%;
pointer-events: all;
@@ -7,30 +7,32 @@
flex-direction: column;
}
-.buttonBox-mainButton {
+.labelBox-mainButton {
width: 100%;
height: 100%;
border-radius: inherit;
- text-align: center;
- display: table;
- overflow: hidden;
- text-overflow: ellipsis;
letter-spacing: 2px;
text-transform: uppercase;
+ overflow: hidden;
+ display:flex;
}
-.buttonBox-mainButtonCenter {
- height: 100%;
- display: table-cell;
- vertical-align: middle;
+.labelBox-mainButtonCenter {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline;
+ align-items: center;
+ margin: auto;
}
-.buttonBox-params {
+.labelBox-params {
display: flex;
flex-direction: row;
}
-.buttonBox-missingParam {
+.labelBox-missingParam {
width: 100%;
background: lightgray;
+ border: dimGray solid 1px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
new file mode 100644
index 000000000..391e359cc
--- /dev/null
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -0,0 +1,89 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit } from '@fortawesome/free-regular-svg-icons';
+import { action } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { List } from '../../../new_fields/List';
+import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
+import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { FieldView, FieldViewProps } from './FieldView';
+import './LabelBox.scss';
+
+
+library.add(faEdit as any);
+
+const LabelSchema = createSchema({});
+
+type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
+const LabelDocument = makeInterface(LabelSchema, documentSchema);
+
+@observer
+export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument>(LabelDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
+ private dropDisposer?: DragManager.DragDropDisposer;
+
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ this.dropDisposer?.();
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ }
+ }
+
+ get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; }
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({
+ description: "Clear Script Params", event: () => {
+ const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
+ params?.map(p => this.paramsDoc[p] = undefined);
+ }, icon: "trash"
+ });
+
+ ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ const docDragData = de.complete.docDragData;
+ const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
+ const missingParams = params?.filter(p => !this.paramsDoc[p]);
+ if (docDragData && missingParams?.includes((e.target as any).textContent)) {
+ this.paramsDoc[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
+ d.onDragStart ? docDragData.draggedDocuments[i] : d));
+ e.stopPropagation();
+ }
+ }
+ // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
+ render() {
+ const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
+ const missingParams = params?.filter(p => !this.paramsDoc[p]);
+ params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
+ return (
+ <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
+ style={{ boxShadow: this.layoutDoc.opacity ? StrCast(this.layoutDoc.boxShadow) : "" }}>
+ <div className="labelBox-mainButton" style={{
+ background: StrCast(this.layoutDoc.backgroundColor),
+ color: StrCast(this.layoutDoc.color, "inherit"),
+ fontSize: NumCast(this.layoutDoc.fontSize) || "inherit",
+ letterSpacing: StrCast(this.layoutDoc.letterSpacing),
+ textTransform: StrCast(this.layoutDoc.textTransform) as any
+ }} >
+ <div className="labelBox-mainButtonCenter">
+ {StrCast(this.layoutDoc.text, StrCast(this.layoutDoc.title))}
+ </div>
+ </div>
+ <div className="labelBox-fieldKeyParams" >
+ {!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocuLinkBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index f2c203548..7b6093ebd 100644
--- a/src/client/views/nodes/DocuLinkBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -1,4 +1,4 @@
-.docuLinkBox-cont, .docuLinkBox-cont-small {
+.linkAnchorBox-cont, .linkAnchorBox-cont-small {
cursor: default;
position: absolute;
width: 15;
@@ -7,7 +7,7 @@
pointer-events: all;
user-select: none;
- .docuLinkBox-linkCloser {
+ .linkAnchorBox-linkCloser {
position: absolute;
width: 18;
height: 18;
@@ -23,7 +23,7 @@
}
}
-.docuLinkBox-cont-small {
+.linkAnchorBox-cont-small {
width:5px;
height:5px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 81cf90f92..13ffc6956 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -4,11 +4,11 @@ import { Doc, DocListCast } from "../../../new_fields/Doc";
import { documentSchema } from "../../../new_fields/documentSchemas";
import { makeInterface } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from '../../../Utils';
+import { Utils, setupMoveUpEvents } from '../../../Utils';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
-import { DocComponent } from "../DocComponent";
-import "./DocuLinkBox.scss";
+import { ViewBoxBaseComponent } from "../DocComponent";
+import "./LinkAnchorBox.scss";
import { FieldView, FieldViewProps } from "./FieldView";
import React = require("react");
import { ContextMenuProps } from "../ContextMenuItem";
@@ -16,21 +16,20 @@ import { ContextMenu } from "../ContextMenu";
import { LinkEditor } from "../linking/LinkEditor";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SelectionManager } from "../../util/SelectionManager";
+import { TraceMobx } from "../../../new_fields/util";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-type DocLinkSchema = makeInterface<[typeof documentSchema]>;
-const DocLinkDocument = makeInterface(documentSchema);
+type LinkAnchorSchema = makeInterface<[typeof documentSchema]>;
+const LinkAnchorDocument = makeInterface(documentSchema);
@observer
-export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(DocLinkDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey); }
+export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); }
_doubleTap = false;
_lastTap: number = 0;
_ref = React.createRef<HTMLDivElement>();
- _downX = 0;
- _downY = 0;
_isOpen = false;
_timeout: NodeJS.Timeout | undefined;
@observable _x = 0;
@@ -40,56 +39,42 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
@observable _forceOpen = false;
onPointerDown = (e: React.PointerEvent) => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- (e.button === 0 && !e.ctrlKey) && e.stopPropagation();
+ setupMoveUpEvents(this, e, this.onPointerMove, () => { }, this.onClick);
}
- onPointerMove = action((e: PointerEvent) => {
+ onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
- if (!this._isOpen && cdiv && (Math.abs(e.clientX - this._downX) > 5 || Math.abs(e.clientY - this._downY) > 5)) {
+ if (!this._isOpen && cdiv) {
const bounds = cdiv.getBoundingClientRect();
const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
- const dragdist = Math.sqrt((pt[0] - this._downX) * (pt[0] - this._downX) + (pt[1] - this._downY) * (pt[1] - this._downY));
+ const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
if (separation > 100) {
- //DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack.
- const dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const dragData = new DragManager.DocumentDragData([this.rootDoc]);
dragData.dropAction = "alias";
- dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y"];
- DragManager.StartDocumentDrag([this._ref.current!], dragData, this._downX, this._downY);
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isLinkButton"];
+ DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
+ return true;
} else if (dragdist > separation) {
- this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
- this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
+ this.layoutDoc[this.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
+ this.layoutDoc[this.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
}
}
+ return false;
});
- onPointerUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- if (Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 && (e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) {
+ @action
+ onClick = (e: PointerEvent) => {
+ this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0);
+ this._lastTap = Date.now();
+ if ((e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton)) {
this.props.select(false);
}
- 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();
- }
-
- @action
- onClick = (e: React.MouseEvent) => {
- if (!this._doubleTap) {
+ if (!this._doubleTap && !e.ctrlKey && e.button < 2) {
+ const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack! need a better prop for passing the anchor's container
this._editing = true;
- this.props.ContainingCollectionDoc && this.props.bringToFront(this.props.ContainingCollectionDoc, false);
- const {clientX, clientY} = e;
- if (!this.props.Document.onClick && !this._isOpen) {
+ anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
+ if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
this._timeout = setTimeout(action(() => {
- if (Math.abs(clientX - this._downX) < 3 && Math.abs(clientY - this._downY) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
- DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
- }
+ DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
this._editing = false;
}), 300 - (Date.now() - this._lastTap));
}
@@ -97,15 +82,14 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
this._timeout && clearTimeout(this._timeout);
this._timeout = undefined;
}
- e.stopPropagation();
}
openLinkDocOnRight = (e: React.MouseEvent) => {
- this.props.addDocTab(this.props.Document, "onRight");
+ this.props.addDocTab(this.rootDoc, "onRight");
}
openLinkTargetOnRight = (e: React.MouseEvent) => {
- const alias = Doc.MakeAlias(Cast(this.props.Document[this.props.fieldKey], Doc, null));
- alias.isButton = undefined;
+ const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
+ alias.isLinkButton = undefined;
alias.isBackground = undefined;
alias.layoutKey = "layout";
this.props.addDocTab(alias, "onRight");
@@ -126,24 +110,25 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
}
render() {
- const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0;
- const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0;
- const c = StrCast(this.props.Document.backgroundColor, "lightblue");
- const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+ TraceMobx();
+ const x = this.props.PanelWidth() > 1 ? NumCast(this.layoutDoc[this.fieldKey + "_x"], 100) : 0;
+ const y = this.props.PanelWidth() > 1 ? NumCast(this.layoutDoc[this.fieldKey + "_y"], 100) : 0;
+ const c = StrCast(this.layoutDoc.backgroundColor, "lightblue");
+ const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
- const timecode = this.props.Document[anchor + "Timecode"];
- const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
+ const timecode = this.dataDoc[anchor + "_timecode"];
+ const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title) + (timecode !== undefined ? ":" + timecode : "");
const flyout = (
- <div className="docuLinkBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
- <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
- {!this._forceOpen ? (null) : <div className="docuLinkBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
+ <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
+ <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => { })} />
+ {!this._forceOpen ? (null) : <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
<FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
</div>}
</div>
);
const small = this.props.PanelWidth() <= 1;
- return <div className={`docuLinkBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
+ return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} title={targetTitle} onContextMenu={this.specificContextMenu}
ref={this._ref} style={{
background: c,
left: !small ? `calc(${x}% - 7.5px)` : undefined,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 0e327e130..af4bf420f 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -4,7 +4,7 @@ import { documentSchema } from "../../../new_fields/documentSchemas";
import { makeInterface, listSpec } from "../../../new_fields/Schema";
import { returnFalse, returnZero } from "../../../Utils";
import { CollectionTreeView } from "../collections/CollectionTreeView";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
import { FieldView, FieldViewProps } from './FieldView';
import "./LinkBox.scss";
import { Cast } from "../../../new_fields/Types";
@@ -13,7 +13,7 @@ type LinkDocument = makeInterface<[typeof documentSchema]>;
const LinkDocument = makeInterface(documentSchema);
@observer
-export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument>(LinkDocument) {
+export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(LinkDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
render() {
return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`}
@@ -23,6 +23,8 @@ export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument
<CollectionTreeView {...this.props}
ChromeHeight={returnZero}
overrideDocuments={[this.dataDoc]}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)}
annotationsKey={""}
CollectionView={undefined}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f8c008a2d..6db36e43c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -9,25 +9,24 @@ import { ScriptField } from '../../../new_fields/ScriptField';
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { PdfField, URLField } from "../../../new_fields/URLField";
import { Utils } from '../../../Utils';
-import { KeyCodes } from '../../northstar/utils/KeyCodes';
import { undoBatch } from '../../util/UndoManager';
import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { PDFViewer } from "../pdf/PDFViewer";
import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
+import { KeyCodes } from '../../util/KeyCodes';
import "./PDFBox.scss";
import React = require("react");
import { documentSchema } from '../../../new_fields/documentSchemas';
-import { url } from 'inspector';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@observer
-export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
private _keyValue: string = "";
private _valueValue: string = "";
@@ -249,7 +248,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
_pdfjsRequested = false;
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
- if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
+ if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document.scrollY !== undefined) this._everActive = true;
if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
if (pdfUrl instanceof PdfField && this._pdf) {
return this.renderPdfView;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index a39c337ca..e428e16da 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -4,10 +4,11 @@ import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faH
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast, DocCastAsync } from "../../../new_fields/Doc";
import { InkTool } from "../../../new_fields/InkField";
-import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
import { returnFalse } from "../../../Utils";
+import { documentSchema } from "../../../new_fields/documentSchemas";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -15,6 +16,8 @@ import { CollectionView, CollectionViewType } from "../collections/CollectionVie
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./PresBox.scss";
+import { ViewBoxBaseComponent } from "../DocComponent";
+import { makeInterface } from "../../../new_fields/Schema";
library.add(faArrowLeft);
library.add(faArrowRight);
@@ -26,37 +29,41 @@ library.add(faTimes);
library.add(faMinus);
library.add(faEdit);
+type PresBoxSchema = makeInterface<[typeof documentSchema]>;
+const PresBoxDocument = makeInterface(documentSchema);
+
@observer
-export class PresBox extends React.Component<FieldViewProps> {
+export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
_childReaction: IReactionDisposer | undefined;
@observable _isChildActive = false;
componentDidMount() {
- this.props.Document._forceRenderEngine = "timeline";
- this.props.Document._replacedChrome = "replaced";
+ this.layoutDoc._forceRenderEngine = "timeline";
+ this.layoutDoc._replacedChrome = "replaced";
this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
}
componentWillUnmount() {
this._childReaction?.();
}
- @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); }
- @computed get currentIndex() { return NumCast(this.props.Document._itemIndex); }
+ @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
+ @computed get currentIndex() { return NumCast(this.layoutDoc._itemIndex); }
- updateCurrentPresentation = action(() => Doc.UserDoc().curPresentation = this.props.Document);
+ updateCurrentPresentation = action(() => Doc.UserDoc().curPresentation = this.rootDoc);
next = () => {
this.updateCurrentPresentation();
if (this.childDocs[this.currentIndex + 1] !== undefined) {
let nextSelected = this.currentIndex + 1;
+ this.gotoDocument(nextSelected, this.currentIndex);
- for (; nextSelected < this.childDocs.length - 1; nextSelected++) {
- if (!this.childDocs[nextSelected + 1].groupButton) {
+ for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) {
+ if (!this.childDocs[nextSelected].groupButton) {
break;
+ } else {
+ this.gotoDocument(nextSelected, this.currentIndex);
}
}
-
- this.gotoDocument(nextSelected, this.currentIndex);
}
}
back = () => {
@@ -72,20 +79,13 @@ export class PresBox extends React.Component<FieldViewProps> {
}
prevSelected = Math.max(0, prevSelected - 1);
- if (this.currentIndex > 0 && didZoom) {
- const prevScale = NumCast(this.childDocs[prevSelected].viewScale);
- const curScale = DocumentManager.Instance.getScaleOfDocView(docAtCurrent);
- if (prevScale && prevScale !== curScale) {
- DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
- }
- }
this.gotoDocument(prevSelected, this.currentIndex);
}
}
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)
+ active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
+ (this.layoutDoc.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
@@ -137,11 +137,10 @@ export class PresBox extends React.Component<FieldViewProps> {
*/
navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
this.updateCurrentPresentation();
- const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
let docToJump = curDoc;
let willZoom = false;
- const presDocs = DocListCast(this.props.Document[this.props.fieldKey]);
+ const presDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
let nextSelected = presDocs.indexOf(curDoc);
const currentDocGroups: Doc[] = [];
for (; nextSelected < presDocs.length - 1; nextSelected++) {
@@ -163,45 +162,28 @@ 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.context, Doc);
+ const aliasOf = await DocCastAsync(docToJump.aliasOf);
+ const srcContext = aliasOf && await DocCastAsync(aliasOf.context);
if (docToJump === curDoc) {
//checking if curDoc has navigation open
- const target = await Cast(curDoc.presentationTargetDoc, Doc);
+ const target = await DocCastAsync(curDoc.presentationTargetDoc);
if (curDoc.navButton && target) {
DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext);
} else if (curDoc.zoomButton && 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, undefined, srcContext);
- curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target);
-
- //saving the scale user was on before zooming in
- if (curScale !== 1) {
- fromDoc.viewScale = curScale;
- }
-
}
- return;
- }
- const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
-
- //awaiting jump so that new scale can be found, since jumping is async
- 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
- if (curScale !== 1) {
- fromDoc.viewScale = curScale;
+ } else {
+ //awaiting jump so that new scale can be found, since jumping is async
+ const presTargetDoc = await DocCastAsync(docToJump.presentationTargetDoc);
+ presTargetDoc && await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext);
}
-
}
@undoBatch
public removeDocument = (doc: Doc) => {
- return Doc.RemoveDocFromList(this.props.Document, this.props.fieldKey, doc);
+ return Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
}
//The function that is called when a document is clicked or reached through next or back.
@@ -210,10 +192,10 @@ export class PresBox extends React.Component<FieldViewProps> {
this.updateCurrentPresentation();
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
- this.props.Document._itemIndex = index;
+ this.layoutDoc._itemIndex = index;
- if (!this.props.Document.presStatus) {
- this.props.Document.presStatus = true;
+ if (!this.layoutDoc.presStatus) {
+ this.layoutDoc.presStatus = true;
this.startPresentation(index);
}
@@ -226,10 +208,10 @@ export class PresBox extends React.Component<FieldViewProps> {
//The function that starts or resets presentaton functionally, depending on status flag.
startOrResetPres = () => {
this.updateCurrentPresentation();
- if (this.props.Document.presStatus) {
+ if (this.layoutDoc.presStatus) {
this.resetPresentation();
} else {
- this.props.Document.presStatus = true;
+ this.layoutDoc.presStatus = true;
this.startPresentation(0);
this.gotoDocument(0, this.currentIndex);
}
@@ -238,7 +220,7 @@ export class PresBox extends React.Component<FieldViewProps> {
addDocument = (doc: Doc) => {
const newPinDoc = Doc.MakeAlias(doc);
newPinDoc.presentationTargetDoc = doc;
- return Doc.AddDocToList(this.props.Document, this.props.fieldKey, newPinDoc);
+ return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc);
}
@@ -246,10 +228,9 @@ export class PresBox extends React.Component<FieldViewProps> {
//stops the presentaton.
resetPresentation = () => {
this.updateCurrentPresentation();
- this.childDocs.forEach(doc => doc.opacity = doc.viewScale = 1);
- this.props.Document._itemIndex = 0;
- this.props.Document.presStatus = false;
- this.childDocs.length && DocumentManager.Instance.zoomIntoScale(this.childDocs[0], 1);
+ this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1);
+ this.layoutDoc._itemIndex = 0;
+ this.layoutDoc.presStatus = false;
}
//The function that starts the presentation, also checking if actions should be applied
@@ -258,47 +239,42 @@ export class PresBox extends React.Component<FieldViewProps> {
this.updateCurrentPresentation();
this.childDocs.map(doc => {
if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) {
- doc.opacity = 0;
+ (doc.presentationTargetDoc as Doc).opacity = 0;
}
if (doc.hideAfterButton && this.childDocs.indexOf(doc) < startIndex) {
- doc.opacity = 0;
+ (doc.presentationTargetDoc as Doc).opacity = 0;
}
if (doc.fadeButton && this.childDocs.indexOf(doc) < startIndex) {
- doc.opacity = 0.5;
+ (doc.presentationTargetDoc as Doc).opacity = 0.5;
}
});
}
- updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: number) => {
- if (BoolCast(this.props.Document.inOverlay) !== (mode === CollectionViewType.Invalid)) {
- if (this.props.Document.inOverlay) {
- Doc.RemoveDocFromList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document);
- CollectionDockingView.AddRightSplit(this.props.Document);
- this.props.Document.inOverlay = false;
+ updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: CollectionViewType) => {
+ if (BoolCast(this.layoutDoc.inOverlay) !== (mode === CollectionViewType.Invalid)) {
+ if (this.layoutDoc.inOverlay) {
+ Doc.RemoveDocFromList((Doc.UserDoc().overlays as Doc), undefined, this.rootDoc);
+ CollectionDockingView.AddRightSplit(this.rootDoc);
+ this.layoutDoc.inOverlay = false;
} else {
- this.props.Document.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25;
- this.props.Document.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25;
- this.props.addDocTab?.(this.props.Document, "close");
- Doc.AddDocToList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document);
+ this.layoutDoc.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25;
+ this.layoutDoc.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25;
+ this.props.addDocTab?.(this.rootDoc, "close");
+ Doc.AddDocToList((Doc.UserDoc().overlays as Doc), undefined, this.rootDoc);
}
}
}));
- /**
- * Initially every document starts with a viewScale 1, which means
- * that they will be displayed in a canvas with scale 1.
- */
- initializeScaleViews = (docList: Doc[], viewtype: number) => {
+ initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => {
const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
docList.forEach(doc => {
- doc.presBox = this.props.Document; // give contained documents a reference to the presentation
+ doc.presBox = this.rootDoc; // give contained documents a reference to the presentation
doc.collapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din
- !NumCast(doc.viewScale) && (doc.viewScale = 1);
});
}
selectElement = (doc: Doc) => {
- this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.props.Document._itemIndex));
+ this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.layoutDoc._itemIndex));
}
getTransform = () => {
@@ -311,17 +287,17 @@ export class PresBox extends React.Component<FieldViewProps> {
@undoBatch
viewChanged = action((e: React.ChangeEvent) => {
//@ts-ignore
- this.props.Document._viewType = Number(e.target.selectedOptions[0].value);
- this.props.Document._viewType === CollectionViewType.Stacking && (this.props.Document._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- this.updateMinimize(e, Number(this.props.Document._viewType));
+ this.layoutDoc._viewType = e.target.selectedOptions[0].value;
+ this.layoutDoc._viewType === CollectionViewType.Stacking && (this.layoutDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
+ this.updateMinimize(e, StrCast(this.layoutDoc._viewType));
});
- childLayoutTemplate = () => this.props.Document._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined;
+ childLayoutTemplate = () => this.layoutDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined;
render() {
- const mode = NumCast(this.props.Document._viewType, CollectionViewType.Invalid);
- this.initializeScaleViews(this.childDocs, mode);
- return <div className="presBox-cont" style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} >
- <div className="presBox-buttons" style={{ display: this.props.Document._chromeStatus === "disabled" ? "none" : undefined }}>
+ const mode = StrCast(this.layoutDoc._viewType) as CollectionViewType;
+ this.initializeViewAliases(this.childDocs, mode);
+ return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.layoutDoc.inOverlay ? "all" : "none" }} >
+ <div className="presBox-buttons" style={{ display: this.layoutDoc._chromeStatus === "disabled" ? "none" : undefined }}>
<select className="collectionViewBaseChrome-viewPicker"
onPointerDown={e => e.stopPropagation()}
onChange={this.viewChanged}
@@ -332,15 +308,21 @@ export class PresBox extends React.Component<FieldViewProps> {
<option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option>
</select>
<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}>
- <FontAwesomeIcon icon={this.props.Document.presStatus ? "stop" : "play"} />
+ <button className="presBox-button" title={"Reset Presentation" + this.layoutDoc.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}>
+ <FontAwesomeIcon icon={this.layoutDoc.presStatus ? "stop" : "play"} />
</button>
<button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
</div>
<div className="presBox-listCont" >
{mode !== CollectionViewType.Invalid ?
- <CollectionView {...this.props} PanelHeight={this.panelHeight} moveDocument={returnFalse} childLayoutTemplate={this.childLayoutTemplate}
- addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+ <CollectionView {...this.props}
+ PanelHeight={this.panelHeight}
+ moveDocument={returnFalse}
+ childLayoutTemplate={this.childLayoutTemplate}
+ addDocument={this.addDocument}
+ removeDocument={returnFalse}
+ focus={this.selectElement}
+ ScreenToLocalTransform={this.getTransform} />
: (null)
}
</div>
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 04f815e91..7929c6c33 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -6,7 +6,7 @@ import { Id } from '../../../new_fields/FieldSymbols';
import { makeInterface } from "../../../new_fields/Schema";
import { StrCast } from "../../../new_fields/Types";
import { SelectionManager } from "../../util/SelectionManager";
-import { DocAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { SearchBox } from "../search/SearchBox";
import { FieldView, FieldViewProps } from './FieldView';
import "./QueryBox.scss";
@@ -15,7 +15,7 @@ type QueryDocument = makeInterface<[typeof documentSchema]>;
const QueryDocument = makeInterface(documentSchema);
@observer
-export class QueryBox extends DocAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
+export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
_docListChangedReaction: IReactionDisposer | undefined;
componentDidMount() {
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7c58a5148..11b24b059 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -7,15 +7,14 @@ import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
import { makeInterface } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
@@ -27,7 +26,7 @@ const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
library.add(faVideo);
@observer
-export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
+export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
private _reactionDisposer?: IReactionDisposer;
private _videoRef: HTMLVideoElement | null = null;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
@@ -38,22 +37,21 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
videoLoad = () => {
const aspect = this.player!.videoWidth / this.player!.videoHeight;
- const nativeWidth = (this.Document._nativeWidth || 0);
- const nativeHeight = (this.Document._nativeHeight || 0);
+ const nativeWidth = (this.layoutDoc._nativeWidth || 0);
+ const nativeHeight = (this.layoutDoc._nativeHeight || 0);
if (!nativeWidth || !nativeHeight) {
- if (!this.Document._nativeWidth) this.Document._nativeWidth = 400;
- this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect;
- this.Document._height = (this.Document._width || 0) / aspect;
+ if (!this.layoutDoc._nativeWidth) this.layoutDoc._nativeWidth = 400;
+ this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / aspect;
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
}
- if (!this.Document.duration) this.Document.duration = this.player!.duration;
}
@action public Snapshot() {
- const width = this.Document._width || 0;
- const height = this.Document._height || 0;
+ const width = NumCast(this.layoutDoc._width);
+ const height = NumCast(this.layoutDoc._height);
const canvas = document.createElement('canvas');
canvas.width = 640;
- canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1);
+ canvas.height = 640 * NumCast(this.layoutDoc._nativeHeight) / NumCast(this.layoutDoc._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);
@@ -71,7 +69,7 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
setTimeout(() => {
if (returnedFilename) {
const imageSummary = Docs.Create.ImageDocument(Utils.prepend(returnedFilename), {
- x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+ x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y),
_width: 150, _height: height / width * 150, title: "--screenshot--"
});
this.props.addDocument?.(imageSummary);
@@ -111,7 +109,7 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
}
@observable _screenCapture = false;
specificContextMenu = (e: React.MouseEvent): void => {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this.fieldKey], VideoField);
if (field) {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
@@ -170,16 +168,18 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationKey}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ annotationsKey={""}
focus={this.props.focus}
isSelected={this.props.isSelected}
isAnnotationOverlay={true}
select={emptyFunction}
- active={this.annotationsActive}
+ active={returnFalse}
ContentScaling={returnOne}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
+ whenActiveChanged={emptyFunction}
+ removeDocument={returnFalse}
+ moveDocument={returnFalse}
addDocument={returnFalse}
CollectionView={undefined}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
@@ -188,7 +188,7 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
{this.contentFunc}
</CollectionFreeFormView>
</div>
- {this.active() ? this.uIButtons : (null)}
+ {this.props.isSelected() ? this.uIButtons : (null)}
</div >);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss
new file mode 100644
index 000000000..678a1a22d
--- /dev/null
+++ b/src/client/views/nodes/ScriptingBox.scss
@@ -0,0 +1,36 @@
+.scriptingBox-outerDiv {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ pointer-events: all;
+ background-color: rgb(241, 239, 235);
+ padding: 10px;
+ .scriptingBox-inputDiv {
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 30px);
+ .scriptingBox-errorMessage {
+ overflow: auto;
+ }
+ .scripting-params {
+ background: "beige";
+ }
+ .scriptingBox-textArea {
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ resize: none;
+ padding: 7px;
+ }
+ }
+
+ .scriptingBox-toolbar {
+ width: 100%;
+ height: 30px;
+ .scriptingBox-button {
+ width: 50%
+ }
+ }
+}
+
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
new file mode 100644
index 000000000..c607d6614
--- /dev/null
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -0,0 +1,98 @@
+import { action, observable, computed } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { StrCast, ScriptCast, Cast } from "../../../new_fields/Types";
+import { InteractionUtils } from "../../util/InteractionUtils";
+import { CompileScript, isCompileError, ScriptParam } from "../../util/Scripting";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
+import { EditableView } from "../EditableView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import "./ScriptingBox.scss";
+import { OverlayView } from "../OverlayView";
+import { DocumentIconContainer } from "./DocumentIcon";
+import { List } from "../../../new_fields/List";
+
+const ScriptingSchema = createSchema({});
+type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>;
+const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
+
+@observer
+export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
+ protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
+ public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
+
+ _overlayDisposer?: () => void;
+
+ @observable private _errorMessage: string = "";
+
+ @computed get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-rawScript"], StrCast(this.layoutDoc[this.props.fieldKey + "-rawScript"])); }
+ @computed get compileParams() { return Cast(this.dataDoc[this.props.fieldKey + "-params"], listSpec("string"), Cast(this.layoutDoc[this.props.fieldKey + "-params"], listSpec("string"), [])); }
+ set rawScript(value) { this.dataDoc[this.props.fieldKey + "-rawScript"] = value; }
+ set compileParams(value) { this.dataDoc[this.props.fieldKey + "-params"] = value; }
+
+ @action
+ componentDidMount() {
+ this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript || this.rawScript;
+ }
+
+ componentWillUnmount() { this._overlayDisposer?.(); }
+
+ @action
+ onCompile = () => {
+ const params = this.compileParams.reduce((o: ScriptParam, p: string) => { o[p] = "any"; return o; }, {} as ScriptParam);
+ const result = CompileScript(this.rawScript, {
+ editable: true,
+ transformer: DocumentIconContainer.getTransformer(),
+ params,
+ typecheck: false
+ });
+ this._errorMessage = isCompileError(result) ? result.errors.map(e => e.messageText).join("\n") : "";
+ return this.dataDoc[this.props.fieldKey] = result.compiled ? new ScriptField(result) : undefined;
+ }
+
+ @action
+ onRun = () => {
+ this.onCompile()?.script.run({}, err => this._errorMessage = err.map((e: any) => e.messageText).join("\n"));
+ }
+
+ onFocus = () => {
+ this._overlayDisposer?.();
+ this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+ }
+
+ render() {
+ const params = <EditableView
+ contents={this.compileParams.join(" ")}
+ display={"block"}
+ maxHeight={72}
+ height={35}
+ fontSize={28}
+ GetValue={() => ""}
+ SetValue={value => { this.compileParams = new List<string>(value.split(" ").filter(s => s !== " ")); return true; }}
+ />;
+ return (
+ <div className="scriptingBox-outerDiv"
+ onWheel={e => this.props.isSelected(true) && e.stopPropagation()}>
+ <div className="scriptingBox-inputDiv"
+ onPointerDown={e => this.props.isSelected(true) && e.stopPropagation()} >
+ <textarea className="scriptingBox-textarea"
+ placeholder="write your script here"
+ onChange={e => this.rawScript = e.target.value}
+ value={this.rawScript}
+ onFocus={this.onFocus}
+ onBlur={e => this._overlayDisposer?.()} />
+ <div className="scriptingBox-errorMessage" style={{ background: this._errorMessage ? "red" : "" }}>{this._errorMessage}</div>
+ <div className="scriptingBox-params" >{params}</div>
+ </div>
+ {this.rootDoc.layout === "layout" ? <div></div> : (null)}
+ <div className="scriptingBox-toolbar">
+ <button className="scriptingBox-button" onPointerDown={e => { this.onCompile(); e.stopPropagation(); }}>Compile</button>
+ <button className="scriptingBox-button" onPointerDown={e => { this.onRun(); e.stopPropagation(); }}>Run</button>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 844d95d11..746ea0b64 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -1,22 +1,20 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit } from '@fortawesome/free-regular-svg-icons';
-import { computed, runInAction } from 'mobx';
+import { runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Handles, Rail, Slider, Tracks, Ticks } from 'react-compound-slider';
-import { Doc } from '../../../new_fields/Doc';
+import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { documentSchema } from '../../../new_fields/documentSchemas';
-import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, FieldValue, StrCast, NumCast, Cast } from '../../../new_fields/Types';
-import { DragManager } from '../../util/DragManager';
+import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
-import './SliderBox.scss';
-import { Handle, TooltipRail, Track, Tick } from './SliderBox-components';
-import { FieldView, FieldViewProps } from './FieldView';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { ScriptBox } from '../ScriptBox';
+import { FieldView, FieldViewProps } from './FieldView';
+import { Handle, Tick, TooltipRail, Track } from './SliderBox-components';
+import './SliderBox.scss';
library.add(faEdit as any);
@@ -32,36 +30,33 @@ type SliderDocument = makeInterface<[typeof SliderSchema, typeof documentSchema]
const SliderDocument = makeInterface(SliderSchema, documentSchema);
@observer
-export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(SliderDocument) {
+export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocument>(SliderDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); }
- private dropDisposer?: DragManager.DragDropDisposer;
-
- @computed get dataDoc() {
- return this.props.DataDoc &&
- (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
- this.props.DataDoc.layout === this.Document) ? this.props.DataDoc : Doc.GetProto(this.Document);
- }
+ get minThumbKey() { return this.fieldKey + "-minThumb"; }
+ get maxThumbKey() { return this.fieldKey + "-maxThumb"; }
+ get minKey() { return this.fieldKey + "-min"; }
+ get maxKey() { return this.fieldKey + "-max"; }
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) });
ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" });
}
onChange = (values: readonly number[]) => runInAction(() => {
- this.Document._sliderMinThumb = values[0];
- this.Document._sliderMaxThumb = values[1];
- Cast(this.Document.onThumbChanged, ScriptField, null)?.script.run({ range: values, this: this.props.Document });
+ this.dataDoc[this.minThumbKey] = values[0];
+ this.dataDoc[this.maxThumbKey] = values[1];
+ Cast(this.layoutDoc.onThumbChanged, ScriptField, null)?.script.run({ self: this.rootDoc, range: values, this: this.layoutDoc });
})
render() {
- const domain = [NumCast(this.props.Document._sliderMin), NumCast(this.props.Document._sliderMax)];
- const defaultValues = [NumCast(this.props.Document._sliderMinThumb), NumCast(this.props.Document._sliderMaxThumb)];
- return (
+ const domain = [NumCast(this.layoutDoc[this.minKey]), NumCast(this.layoutDoc[this.maxKey])];
+ const defaultValues = [NumCast(this.dataDoc[this.minThumbKey]), NumCast(this.dataDoc[this.maxThumbKey])];
+ return domain[1] <= domain[0] ? (null) : (
<div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()}
- style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
+ style={{ boxShadow: this.layoutDoc.opacity === 0 ? undefined : StrCast(this.layoutDoc.boxShadow, "") }}>
<div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
- background: this.Document.backgroundColor, color: this.Document.color || "black",
- fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || ""
+ background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
+ fontSize: NumCast(this.layoutDoc.fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
}} >
<Slider
mode={2}
@@ -77,7 +72,7 @@ export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(Slid
{({ handles, activeHandleID, getHandleProps }) => (
<div className="slider-handles">
{handles.map((handle, i) => {
- const value = i === 0 ? this.Document._sliderMinThumb : this.Document._sliderMaxThumb;
+ const value = i === 0 ? defaultValues[0] : defaultValues[1];
return (
<div title={String(value)}>
<Handle
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 24b66d8f7..588068334 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -9,14 +9,14 @@ import { Doc } from "../../../new_fields/Doc";
import { InkTool } from "../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { Cast, StrCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
-import { Utils, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
@@ -33,7 +33,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
library.add(faVideo);
@observer
-export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
static _youtubeIframeCounter: number = 0;
private _reactionDisposer?: IReactionDisposer;
private _youtubeReactionDisposer?: IReactionDisposer;
@@ -55,14 +55,10 @@ 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);
- 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.duration) this.Document.duration = this.player!.duration;
+ this.layoutDoc._nativeWidth = this.player!.videoWidth;
+ this.layoutDoc._nativeHeight = (this.layoutDoc._nativeWidth || 0) / aspect;
+ this.layoutDoc._height = (this.layoutDoc._width || 0) / aspect;
+ this.dataDoc[this.fieldKey + "-" + "duration"] = this.player!.duration;
}
@action public Play = (update: boolean = true) => {
@@ -90,7 +86,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
@action public FullScreen() {
this._fullScreen = true;
this.player && this.player.requestFullscreen();
- this._youtubePlayer && this.props.addDocTab(this.props.Document, "inTab");
+ this._youtubePlayer && this.props.addDocTab(this.rootDoc, "inTab");
}
choosePath(url: string) {
@@ -101,11 +97,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.layoutDoc._width || 0);
+ const height = (this.layoutDoc._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.layoutDoc._nativeHeight || 0) / (this.layoutDoc._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);
@@ -116,25 +112,28 @@ 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()
+ x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1),
+ _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString()
});
- b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
+ b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.layoutDoc.currentTimecode || 0)}`);
} else {
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_")));
+ const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc.currentTimecode || 0).toString().replace(/\./, "_")));
VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
if (returnedFilename) {
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-"
+ _nativeWidth: this.layoutDoc._nativeWidth, _nativeHeight: this.layoutDoc._nativeHeight,
+ x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0),
+ _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc.currentTimecode || 0) + " image-"
});
- imageSummary.isButton = true;
+ Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth;
+ Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight;
+ imageSummary.isLinkButton = true;
this.props.addDocument && this.props.addDocument(imageSummary);
- DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "video snapshot");
+ DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot");
}
});
}
@@ -142,8 +141,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
@action
updateTimecode = () => {
- this.player && (this.Document.currentTimecode = this.player.currentTime);
- this._youtubePlayer && (this.Document.currentTimecode = this._youtubePlayer.getCurrentTime());
+ this.player && (this.layoutDoc.currentTimecode = this.player.currentTime);
+ this._youtubePlayer && (this.layoutDoc.currentTimecode = this._youtubePlayer.getCurrentTime());
}
componentDidMount() {
@@ -151,12 +150,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.layoutDoc._nativeWidth || 0);
+ const nativeHeight = (this.layoutDoc._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.layoutDoc._nativeWidth) this.layoutDoc._nativeWidth = 600;
+ this.layoutDoc._nativeHeight = (this.layoutDoc._nativeWidth || 0) / youtubeaspect;
+ this.layoutDoc._height = (this.layoutDoc._width || 0) / youtubeaspect;
}
}
}
@@ -174,7 +173,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
this._videoRef!.ontimeupdate = this.updateTimecode;
vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
this._reactionDisposer && this._reactionDisposer();
- this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
+ this._reactionDisposer = reaction(() => (this.layoutDoc.currentTimecode || 0),
time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
}
}
@@ -215,7 +214,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
@computed get content() {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this.fieldKey], VideoField);
const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
@@ -259,7 +258,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
const onYoutubePlayerReady = (event: any) => {
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
- this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0));
+ this._reactionDisposer = reaction(() => this.layoutDoc.currentTimecode, () => !this._playing && this.Seek((this.layoutDoc.currentTimecode || 0)));
this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
const interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -274,7 +273,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
private get uIButtons() {
- const curTime = (this.Document.currentTimecode || 0);
+ const curTime = (this.layoutDoc.currentTimecode || 0);
return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} >
<span>{"" + Math.round(curTime)}</span>
<span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
@@ -316,7 +315,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
onResetMove = (e: PointerEvent) => {
this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
- this.Seek(Math.max(0, (this.Document.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
+ this.Seek(Math.max(0, (this.layoutDoc.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
e.stopImmediatePropagation();
}
@@ -324,22 +323,22 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
onResetUp = (e: PointerEvent) => {
document.removeEventListener("pointermove", this.onResetMove, true);
document.removeEventListener("pointerup", this.onResetUp, true);
- this._isResetClick < 10 && (this.Document.currentTimecode = 0);
+ this._isResetClick < 10 && (this.layoutDoc.currentTimecode = 0);
}
@computed get youtubeContent() {
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
- const start = untracked(() => Math.round(this.Document.currentTimecode || 0));
+ const start = untracked(() => Math.round((this.layoutDoc.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.layoutDoc._nativeWidth || 640)} height={(this.layoutDoc._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}`} />;
}
@action.bound
addDocumentWithTimestamp(doc: Doc): boolean {
- const curTime = (this.Document.currentTimecode || -1);
+ const curTime = (this.layoutDoc.currentTimecode || -1);
curTime !== -1 && (doc.displayTimecode = curTime);
return this.addDocument(doc);
}
@@ -352,6 +351,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
annotationsKey={this.annotationKey}
focus={this.props.focus}
isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 838fbefb1..55ad7eb0f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -9,12 +9,12 @@ import { InkTool } from "../../../new_fields/InkField";
import { makeInterface } from "../../../new_fields/Schema";
import { Cast, NumCast } from "../../../new_fields/Types";
import { WebField } from "../../../new_fields/URLField";
-import { Utils, returnOne, emptyFunction } from "../../../Utils";
+import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { SelectionManager } from "../../util/SelectionManager";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
@@ -33,7 +33,7 @@ type WebDocument = makeInterface<[typeof documentSchema]>;
const WebDocument = makeInterface(documentSchema);
@observer
-export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
+export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
@observable private collapsed: boolean = true;
@@ -335,6 +335,8 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
annotationsKey={this.annotationKey}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
focus={this.props.focus}
isSelected={this.props.isSelected}
isAnnotationOverlay={true}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index d23c81065..71b19f3a6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -97,9 +97,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
else if (e.button === 0) {
const annoGroup = await Cast(this.props.document.group, Doc);
if (annoGroup) {
- DocumentManager.Instance.FollowLink(undefined, annoGroup,
- (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"),
- false, false, undefined);
+ DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
e.stopPropagation();
}
}
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 5913c5a82..2a6eff7ff 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -64,7 +64,7 @@ export default class PDFMenu extends AntimodeMenu {
togglePin = action((e: React.MouseEvent) => {
this.Pinned = !this.Pinned;
!this.Pinned && (this.Highlighting = false);
- })
+ });
@action
highlightClicked = (e: React.MouseEvent) => {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c032f019d..c49e6512a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List";
import { makeInterface, createSchema } from "../../../new_fields/Schema";
import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils";
+import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -23,7 +23,7 @@ import Annotation from "./Annotation";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { SelectionManager } from "../../util/SelectionManager";
import { undoBatch } from "../../util/UndoManager";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentType } from "../../documents/DocumentTypes";
import { documentSchema } from "../../../new_fields/documentSchemas";
import { DocumentDecorations } from "../DocumentDecorations";
@@ -59,7 +59,7 @@ interface IViewerProps {
PanelHeight: () => number;
ContentScaling: () => number;
select: (isCtrlPressed: boolean) => void;
- rootSelected: () => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
startupLive: boolean;
renderDepth: number;
focus: (doc: Doc) => void;
@@ -79,7 +79,7 @@ interface IViewerProps {
* Handles rendering and virtualization of the pdf
*/
@observer
-export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
+export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
static _annotationStyle: any = addStyleSheet();
@observable private _pageSizes: { width: number, height: number }[] = [];
@observable private _annotations: Doc[] = [];
@@ -164,7 +164,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
}
componentWillUnmount = () => {
- this._reactionDisposer && this._reactionDisposer();
+ this._reactionDisposer?.();
this._scrollTopReactionDisposer?.();
this._annotationReactionDisposer?.();
this._filterReactionDisposer?.();
@@ -282,7 +282,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
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;
+ annoDoc.isLinkButton = true;
annoDocs.push(annoDoc);
anno.remove();
mainAnnoDoc = annoDoc;
@@ -585,7 +585,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
dragComplete: e => {
if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
- if (link) link.maximizeLocation = "onRight";
+ if (link) link.followLinkLocation = "onRight";
}
}
});
@@ -648,6 +648,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
setPreviewCursor={this.setPreviewCursor}
PanelHeight={this.panelWidth}
PanelWidth={this.panelHeight}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
dropAction={"alias"}
VisibleHeight={this.visibleHeight}
focus={this.props.focus}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 7ad3474e8..dd0cbf929 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -12,7 +12,7 @@ import { Cast, NumCast } from "../../../new_fields/Types";
import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
-import { DocExtendableComponent } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
@@ -44,14 +44,14 @@ const PresDocument = makeInterface(presSchema, documentSchema);
* It involves some functionality for its buttons and options.
*/
@observer
-export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresDocument>(PresDocument) {
+export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
_heightDisposer: IReactionDisposer | undefined;
@computed get indexInPres() { return NumCast(this.presElementDoc?.presentationIndex); }
@computed get presBoxDoc() { return Cast(this.presElementDoc?.presBox, Doc) as Doc; }
- @computed get presElementDoc() { return this.props.Document.rootDocument as Doc; }
- @computed get presLayoutDoc() { return this.props.Document; }
+ @computed get presElementDoc() { return this.rootDoc; }
+ @computed get presLayoutDoc() { return this.layoutDoc; }
@computed get targetDoc() { return this.presElementDoc?.presentationTargetDoc as Doc; }
@computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); }
@@ -197,7 +197,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree;
const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : "");
const pbi = "presElementBox-interaction";
- return !this.presElementDoc ? (null) : (
+ return !(this.presElementDoc instanceof Doc) || this.targetDoc instanceof Promise ? (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.presElementDoc); e.stopPropagation(); }}>
@@ -205,7 +205,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
<strong className="presElementBox-name">
{`${this.indexInPres + 1}. ${this.targetDoc?.title}`}
</strong>
- <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.presElementDoc)}>X</button>
+ <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument?.(this.presElementDoc)}>X</button>
<br />
</>}
<button title="Zoom" className={pbi + (this.presElementDoc.zoomButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 1c05ff864..662b37d77 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -33,7 +33,7 @@ export enum Keys {
export class FilterBox extends React.Component {
static Instance: FilterBox;
- public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
//if true, any keywords can be used. if false, all keywords are required.
//this also serves as an indicator if the word status filter is applied
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 46c109934..9cf5a9c87 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -24,9 +24,14 @@ library.add(faChartBar);
library.add(faGlobeAsia);
library.add(faBan);
+export interface IconBarProps {
+ setIcons: (icons: string[]) => {};
+}
+
+
@observer
-export class IconBar extends React.Component {
- public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+export class IconBar extends React.Component<IconBarProps> {
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
@observable private _icons: string[] = this._allIcons;
@@ -38,7 +43,10 @@ export class IconBar extends React.Component {
@observable public _select: number = 0;
@action.bound
- updateIcon(newArray: string[]) { this._icons = newArray; }
+ updateIcon(newArray: string[]) {
+ this._icons = newArray;
+ this.props.setIcons?.(this._icons);
+ }
@action.bound
getIcons(): string[] { return this._icons; }
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 4f94139d9..52641c543 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -86,15 +86,13 @@ export class IconButton extends React.Component<IconButtonProps>{
return faMusic;
case (DocumentType.COL):
return faObjectGroup;
- case (DocumentType.HIST):
- return faChartBar;
case (DocumentType.IMG):
return faImage;
case (DocumentType.LINK):
return faLink;
case (DocumentType.PDF):
return faFilePdf;
- case (DocumentType.TEXT):
+ case (DocumentType.RTF):
return faStickyNote;
case (DocumentType.VID):
return faVideo;
@@ -158,15 +156,13 @@ export class IconButton extends React.Component<IconButtonProps>{
return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />);
case (DocumentType.COL):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />);
- case (DocumentType.HIST):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faChartBar} />);
case (DocumentType.IMG):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />);
case (DocumentType.LINK):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />);
case (DocumentType.PDF):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />);
- case (DocumentType.TEXT):
+ case (DocumentType.RTF):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />);
case (DocumentType.VID):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />);
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 532b151c5..90f995a8c 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -125,9 +125,7 @@ export class SearchBox extends React.Component<SearchProps> {
@action
- getViews = async (doc: Doc) => {
- return await SearchUtil.GetViewsOfDocument(doc);
- }
+ getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc)
@action.bound
onChange(e: React.ChangeEvent<HTMLInputElement>) {
@@ -166,23 +164,18 @@ export class SearchBox extends React.Component<SearchProps> {
}
}
- public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB, DocumentType.TEMPLATE];
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
//if true, any keywords can be used. if false, all keywords are required.
//this also serves as an indicator if the word status filter is applied
@observable private _filterOpen: boolean = false;
//if icons = all icons, then no icon filter is applied
@observable private _icons: string[] = this._allIcons;
//if all of these are true, no key filter is applied
- @observable private _anyKeywordStatus: boolean = true;
- @observable private _allKeywordStatus: boolean = true;
@observable private _titleFieldStatus: boolean = true;
@observable private _authorFieldStatus: boolean = true;
- @observable private _dataFieldStatus: boolean = true;
//this also serves as an indicator if the collection status filter is applied
@observable public _deletedDocsStatus: boolean = false;
@observable private _collectionStatus = false;
- @observable private _collectionSelfStatus = true;
- @observable private _collectionParentStatus = true;
getFinalQuery(query: string): string {
@@ -223,7 +216,7 @@ export class SearchBox extends React.Component<SearchProps> {
@action
filterDocsByType(docs: Doc[]) {
- if (this._icons.length === 9) {
+ if (this._icons.length === this._allIcons.length) {
return docs;
}
const finalDocs: Doc[] = [];
@@ -254,7 +247,7 @@ export class SearchBox extends React.Component<SearchProps> {
}
get filterTypes() {
- return this._icons.length === 9 ? undefined : this._icons;
+ return this._icons.length === this._allIcons.length ? undefined : this._icons;
}
@action.bound
@@ -332,7 +325,7 @@ export class SearchBox extends React.Component<SearchProps> {
@action
submitSearch = async () => {
- let query = this._searchString;
+ const query = this._searchString;
this.getFinalQuery(query);
this._results = [];
this._resultsSet.clear();
@@ -359,8 +352,10 @@ export class SearchBox extends React.Component<SearchProps> {
private get filterQuery() {
const types = this.filterTypes;
- const includeDeleted = this.getDataStatus();
- return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : "");
+ const includeDeleted = this.getDataStatus() ? "" : " AND NOT deleted_b:true";
+ const includeIcons = this.getDataStatus() ? "" : " AND NOT type_t:fonticonbox";
+ // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello
+ return "NOT baseProto_b:true" + includeDeleted + includeIcons + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
}
getDataStatus() { return this._deletedDocsStatus; }
@@ -569,10 +564,10 @@ export class SearchBox extends React.Component<SearchProps> {
handleNodeChange = () => {
this._nodeStatus = !this._nodeStatus;
if (this._nodeStatus) {
- this.expandSection(`node${this.props.id}`)
+ this.expandSection(`node${this.props.id}`);
}
else {
- this.collapseSection(`node${this.props.id}`)
+ this.collapseSection(`node${this.props.id}`);
}
}
@@ -608,13 +603,13 @@ export class SearchBox extends React.Component<SearchProps> {
collapseSection(thing: string) {
- let id = this.props.id;
- let element = document.getElementById(thing)!;
+ const id = this.props.id;
+ const element = document.getElementById(thing)!;
// get the height of the element's inner content, regardless of its actual size
- var sectionHeight = element.scrollHeight;
+ const sectionHeight = element.scrollHeight;
// temporarily disable all css transitions
- var elementTransition = element.style.transition;
+ const elementTransition = element.style.transition;
element.style.transition = '';
// on the next frame (as soon as the previous style change has taken effect),
@@ -628,7 +623,7 @@ export class SearchBox extends React.Component<SearchProps> {
// have the element transition to height: 0
requestAnimationFrame(function () {
element.style.height = 0 + 'px';
- thing == `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null;
+ thing === `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null;
});
});
@@ -638,12 +633,11 @@ export class SearchBox extends React.Component<SearchProps> {
expandSection(thing: string) {
console.log("expand");
- let element = document.getElementById(thing)!;
+ const element = document.getElementById(thing)!;
// get the height of the element's inner content, regardless of its actual size
- var sectionHeight = element.scrollHeight;
+ const sectionHeight = element.scrollHeight;
// have the element transition to the height of its inner content
- let temp = element.style.height;
element.style.height = sectionHeight + 'px';
// when the next css transition finishes (which should be the one we just triggered)
@@ -663,7 +657,7 @@ export class SearchBox extends React.Component<SearchProps> {
}
autoset(thing: string) {
- let element = document.getElementById(thing)!;
+ const element = document.getElementById(thing)!;
console.log("autoset");
element.removeEventListener('transitionend', function (e) { });
@@ -715,8 +709,8 @@ export class SearchBox extends React.Component<SearchProps> {
bringToFront={emptyFunction}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}
+ NativeHeight={()=>100}
+ NativeWidth={width}
/>
</div>;
}
@@ -725,10 +719,10 @@ export class SearchBox extends React.Component<SearchProps> {
setupDocTypeButtons() {
let doc = this.props.Document;
- const ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ ...opts, dontDecorateSelection: true, backgroundColor: "#121721", dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100 })) as any as Doc;
+ const ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ ...opts, backgroundColor: "#121721", dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100 })) as any as Doc;
const blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
...opts,
- _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", dontDecorateSelection: true, forceActive: true,
+ _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", forceActive: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
backgroundColor: "black", treeViewPreventOpen: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true
})) as any as Doc;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0d77026ad..fe2000700 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -68,7 +68,7 @@ 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) {
+ if (col._viewType === CollectionViewType.Freeform) {
const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
col._panX = newPanX;
@@ -178,14 +178,13 @@ export class SearchItem extends React.Component<SearchItemProps> {
}
const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
- layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
+ layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote :
layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm :
layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup :
layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic :
layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink :
- layoutresult.indexOf(DocumentType.HIST) !== -1 ? faChartBar :
- layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
- faCaretUp;
+ layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
+ faCaretUp;
return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} >
<FontAwesomeIcon icon={button} size="2x" />
</div>;