aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/AudioBox.tsx10
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx6
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx17
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx17
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss57
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx158
-rw-r--r--src/client/views/nodes/DocumentView.scss8
-rw-r--r--src/client/views/nodes/DocumentView.tsx59
-rw-r--r--src/client/views/nodes/FieldView.tsx11
-rw-r--r--src/client/views/nodes/FilterBox.tsx35
-rw-r--r--src/client/views/nodes/FontIconBox.scss13
-rw-r--r--src/client/views/nodes/FontIconBox.tsx3
-rw-r--r--src/client/views/nodes/ImageBox.tsx40
-rw-r--r--src/client/views/nodes/KeyValueBox.scss20
-rw-r--r--src/client/views/nodes/KeyValuePair.scss2
-rw-r--r--src/client/views/nodes/LinkBox.tsx1
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.scss65
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx4
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.scss5
-rw-r--r--src/client/views/nodes/PDFBox.tsx135
-rw-r--r--src/client/views/nodes/RadialMenu.scss4
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx34
-rw-r--r--src/client/views/nodes/VideoBox.scss67
-rw-r--r--src/client/views/nodes/VideoBox.tsx112
-rw-r--r--src/client/views/nodes/WebBox.scss3
-rw-r--r--src/client/views/nodes/WebBox.tsx217
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss11
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx112
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts73
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss4
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx21
-rw-r--r--src/client/views/nodes/formattedText/TooltipTextMenu.scss10
-rw-r--r--src/client/views/nodes/trails/PresBox.scss (renamed from src/client/views/nodes/PresBox.scss)144
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx (renamed from src/client/views/nodes/PresBox.tsx)186
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss235
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx388
-rw-r--r--src/client/views/nodes/trails/PresEnums.ts28
-rw-r--r--src/client/views/nodes/trails/index.ts3
40 files changed, 1551 insertions, 779 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index a2e36f12e..faaa887c4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -196,7 +196,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
- this.props.Document[this.props.fieldKey] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client));
+ this.props.Document[this.props.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._recordStart = new Date().getTime();
@@ -267,7 +267,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// returns the html audio element
@computed get audio() {
- return <audio ref={this.setRef} className={`audiobox-control${this.isContentActive() ? "-interactive" : ""}`}>
+ return <audio ref={this.setRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
<source src={this.path} type="audio/mpeg" />
Not supported.
</audio>;
@@ -328,6 +328,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
focus={DocUtils.DefaultFocus}
bringToFront={emptyFunction}
CollectionView={undefined}
+ isAnyChildContentActive={this.isAnyChildContentActive}
duration={this.duration}
playFrom={this.playFrom}
setTime={this.setAnchorTime}
@@ -337,7 +338,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ScreenToLocalTransform={this.timelineScreenToLocal}
Play={this.Play}
Pause={this.Pause}
- isContentActive={this.isContentActive}
playLink={this.playLink}
PanelWidth={this.timelineWidth}
PanelHeight={this.timelineHeight}
@@ -345,7 +345,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
render() {
- const interactive = SnappingManager.GetIsDragging() || this.isContentActive() ? "-interactive" : "";
+ const interactive = SnappingManager.GetIsDragging() || this.props.isContentActive() ? "-interactive" : "";
return <div className="audiobox-container"
onContextMenu={this.specificContextMenu}
onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined}
@@ -370,7 +370,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
RECORD
</button>}
</div> :
- <div className="audiobox-controls" style={{ pointerEvents: this._isAnyChildContentActive || this.isContentActive() ? "all" : "none" }} >
+ <div className="audiobox-controls" style={{ pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? "all" : "none" }} >
<div className="audiobox-dictation" />
<div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
<div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 092823603..9cc4b1f9a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,8 +17,8 @@ import { InkingStroke } from "../InkingStroke";
import { StyleProp } from "../StyleProvider";
import "./CollectionFreeFormDocumentView.scss";
import { DocumentView, DocumentViewProps } from "./DocumentView";
-import { FieldViewProps } from "./FieldView";
import React = require("react");
+import Color = require("color");
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -164,6 +164,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
PanelWidth: this.panelWidth,
PanelHeight: this.panelHeight,
};
+ const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (background && Color(background).alpha() !== 1 ? "multiply" : undefined);
return <div className={"collectionFreeFormDocumentView-container"}
style={{
outline: this.Highlight ? "orange solid 2px" : "",
@@ -172,7 +174,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
transform: this.transform,
transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
zIndex: this.ZInd,
- mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
+ mixBlendMode,
display: this.ZInd === -99 ? "none" : undefined
}} >
<DocumentView {...divProps} ref={action((r: DocumentView | null) => this._contentView = r)} />
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 153176afc..6708a08ee 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,17 +1,18 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { Doc } from '../../../fields/Doc';
+import { Doc, Opt } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
import { createSchema, makeInterface } from '../../../fields/Schema';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { StyleProp } from '../StyleProvider';
import "./ComparisonBox.scss";
-import { DocumentView } from './DocumentView';
+import { DocumentView, DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import React = require("react");
@@ -71,6 +72,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
delete this.dataDoc[fieldKey];
}
+ docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+ if (property === StyleProp.PointerEvents) return "none";
+ return this.props.styleProvider?.(doc, props, property);
+ }
+
render() {
const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
const clearButton = (which: string) => {
@@ -84,6 +90,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
return whichDoc ? <>
<DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ isContentActive={returnFalse}
+ isDocumentActive={returnFalse}
+ styleProvider={this.docStyleProvider}
Document={whichDoc}
DataDoc={undefined}
pointerEvents={"none"} />
@@ -102,7 +111,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
return (
- <div className={`comparisonBox${this.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
+ <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
{displayBox("after", 1, this.props.PanelWidth() - 3)}
<div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
{displayBox("before", 0, 0)}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 34488ffbe..3d2cdf5a4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -11,7 +11,7 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
import { CollectionSchemaView } from "../collections/collectionSchema/CollectionSchemaView";
import { CollectionView } from "../collections/CollectionView";
import { InkingStroke } from "../InkingStroke";
-import { PresElementBox } from "../presentationview/PresElementBox";
+import { PresElementBox } from "../nodes/trails/PresElementBox";
import { SearchBox } from "../search/SearchBox";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
@@ -32,7 +32,7 @@ import { LabelBox } from "./LabelBox";
import { LinkAnchorBox } from "./LinkAnchorBox";
import { LinkBox } from "./LinkBox";
import { PDFBox } from "./PDFBox";
-import { PresBox } from "./PresBox";
+import { PresBox } from "./trails/PresBox";
import { ScreenshotBox } from "./ScreenshotBox";
import { ScriptingBox } from "./ScriptingBox";
import { SliderBox } from "./SliderBox";
@@ -64,6 +64,7 @@ interface HTMLtagProps {
htmltag: string;
onClick?: ScriptField;
onInput?: ScriptField;
+ scaling: number;
}
//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
@@ -82,7 +83,7 @@ interface HTMLtagProps {
export class HTMLtag extends React.Component<HTMLtagProps> {
click = (e: React.MouseEvent) => {
const clickScript = (this.props as any).onClick as Opt<ScriptField>;
- clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc });
+ clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling });
}
onInput = (e: React.FormEvent<HTMLDivElement>) => {
const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
@@ -90,9 +91,9 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
}
render() {
const style: { [key: string]: any } = {};
- const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "Document", "key", "onInput", "onClick", "__proto__"]).omit;
+ const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "scaling", "Document", "key", "onInput", "onClick", "__proto__"]).omit;
const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.props.RootDoc, this: this.props.Document }).result as string || "";
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string || "";
};
Object.keys(divKeys).map((prop: string) => {
const p = (this.props as any)[prop] as string;
@@ -180,11 +181,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
return prefix + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + postfix;
};
- layoutFrame = layoutFrame.replace(/(>[^{]*)\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
+ layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} htmltag='${p1}'`;
+ return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.scaling?.() || 1}' htmltag='${p1}'`;
};
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
@@ -200,7 +201,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
if (splits.length > 1) {
const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
- return ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name, value: "string" });
+ return ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name, scale: "number", value: "string" });
}
return undefined;
// add input function to props
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 735aa669f..b37b68249 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -1,16 +1,27 @@
-@import "../globalCssVariables.scss";
+@import "../global/globalCssVariables.scss";
+.documentLinksButton-menu {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ align-items: center;
+}
+
.documentLinksButton-cont {
min-width: 20;
min-height: 20;
position: absolute;
}
+
.documentLinksButton,
.documentLinksButton-endLink,
.documentLinksButton-startLink {
- height: 20px;
- width: 20px;
+ height: 25px;
+ width: 25px;
position: absolute;
border-radius: 50%;
opacity: 0.9;
@@ -33,27 +44,43 @@
}
.documentLinksButton {
- background-color: black;
+ background-color: $dark-gray;
+ color: $white;
font-weight: bold;
+ width: 80%;
+ height: 80%;
+ font-size: 100%;
+ transition: 0.2s ease all;
&:hover {
- background: $main-accent;
- transform: scale(1.05);
- cursor: pointer;
+ background-color: $black;
}
}
-.documentLinksButton-endLink {
- border: red solid 2px;
+.documentLinksButton.startLink {
+ background-color: $medium-blue;
+ color: $white;
+ font-weight: bold;
+ width: 80%;
+ height: 80%;
+ font-size: 100%;
+ transition: 0.2s ease all;
&:hover {
- background: deepskyblue;
- transform: scale(1.05);
- cursor: pointer;
+ background-color: $black;
}
}
-.documentLinksButton-startLink {
- border: red solid 2px;
- background-color: rgba(255, 192, 203, 0.5);
+.documentLinksButton-endLink {
+ border: $medium-blue 2px dashed;
+ color: $medium-blue;
+ background-color: none !important;
+ width: 80%;
+ height: 80%;
+ font-size: 100%;
+ transition: 0.2s ease all;
+
+ &:hover {
+ background-color: $light-blue;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index a6d07374a..7648e866e 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -20,6 +20,7 @@ import './DocumentLinksButton.scss';
import { DocServer } from "../../DocServer";
import { LightboxView } from "../LightboxView";
import { cat } from "shelljs";
+import { Colors } from "../global/globalEnums";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -30,12 +31,12 @@ interface DocumentLinksButtonProps {
Offset?: (number | undefined)[];
AlwaysOn?: boolean;
InMenu?: boolean;
- StartLink?: boolean;
+ StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
- @observable public static StartLink: Doc | undefined;
+ @observable public static StartLink: Doc | undefined; //origin's Doc, if defined
@observable public static StartLinkView: DocumentView | undefined;
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
@@ -45,6 +46,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
public static invisibleWebRef = React.createRef<HTMLDivElement>();
@action public static ClearLinkEditor() { DocumentLinksButton.LinkEditorDocView = undefined; }
+
@action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
if (this.props.InMenu && this.props.StartLink) {
@@ -66,6 +68,40 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return false;
}
+ onLinkMenuOpen = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
+ if (doubleTap) {
+ const rootDoc = this.props.View.rootDoc;
+ const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish";
+ DocServer.GetRefField(docid).then(async docx => {
+ const rootAlias = () => {
+ const rootAlias = Doc.MakeAlias(rootDoc);
+ rootAlias.x = rootAlias.y = 0;
+ return rootAlias;
+ };
+ let wid = rootDoc[WidthSym]();
+ const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid);
+ const docs = await DocListCastAsync(Doc.GetProto(target).data);
+ if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target);
+ DocListCast(rootDoc.links).forEach(link => {
+ const other = LinkManager.getOppositeAnchor(link, rootDoc);
+ const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+ if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+ const alias = Doc.MakeAlias(otherdoc);
+ alias.x = wid;
+ alias.y = 0;
+ alias._lockedPosition = false;
+ wid += otherdoc[WidthSym]();
+ Doc.AddDocToList(Doc.GetProto(target), "data", alias);
+ }
+ });
+ LightboxView.SetLightboxDoc(target);
+ });
+ }
+ else DocumentLinksButton.LinkEditorDocView = this.props.View;
+ }));
+ }
+
@undoBatch
onLinkButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
@@ -78,36 +114,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
- } else if (!this.props.InMenu) {
- if (doubleTap) {
- const rootDoc = this.props.View.rootDoc;
- const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish";
- DocServer.GetRefField(docid).then(async docx => {
- const rootAlias = () => {
- const rootAlias = Doc.MakeAlias(rootDoc);
- rootAlias.x = rootAlias.y = 0;
- return rootAlias;
- };
- let wid = rootDoc[WidthSym]();
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid);
- const docs = await DocListCastAsync(Doc.GetProto(target).data);
- if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target);
- DocListCast(rootDoc.links).forEach(link => {
- const other = LinkManager.getOppositeAnchor(link, rootDoc);
- const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
- if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
- const alias = Doc.MakeAlias(otherdoc);
- alias.x = wid;
- alias.y = 0;
- alias._lockedPosition = false;
- wid += otherdoc[WidthSym]();
- Doc.AddDocToList(Doc.GetProto(target), "data", alias);
- }
- });
- LightboxView.SetLightboxDoc(target);
- });
- }
- else DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}));
}
@@ -120,17 +126,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
- } else {
+ } else { //if this LinkButton's Document is undefined
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
-
//action(() => Doc.BrushDoc(this.props.View.Document));
} else if (!this.props.InMenu) {
DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}
+
completeLink = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => {
if (doubleTap && !this.props.StartLink) {
@@ -141,7 +147,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
} else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
const sourceDoc = DocumentLinksButton.StartLink;
const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
- const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
+ const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "links"); //why is long drag here when this is used for completing links by clicking?
LinkManager.currentLink = linkDoc;
@@ -184,7 +190,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
} else if (startLink !== endLink) {
endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
- const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
+ const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "link", undefined, undefined, true);
LinkManager.currentLink = linkDoc;
@@ -192,7 +198,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
- const dashHyperlink = Utils.prepend("/doc/" + (startIsAnnotation ? endLink[Id] : startLink[Id]));
+ const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId,
(startIsAnnotation ? startLink : endLink)); // edit annotation to add a Dash hyperlink to the linked doc
}
@@ -242,45 +248,50 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return results;
}
+ /**
+ * gets the JSX of the link button (btn used to start/complete links) OR the link-view button (btn on bottom left of each linked node)
+ *
+ * todo:glr / anh seperate functionality such as onClick onPointerDown of link menu button
+ */
@computed get linkButtonInner() {
- const btnDim = this.props.InMenu ? "20px" : "30px";
+ const btnDim = "30px";
const link = <img style={{ width: "22px", height: "16px" }} src={`/assets/${"link.png"}`} />;
-
- return <div className="documentLinksButton-cont" ref={this._linkButton}
- style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}
- >
- <div className={"documentLinksButton"}
- onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
- style={{
- backgroundColor: this.props.InMenu ? "" : "#add8e6",
- color: this.props.InMenu ? "white" : "black",
- width: btnDim,
- height: btnDim,
- }} >
- {this.props.InMenu ?
- this.props.StartLink ?
- <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
- : link
- : Array.from(this.filteredLinks).length}
- </div>
- {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ?
- <div className={"documentLinksButton-endLink"}
+ const isActive = (DocumentLinksButton.StartLink === this.props.View.props.Document) && this.props.StartLink;
+ return (!this.props.InMenu ?
+ <div className="documentLinksButton-cont"
+ style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}
+ >
+ <div className={"documentLinksButton"}
+ onPointerDown={this.onLinkMenuOpen} onClick={this.onLinkClick}
style={{
- width: btnDim, height: btnDim,
- backgroundColor: DocumentLinksButton.StartLink ? "" : "grey",
- opacity: DocumentLinksButton.StartLink ? "" : "50%",
- border: DocumentLinksButton.StartLink ? "" : "none",
- cursor: DocumentLinksButton.StartLink ? "pointer" : "default"
- }}
- onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
- onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)} />
- : (null)
- }
- {DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ?
- <div className={"documentLinksButton-startLink"} onPointerDown={this.clearLinks} onClick={this.clearLinks} style={{ width: btnDim, height: btnDim }} />
- : (null)
- }
- </div >;
+ backgroundColor: Colors.LIGHT_BLUE,
+ color: Colors.BLACK,
+ width: btnDim,
+ height: btnDim,
+ }}>
+ {Array.from(this.filteredLinks).length}
+ </div>
+ </div>
+ :
+ <div className="documentLinksButton-menu" ref={this._linkButton}>
+ {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node
+ <div className={"documentLinksButton-endLink"}
+ onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
+ onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
+ </div>
+ : (null)
+ }
+ {
+ this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+ <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
+ </div>
+ :
+ (null)
+ }
+ </div>
+ );
}
render() {
@@ -290,6 +301,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const buttonTitle = "Tap to view links; double tap to open link collection";
const title = this.props.InMenu ? menuTitle : buttonTitle;
+ //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) :
this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ?
<Tooltip title={<div className="dash-tooltip">{title}</div>}>
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index bdbece621..7f164ca48 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-@import "../globalCssVariables";
+@import "../global/globalCssVariables";
.documentView-effectsWrapper {
border-radius: inherit;
@@ -22,7 +22,7 @@
transition: outline .3s linear;
cursor: grab;
- // background: $light-color; //overflow: hidden;
+ // background: $white; //overflow: hidden;
transform-origin: left top;
&.minimized {
@@ -147,7 +147,7 @@
.documentView-titleWrapper,
.documentView-titleWrapper-hover {
overflow: hidden;
- color: white;
+ color: $black;
transform-origin: top left;
top: 0;
width: 100%;
@@ -218,6 +218,6 @@
.documentView-node:first-child {
position: relative;
- background: "#B59B66"; //$light-color;
+ background: "#B59B66"; //$white;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b861669f8..5bd6049d6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
import { AudioField } from "../../../fields/URLField";
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils, returnTrue } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
@@ -43,7 +43,7 @@ import { DocumentLinksButton } from './DocumentLinksButton';
import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
import { LinkDocPreview } from "./LinkDocPreview";
-import { PresBox } from './PresBox';
+import { PresBox } from './trails/PresBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
import { ScriptingBox } from "./ScriptingBox";
@@ -64,7 +64,7 @@ export enum ViewAdjustment {
doNothing = 0
}
-export const ViewSpecPrefix = "_VIEW"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
+export const ViewSpecPrefix = "viewSpec"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
export interface DocFocusOptions {
originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
@@ -84,10 +84,13 @@ export interface DocComponentView {
reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
+ isAnyChildContentActive?: () => boolean; // is any child content of the document active
getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
playFrom?: (time: number, endTime?: number) => void;
setFocus?: () => void;
+ fieldKey?: string;
+ annotationKey?: string;
}
export interface DocumentViewSharedProps {
renderDepth: number;
@@ -137,7 +140,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
treeViewDoc?: Doc;
isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
- isContentActive: () => boolean | undefined; // whether a document should handle pointer events
+ isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
radialMenu?: String[];
LayoutTemplateString?: string;
@@ -180,9 +183,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
private _dropDisposer?: DragManager.DragDropDisposer;
private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
+ @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
- private get topMost() { return this.props.renderDepth === 0; }
+ private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
@@ -420,8 +423,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
focus = (anchor: Doc, options?: DocFocusOptions) => {
LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
- // copying over _VIEW fields immediately allows the view type to switch to create the right _componentView
- Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]));
+ // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
+ Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
+ this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
+ });
// after a timeout, the right _componentView should have been created, so call it to update its view spec values
setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
@@ -662,7 +667,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
- Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
+ StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
this.props.contextMenuItems?.().forEach(item =>
item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
@@ -746,7 +751,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
}
}
@@ -754,16 +759,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
}
- !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
- cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
-
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
!Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
!Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
}
+
if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
@@ -775,8 +779,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
setHeight = (height: number) => this.layoutDoc._height = height;
- setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
- isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() ? true : false;
+ setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
+ isContentActive = (outsideReaction?: boolean) => {
+ return CurrentUserUtils.SelectedTool !== InkTool.None ||
+ this.props.Document.forceActive ||
+ this.props.isSelected(outsideReaction) ||
+ this._componentView?.isAnyChildContentActive?.() ||
+ this.props.isContentActive() ? true : false;
+ }
@computed get contents() {
TraceMobx();
const audioView = !this.layoutDoc._showAudio ? (null) :
@@ -791,7 +801,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>;
return <div className="documentView-contentsView"
style={{
- pointerEvents: this.props.contentPointerEvents as any,
+ pointerEvents: (this.props.contentPointerEvents as any) || (this.isContentActive() ? "all" : "none"),
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView key={1} {...this.props}
@@ -808,7 +818,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
{this.hideLinkButton ? (null) :
- <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
+ <div style={{ transformOrigin: "top left", transform: `scale(${Math.min(1, this.props.ScreenToLocalTransform().scale(this.props.ContentScaling?.() || 1).Scale)})` }}>
+ <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />
+ </div>}
{audioView}
</div>;
@@ -838,7 +850,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
// need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews. All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here
- const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
+ const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
return filtered.map((link, i) =>
<div className="documentView-anchorCont" key={i + 1}>
<DocumentView {...this.props}
@@ -846,6 +858,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
+ fitWidth={returnTrue}
styleProvider={this.anchorStyleProvider}
removeDocument={this.hideLinkAnchor}
LayoutTemplate={undefined}
@@ -884,7 +897,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
- const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 });
+ const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
audioDoc.treeViewExpandedView = "layout";
const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
if (audioAnnos === undefined) {
@@ -970,7 +983,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
const highlightColor = (CurrentUserUtils.ActiveDashboard?.darkScheme ?
["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
- ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
+ ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
@@ -978,7 +991,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
- const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
+ const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
return <div className={DocumentView.ROOT_DIV} ref={this._mainCont}
onContextMenu={this.onContextMenu}
@@ -1042,7 +1055,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
}
- @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || [CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._viewType as any); }
+ @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); }
@computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
@computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
@computed get nativeScaling() {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 86250c9d1..ee81e106a 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -2,11 +2,10 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
-import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc";
+import { Doc, Field, FieldResult } from "../../../fields/Doc";
import { List } from "../../../fields/List";
-import { VideoField, WebField } from "../../../fields/URLField";
+import { WebField } from "../../../fields/URLField";
import { DocumentViewSharedProps } from "./DocumentView";
-import { VideoBox } from "./VideoBox";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -64,9 +63,9 @@ export class FieldView extends React.Component<FieldViewProps> {
// else if (field instaceof PresBox) {
// return <PresBox {...this.props} />;
// }
- else if (field instanceof VideoField) {
- return <VideoBox {...this.props} />;
- }
+ // else if (field instanceof VideoField) {
+ // return <VideoBox {...this.props} />;
+ // }
// else if (field instanceof AudioField) {
// return <AudioBox {...this.props} />;
//}
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index c892a9f6c..7ad03e055 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -79,10 +79,19 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
* @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
*/
@computed static get targetDoc() {
+ return SelectionManager.Views().length ? SelectionManager.Views()[0].Document : CurrentUserUtils.ActiveDashboard;
+ }
+ @computed static get targetDocChildKey() {
+ if (SelectionManager.Views().length) {
+ return SelectionManager.Views()[0].ComponentView?.annotationKey || SelectionManager.Views()[0].ComponentView?.fieldKey || "data";
+ }
+ return "data";
+ }
+ @computed static get targetDocChildren() {
if (SelectionManager.Views().length) {
- return SelectionManager.Views()[0]?.Document.type === DocumentType.COL ? SelectionManager.Views()[0].Document : SelectionManager.Views()[0]?.props.ContainingCollectionDoc!;
+ return DocListCast(FilterBox.targetDoc[FilterBox.targetDocChildKey]);
}
- return CurrentUserUtils.ActiveDashboard;
+ return DocListCast(CurrentUserUtils.ActiveDashboard.data);
}
@observable _loaded = false;
@@ -100,8 +109,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
const targetDoc = FilterBox.targetDoc;
if (this._loaded && targetDoc) {
const allDocs = new Set<Doc>();
- const activeTabs = DocListCast(targetDoc.data);
- SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc));
+ const activeTabs = FilterBox.targetDocChildren;
+ SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc));
setTimeout(action(() => this._allDocs = Array.from(allDocs)));
}
return this._allDocs;
@@ -133,8 +142,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
return this.activeAttributes.map(attribute => StrCast(attribute.title));
}
- gatherFieldValues(dashboard: Doc, facetKey: string) {
- const childDocs = DocListCast(dashboard.data);
+ gatherFieldValues(childDocs: Doc[], facetKey: string) {
const valueSet = new Set<string>();
let rtFields = 0;
childDocs.forEach((d) => {
@@ -194,13 +202,13 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
* Responds to clicking the check box in the flyout menu
*/
facetClick = (facetHeader: string) => {
- const { targetDoc } = FilterBox;
+ const { targetDoc, targetDocChildren } = FilterBox;
const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
if (found !== -1) {
this.removeFilter(facetHeader);
} else {
- const allCollectionDocs = DocListCast((targetDoc.data as any)?.[0].data);
- const facetValues = this.gatherFieldValues(targetDoc, facetHeader);
+ const allCollectionDocs = targetDocChildren;
+ const facetValues = this.gatherFieldValues(targetDocChildren, facetHeader);
let nonNumbers = 0;
let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
@@ -248,7 +256,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
newFacet.layoutKey = "layout";
newFacet.type = DocumentType.COL;
newFacet.target = targetDoc;
- newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${facetHeader}")`);
+ newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
}
newFacet.hideContextMenu = true;
Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
@@ -409,6 +417,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
renderDepth={1}
dropAction={this.props.dropAction}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ isAnyChildContentActive={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
isSelected={returnFalse}
@@ -479,10 +488,10 @@ Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader:
}
return undefined;
});
-Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) {
+Scripting.addGlobal(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) {
const allCollectionDocs = new Set<Doc>();
- const activeTabs = DocListCast(layoutDoc.data);
- SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allCollectionDocs.add(doc));
+ const activeTabs = DocListCast(layoutDoc[childKey]);
+ SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
const set = new Set<string>();
if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key)));
else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 33ac85a0e..718af2c16 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -1,5 +1,7 @@
+@import "../global/globalCssVariables";
+
.fontIconBox-label {
- color: white;
+ color: $white;
margin-right: 4px;
margin-top: 1px;
position: relative;
@@ -22,8 +24,8 @@
position: absolute;
top: -10px;
right: -10px;
- color: white;
- background: #f44b42;
+ color: $white;
+ background: $pink;
font-weight: 300;
border-radius: 100%;
width: 25px;
@@ -37,7 +39,7 @@
.menuButton-circle,
.menuButton-round {
border-radius: 100%;
- background-color: black;
+ background-color: $dark-gray;
padding: 0;
.fontIconBox-label {
@@ -47,13 +49,14 @@
}
&:hover {
- background-color: #aaaaa3;
+ background-color: $light-gray;
}
}
.menuButton-square {
padding-top: 3px;
padding-bottom: 3px;
+ background-color: $dark-gray;
.fontIconBox-label {
border-radius: 0px;
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 6ae4b9726..0d415e238 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -14,6 +14,7 @@ import { DocComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import './FontIconBox.scss';
+import { Colors } from '../global/globalEnums';
const FontIconSchema = createSchema({
icon: "string",
});
@@ -47,7 +48,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
const icon = StrCast(this.dataDoc.icon, "user") as any;
const presSize = shape === 'round' ? 25 : 30;
const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`}
- style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />;
+ style={{ width: presSize, height: presSize, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})`, marginBottom: "5px" }} />;
const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu}
style={{ backgroundColor: backgroundColor, }}>
<div className="menuButton-wrap">
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e2e08a0e6..38deb4a73 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,8 +1,9 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
import { observer } from "mobx-react";
import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
+import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { createSchema, makeInterface } from '../../../fields/Schema';
@@ -10,10 +11,11 @@ import { ComputedField } from '../../../fields/ScriptField';
import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnOne, Utils } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Networking } from '../../Network';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
@@ -21,11 +23,13 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
+import { SnappingManager } from '../../util/SnappingManager';
const path = require('path');
export const pageSchema = createSchema({
@@ -60,6 +64,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
} // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+
+ getAnchor = () => {
+ const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations);
+ anchor && this.addDocument(anchor);
+ return anchor ?? this.rootDoc;
+ }
+
componentDidMount() {
this.props.setContentView?.(this); // bcz: do not remove this. without it, stepping into an image in the lightbox causes an infinite loop....
this._disposers.sizer = reaction(() => (
@@ -72,8 +83,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{ fireImmediately: true, delay: 1000 });
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.clear();
+ // Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
+ // this._savedAnnotations.clear();
}));
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
({ nativeSize, width }) => {
@@ -227,7 +238,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
let succeeded = true;
let data: ImageField | undefined;
try {
- data = new ImageField(Utils.prepend(accessPaths.agnostic.client));
+ data = new ImageField(accessPaths.agnostic.client);
} catch {
succeeded = false;
}
@@ -283,7 +294,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div className="imageBox-fader" >
<img key="paths" ref={this._imgRef}
src={srcpath}
- style={{ transform, transformOrigin }} draggable={false}
+ style={{ transform, transformOrigin }}
+ draggable={false}
width={nativeWidth} />
{fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
<img className="imageBox-fadeaway" key={"fadeaway"} ref={this._imgRef}
@@ -319,14 +331,19 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
TraceMobx();
return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
}
- @action
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true)) this._marqueeing = [e.clientX, e.clientY];
+ if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ setupMoveUpEvents(this, e, action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ }
}
@action
finishMarquee = () => {
this._marqueeing = undefined;
- this.props.select(true);
+ this.props.select(false);
}
render() {
@@ -342,16 +359,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}} >
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
+ isAnnotationOverlay={true}
fieldKey={this.annotationKey}
CollectionView={undefined}
- isAnnotationOverlay={true}
annotationLayerHostsContent={true}
PanelWidth={this.props.PanelWidth}
PanelHeight={this.props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
- select={emptyFunction}
- isContentActive={this.isContentActive}
scaling={returnOne}
+ select={emptyFunction}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index eb7c2f32b..ffcba4981 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,10 +1,10 @@
-@import "../globalCssVariables";
+@import "../global/globalCssVariables";
.keyValueBox-cont {
overflow-y: scroll;
width:100%;
height: 100%;
- background-color: $light-color;
- border: 1px solid $intermediate-color;
+ background-color: $white;
+ border: 1px solid $medium-gray;
border-radius: $border-radius;
box-sizing: border-box;
display: inline-block;
@@ -56,8 +56,8 @@ $header-height: 30px;
width:100%;
position: relative;
display: inline-block;
- background: $intermediate-color;
- color: $light-color;
+ background: $medium-gray;
+ color: $white;
text-transform: uppercase;
letter-spacing: 2px;
font-size: 12px;
@@ -66,7 +66,7 @@ $header-height: 30px;
th {
font-weight: normal;
&:first-child {
- border-right: 1px solid $light-color;
+ border-right: 1px solid $white;
}
}
}
@@ -76,9 +76,9 @@ $header-height: 30px;
display: flex;
width:100%;
height:$header-height;
- background: $light-color;
+ background: $white;
.formattedTextBox-cont {
- background: $light-color;
+ background: $white;
}
}
.keyValueBox-cont {
@@ -116,8 +116,8 @@ $header-height: 30px;
display: flex;
width:100%;
height:30px;
- background: $light-color-secondary;
+ background: $light-gray;
.formattedTextBox-cont {
- background: $light-color-secondary;
+ background: $light-gray;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index f78767234..5b660e582 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,4 +1,4 @@
-@import "../globalCssVariables";
+@import "../global/globalCssVariables";
.keyValuePair-td-key {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index c65ba9c69..55ea45bb8 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -30,6 +30,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
dontRegisterView={true}
renderDepth={this.props.renderDepth + 1}
CollectionView={undefined}
+ isAnyChildContentActive={returnFalse}
isContentActive={this.isContentActiveFunc}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
index d92823ccc..a8db5d360 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.scss
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -1,9 +1,13 @@
+@import "../global/globalCssVariables.scss";
+
.linkDescriptionPopup {
display: flex;
-
- border: 1px solid rgb(170, 26, 26);
-
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ border: 2px solid $medium-blue;
+ background-color: $white;
width: auto;
position: absolute;
@@ -11,17 +15,11 @@
z-index: 10000;
border-radius: 10px;
font-size: 12px;
- //white-space: nowrap;
-
- background-color: rgba(250, 250, 250, 0.95);
- padding-top: 9px;
- padding-bottom: 9px;
- padding-left: 9px;
- padding-right: 9px;
+ gap: 5px;
+ padding: 9px;
.linkDescriptionPopup-input {
float: left;
- background-color: rgba(250, 250, 250, 0.95);
color: rgb(100, 100, 100);
border: none;
min-width: 160px;
@@ -30,46 +28,29 @@
.linkDescriptionPopup-btn {
float: right;
-
justify-content: center;
vertical-align: middle;
-
.linkDescriptionPopup-btn-dismiss {
- background-color: white;
- color: black;
+ cursor: pointer;
display: inline;
- right: 0;
- border-radius: 10px;
- border: 1px solid black;
- padding: 3px;
- font-size: 9px;
- text-align: center;
- position: relative;
- margin-right: 4px;
- justify-content: center;
-
- &:hover{
- cursor: pointer;
- }
+ white-space: nowrap;
+ padding: 5px;
+ vertical-align: middle;
+ background-color: $close-red;
+ border-radius: 3px;
+ color: black;
}
.linkDescriptionPopup-btn-add {
- background-color: black;
- color: white;
+ cursor: pointer;
display: inline;
- right: 0;
- border-radius: 10px;
- border: 1px solid black;
- padding: 3px;
- font-size: 9px;
- text-align: center;
- position: relative;
- justify-content: center;
-
- &:hover{
- cursor: pointer;
- }
+ white-space: nowrap;
+ padding: 5px;
+ vertical-align: middle;
+ background-color: $light-blue;
+ border-radius: 3px;
+ color: black;
}
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 30b272a9a..b62a4dd56 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -54,7 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
}}>
<input className="linkDescriptionPopup-input"
onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
- placeholder={"(optional) enter link label..."}
+ placeholder={"(Optional) Enter link description..."}
onChange={(e) => this.descriptionChanged(e)}>
</input>
<div className="linkDescriptionPopup-btn">
@@ -65,4 +65,4 @@ export class LinkDescriptionPopup extends React.Component<{}> {
</div>
</div>;
}
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index b73fb10df..126a37eb8 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -72,14 +72,14 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
@computed get href() {
if (this.props.hrefs?.length) {
const href = this.props.hrefs[this._hrefInd];
- if (href.indexOf(Utils.prepend("/doc/")) !== 0) { // link to a web page URL -- try to show a preview
+ if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview
if (href.startsWith("https://en.wikipedia.org/wiki/")) {
wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
} else {
setTimeout(action(() => this._toolTipText = "url => " + href));
}
} else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
- const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0];
anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
if (anchor instanceof Doc && DocListCast(anchor.links).length) {
this._linkDoc = DocListCast(anchor.links)[0];
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 0f46da294..72dec6e4c 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -7,7 +7,7 @@
overflow: hidden;
cursor: auto;
transform-origin: top left;
- z-index: 0;
+ //z-index: 0;
.pdfBox-ui {
position: absolute;
@@ -30,6 +30,7 @@
justify-content: center;
border-radius: 3px;
pointer-events: all;
+ z-index: 1; // so it appears on top of the document's title, if shown
}
.pdfBox-pageNums {
@@ -223,7 +224,7 @@
.pdfBox {
width: 100%;
height: 100%;
- pointer-events: none;
+ //pointer-events: none;
.pdfViewerDash-text {
.textLayer {
display: none;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index feaeb9e21..5e07229c1 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,13 +3,13 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
import { documentSchema } from '../../../fields/documentSchemas';
import { makeInterface } from "../../../fields/Schema";
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast, BoolCast } from '../../../fields/Types';
import { PdfField } from "../../../fields/URLField";
import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, returnOne } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { KeyCodes } from '../../util/KeyCodes';
import { undoBatch } from '../../util/UndoManager';
@@ -23,6 +23,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
import React = require("react");
+import { AnchorMenu } from '../pdf/AnchorMenu';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -30,6 +31,7 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
+ public static openSidebarWidth = 250;
private _searchString: string = "";
private _initialScrollTarget: Opt<Doc>;
private _pdfViewer: PDFViewer | undefined;
@@ -52,30 +54,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href));
else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action(pdf => this._pdf = pdf));
}
-
- const backup = "oldPath";
- const href = this.pdfUrl?.url.href;
- if (href) {
- const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g;
- const matches = pathCorrectionTest.exec(href);
- // console.log("\nHere's the { url } being fed into the outer regex:");
- // console.log(href);
- // console.log("And here's the 'properPath' build from the captured filename:\n");
- if (matches !== null && href.startsWith(window.location.origin)) {
- const properPath = Utils.prepend(`/files/pdfs/${matches[0]}`);
- //console.log(properPath);
- if (!properPath.includes(href)) {
- console.log(`The two (url and proper path) were not equal`);
- const proto = Doc.GetProto(this.props.Document);
- proto[this.props.fieldKey] = new PdfField(properPath);
- proto[backup] = href;
- } else {
- //console.log(`The two (url and proper path) were equal`);
- }
- } else {
- console.log("Outer matches was null!");
- }
- }
}
componentWillUnmount() { this._selectReactionDisposer?.(); }
@@ -89,16 +67,21 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
scrollFocus = (doc: Doc, smooth: boolean) => {
+ if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+ this.toggleSidebar(!smooth);
+ }
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
this._initialScrollTarget = doc;
return this._pdfViewer?.scrollFocus(doc, smooth);
}
getAnchor = () => {
- const anchor = Docs.Create.TextanchorDocument({
- title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
- y: NumCast(this.layoutDoc._scrollTop),
- });
+ const anchor =
+ AnchorMenu.Instance?.GetAnchor() ??
+ Docs.Create.TextanchorDocument({
+ title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
+ annotationOn: this.rootDoc,
+ y: NumCast(this.layoutDoc._scrollTop),
+ });
this.addDocument(anchor);
return anchor;
}
@@ -162,15 +145,24 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
}
return false;
- }, emptyFunction, this.toggleSidebar);
+ }, emptyFunction, () => this.toggleSidebar());
}
- toggleSidebar = action(() => {
+ @observable _previewNativeWidth: Opt<number> = undefined;
+ @observable _previewWidth: Opt<number> = undefined;
+ toggleSidebar = action((preview: boolean = false) => {
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
+ const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ if (preview) {
+ this._previewNativeWidth = nativeWidth * ratio;
+ this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this._showSidebar = true;
+ }
+ else {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
});
settingsPanel() {
const pageBtns = <>
@@ -185,9 +177,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</>;
const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
const curPage = this.Document._curPage || 1;
- return !this.isContentActive() ? (null) :
+ return !this.props.isContentActive() ? (null) :
<div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true}
- onPointerDown={e => e.stopPropagation()} style={{ display: this.isContentActive() ? "flex" : "none" }}>
+ onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
<div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title={searchTitle} />
<input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
@@ -217,13 +209,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
{this._pageControls ? pageBtns : (null)}
</div>
<button className="pdfBox-sidebarBtn" title="Toggle Sidebar"
- style={{ display: !this.isContentActive() ? "none" : undefined }}
+ style={{ display: !this.props.isContentActive() ? "none" : undefined }}
onPointerDown={this.sidebarBtnDown} >
<FontAwesomeIcon icon={"chevron-left"} size="sm" />
</button>
</div>;
}
- sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+ sidebarWidth = () => !this.SidebarShown ? 0 :
+ this._previewWidth ? PDFBox.openSidebarWidth :
+ (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
@@ -235,7 +229,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
@computed get renderTitleBox() {
- const classname = "pdfBox" + (this.isContentActive() ? "-interactive" : "");
+ const classname = "pdfBox" + (this.props.isContentActive() ? "-interactive" : "");
return <div className={classname} >
<div className="pdfBox-title-outer">
<strong className="pdfBox-title" >{this.props.Document.title}</strong>
@@ -244,43 +238,60 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ contentScaling = () => {
+ return 1;
+ }
@computed get renderPdfView() {
TraceMobx();
+ const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+ const scale = previewScale * (this.props.scaling?.() || 1);
return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}
style={{
height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ?
NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined
}}>
<div className="pdfBox-background" />
- <PDFViewer {...this.props}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- pdf={this._pdf!}
- url={this.pdfUrl!.url.pathname}
- isContentActive={this.isContentActive}
- anchorMenuClick={this.anchorMenuClick}
- loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
- setPdfViewer={this.setPdfViewer}
- addDocument={this.addDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.removeDocument}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- startupLive={true}
- ContentScaling={this.props.scaling}
- sidebarWidth={this.sidebarWidth}
- />
+ <div style={{
+ width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
+ height: `${100 / scale}%`,
+ transform: `scale(${scale})`,
+ position: "absolute",
+ transformOrigin: "top left",
+ top: 0
+ }}>
+ <PDFViewer {...this.props}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ pdf={this._pdf!}
+ url={this.pdfUrl!.url.pathname}
+ isContentActive={this.props.isContentActive}
+ anchorMenuClick={this.anchorMenuClick}
+ loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
+ setPdfViewer={this.setPdfViewer}
+ addDocument={this.addDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.removeDocument}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ startupLive={true}
+ ContentScaling={returnOne}
+ />
+ </div>
<SidebarAnnos ref={this._sidebarRef}
{...this.props}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ setHeight={emptyFunction}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
- isContentActive={this.isContentActive}
/>
{this.settingsPanel()}
</div>;
diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss
index daa620d12..312b51013 100644
--- a/src/client/views/nodes/RadialMenu.scss
+++ b/src/client/views/nodes/RadialMenu.scss
@@ -1,4 +1,4 @@
-@import "../globalCssVariables";
+@import "../global/globalCssVariables";
.radialMenu-cont {
position: absolute;
@@ -53,7 +53,7 @@ s
transition: all .1s;
border-width: .11px;
border-style: none;
- border-color: $intermediate-color; // rgb(187, 186, 186);
+ border-color: $medium-gray; // rgb(187, 186, 186);
// padding: 10px 0px 10px 0px;
white-space: nowrap;
font-size: 13px;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 252c029e4..7ad96bf05 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,11 +1,11 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { Canvas } from '@react-three/fiber';
-import { action, computed, observable, reaction } from "mobx";
+import { action, computed, observable, reaction, trace, runInAction } from "mobx";
import { observer } from "mobx-react";
// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
import { DateField } from "../../../fields/DateField";
-import { Doc, WidthSym } from "../../../fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
@@ -175,8 +175,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
@computed get content() {
if (this.rootDoc.videoWall) return (null);
- const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
- return <video className={"videoBox-content" + interactive} key="video"
+ return <video className={"videoBox-content"} key="video"
ref={r => {
this._videoRef = r;
setTimeout(() => {
@@ -218,16 +217,15 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
// }
return (null);
}
- toggleRecording = action(async () => {
- this._screenCapture = !this._screenCapture;
- if (this._screenCapture) {
+ toggleRecording = async () => {
+ if (!this._screenCapture) {
this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
const aud_chunks: any = [];
this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data);
this._audioRec.onstop = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(aud_chunks);
if (!(result instanceof Error)) {
- this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client));
+ this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
@@ -244,23 +242,29 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey);
this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined;
this.layoutDoc._fitWidth = undefined;
- this.dataDoc[this.props.fieldKey] = new VideoField(Utils.prepend(result.accessPaths.agnostic.client));
+ this.dataDoc[this.props.fieldKey] = new VideoField(result.accessPaths.agnostic.client);
} else alert("video conversion failed");
};
this._audioRec.start();
this._videoRec.start();
- this.dataDoc.mediaState = "recording";
+ runInAction(() => {
+ this._screenCapture = true;
+ this.dataDoc.mediaState = "recording";
+ });
DocUtils.ActiveRecordings.push(this);
} else {
- this._audioRec.stop();
- this._videoRec.stop();
- this.dataDoc.mediaState = "paused";
+ this._audioRec?.stop();
+ this._videoRec?.stop();
+ runInAction(() => {
+ this._screenCapture = false;
+ this.dataDoc.mediaState = "paused";
+ });
const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
CaptureManager.Instance.open(this.rootDoc);
}
- });
+ }
setupDictation = () => {
if (this.dataDoc[this.fieldKey + "-dictation"]) return;
@@ -275,7 +279,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.dataDoc[this.fieldKey + "-dictation"] = dictationText;
}
contentFunc = () => [this.threed, this.content];
- videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth();
+ videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], this.layoutDoc[WidthSym]()) * this.props.PanelWidth();
formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
render() {
TraceMobx();
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f593f74fb..cdd36eb3b 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -49,63 +49,28 @@
// pointer-events: all;
// }
-.videoBox-time{
- color : white;
- top :25px;
- left : 25px;
+.videoBox-ui {
position: absolute;
- background-color: rgba(50, 50, 50, 0.2);
- transform-origin: left top;
- pointer-events:all;
+ flex-direction: row;
+ right: 5px;
+ top: 5px;
+ display: none;
+ background-color: rgba(0, 0, 0, 0.6);
}
-.videoBox-snapshot{
+.videoBox-time, .videoBox-snapshot, .videoBox-timelineButton, .videoBox-play, .videoBox-full {
color : white;
- top :25px;
- right : 25px;
- position: absolute;
- background-color:rgba(50, 50, 50, 0.2);
+ position: relative;
transform-origin: left top;
pointer-events:all;
- cursor: default;
-}
-
-.videoBox-timelineButton {
- position: absolute;
- display: flex;
- align-items: center;
- z-index: 1010;
- bottom: 5px;
- right: 5px;
- color: white;
+ padding-right: 5px;
cursor: pointer;
- background: dimgrey;
- width: 20px;
- height: 20px;
-}
-.videoBox-play {
- width: 25px;
- height: 20px;
- bottom: 25px;
- left : 25px;
- position: absolute;
- color : white;
- background-color: rgba(50, 50, 50, 0.2);
- border-radius: 4px;
- text-align: center;
- transform-origin: left bottom;
- pointer-events:all;
+ &:hover {
+ background-color: gray;
+ }
}
-.videoBox-full {
- width: 25px;
- height: 20px;
- bottom: 25px;
- right : 25px;
- position: absolute;
- color : white;
- background-color: rgba(50, 50, 50, 0.2);
- border-radius: 4px;
- text-align: center;
- transform-origin: right bottom;
- pointer-events:all;
+.videoBox:hover {
+ .videoBox-ui {
+ display: flex;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 263fd5a19..484dec7e2 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -9,7 +9,7 @@ import { InkTool } from "../../../fields/InkField";
import { makeInterface } from "../../../fields/Schema";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { AudioField, nullAudio, VideoField } from "../../../fields/URLField";
-import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils, returnFalse } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -28,6 +28,8 @@ import { LinkDocPreview } from "./LinkDocPreview";
import "./VideoBox.scss";
import { DragManager } from "../../util/DragManager";
import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Tooltip } from "@material-ui/core";
const path = require('path');
type VideoDocument = makeInterface<[typeof documentSchema]>;
@@ -49,7 +51,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _playRegionTimer: any = null;
private _playRegionDuration = 0;
- @observable static _showControls: boolean;
+ @observable static _nativeControls: boolean;
@observable _marqueeing: number[] | undefined;
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _screenCapture = false;
@@ -75,10 +77,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined) || this.rootDoc;
}
- choosePath(url: string) {
- return url.indexOf(window.location.origin) === -1 ? Utils.CorsProxy(url) : url;
- }
-
videoLoad = () => {
const aspect = this.player!.videoWidth / this.player!.videoHeight;
Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
@@ -129,7 +127,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.updateTimecode();
}
- @action public FullScreen() {
+ @action public FullScreen = () => {
this._fullScreen = true;
this.player && this.player.requestFullscreen();
try {
@@ -141,7 +139,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action public Snapshot(downX?: number, downY?: number) {
const width = (this.layoutDoc._width || 0);
- const height = (this.layoutDoc._height || 0);
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1);
@@ -182,8 +179,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
}
- private createRealSummaryLink = (relative: string, downX?: number, downY?: number) => {
- const url = this.choosePath(Utils.prepend(relative));
+ private createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => {
+ const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath;
const width = this.layoutDoc._width || 1;
const height = this.layoutDoc._height || 0;
const imageSummary = Docs.Create.ImageDocument(url, {
@@ -212,11 +209,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
componentDidMount() {
this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
- this._disposers.selection = reaction(() => this.props.isSelected(),
- selected => !selected && setTimeout(() => {
- Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.clear();
- }));
this._disposers.triggerVideo = reaction(
() => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
time => time !== undefined && setTimeout(() => {
@@ -285,10 +277,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (field) {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
- subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
- subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
- subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
- subitems.push({
+ subitems.push({ description: "Full Screen", event: this.FullScreen, icon: "expand" });
+ subitems.push({ description: "Take Snapshot", event: this.Snapshot, icon: "expand-arrows-alt" });
+ this.rootDoc.type === DocumentType.SCREENSHOT && subitems.push({
description: "Screen Capture", event: (async () => {
runInAction(() => this._screenCapture = !this._screenCapture);
this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
@@ -296,6 +287,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
});
subitems.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + " play when link is selected", event: () => this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks, icon: "expand-arrows-alt" });
subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors, icon: "expand-arrows-alt" });
+ subitems.push({ description: "Toggle Native Controls", event: action(() => VideoBox._nativeControls = !VideoBox._nativeControls), icon: "expand-arrows-alt" });
+ subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
}
}
@@ -314,12 +307,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div key="loading">Loading</div> :
- <div className="container" key="container" style={{ pointerEvents: this._isAnyChildContentActive || this.isContentActive() ? "all" : "none" }}>
+ <div className="container" key="container" style={{ mixBlendMode: "multiply", pointerEvents: this.props.isContentActive() ? "all" : "none" }}>
<div className={`${style}`} style={{ width: "100%", height: "100%", left: "0px" }}>
<video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
style={{ height: "100%", width: "auto", display: "flex", margin: "auto" }}
onCanPlay={this.videoLoad}
- controls={VideoBox._showControls}
+ controls={VideoBox._nativeControls}
onPlay={() => this.Play()}
onSeeked={this.updateTimecode}
onPause={() => this.Pause()}
@@ -328,7 +321,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
Not supported.
</video>
{!this.audiopath || this.audiopath === field.url.href ? (null) :
- <audio ref={this.setAudioRef} className={`audiobox-control${this.isContentActive() ? "-interactive" : ""}`}>
+ <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
<source src={this.audiopath} type="audio/mpeg" />
Not supported.
</audio>}
@@ -384,26 +377,36 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
private get uIButtons() {
const curTime = (this.layoutDoc._currentTimecode || 0);
- return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} >
- <span>{"" + formatTime(curTime)}</span>
- <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
- </div>,
- <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshotDown} >
- <FontAwesomeIcon icon="camera" size="lg" />
- </div>,
- <div className="videoBox-timelineButton" key="timeline" onPointerDown={this.onTimelineHdlDown} style={{ bottom: `${100 - this.heightPercent}%` }}>
- <FontAwesomeIcon icon={"eye"} size="lg" />
- </div>,
- VideoBox._showControls ? (null) : [
- // <div className="control-background">
- <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} >
- <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
- </div>,
- <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} >
- F
- {/* </div> */}
- </div>
- ]]);
+ const nonNativeControls = [
+ <Tooltip title={<div className="dash-tooltip">{"playback"}</div>} key="play" placement="bottom">
+ <div className="videoBox-play" onPointerDown={this.onPlayDown} >
+ <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
+ </div>
+ </Tooltip>,
+ <Tooltip title={<div className="dash-tooltip">{"timecode"}</div>} key="time" placement="bottom">
+ <div className="videoBox-time" onPointerDown={this.onResetDown} >
+ <span>{formatTime(curTime)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.floor((curTime - Math.trunc(curTime)) * 100).toString().padStart(2, "0")}</span>
+ </div>
+ </Tooltip>,
+ <Tooltip title={<div className="dash-tooltip">{"view full screen"}</div>} key="full" placement="bottom">
+ <div className="videoBox-full" onPointerDown={this.FullScreen}>
+ <FontAwesomeIcon icon="expand" size="lg" />
+ </div>
+ </Tooltip>];
+ return <div className="videoBox-ui">
+ {[...(VideoBox._nativeControls ? [] : nonNativeControls),
+ <Tooltip title={<div className="dash-tooltip">{"snapshot current frame"}</div>} key="snap" placement="bottom">
+ <div className="videoBox-snapshot" onPointerDown={this.onSnapshotDown} >
+ <FontAwesomeIcon icon="camera" size="lg" />
+ </div>
+ </Tooltip>,
+ <Tooltip title={<div className="dash-tooltip">{"show annotation timeline"}</div>} key="timeline" placement="bottom">
+ <div className="videoBox-timelineButton" onPointerDown={this.onTimelineHdlDown}>
+ <FontAwesomeIcon icon="eye" size="lg" />
+ </div>
+ </Tooltip>,]}
+ </div>;
}
onPlayDown = () => this._playing ? this.Pause() : this.Play();
@@ -426,7 +429,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
setupMoveUpEvents(this, e,
action((e: PointerEvent) => {
this._clicking = false;
- if (this.isContentActive()) {
+ if (this.props.isContentActive()) {
const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
}
@@ -435,7 +438,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
() => {
this.layoutDoc._timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
setTimeout(action(() => this._clicking = false), 500);
- }, this.isContentActive(), this.isContentActive());
+ }, this.props.isContentActive(), this.props.isContentActive());
});
onResetDown = (e: React.PointerEvent) => {
@@ -457,7 +460,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
onPointerLeave={this.updateTimecode}
onLoad={this.youtubeIframeLoaded} className={`${style}`} width={Doc.NativeWidth(this.layoutDoc) || 640} height={Doc.NativeHeight(this.layoutDoc) || 390}
- src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
+ src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`} />;
}
@action.bound
@@ -526,12 +529,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
playFrom={this.playFrom}
setTime={this.setAnchorTime}
playing={this.playing}
+ isAnyChildContentActive={this.isAnyChildContentActive}
whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged}
removeDocument={this.removeDocument}
ScreenToLocalTransform={this.timelineScreenToLocal}
Play={this.Play}
Pause={this.Pause}
- isContentActive={this.isContentActive}
playLink={this.playLink}
PanelHeight={this.timelineHeight}
/>
@@ -542,9 +545,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <div className="imageBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
}
- marqueeDown = action((e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true)) this._marqueeing = [e.clientX, e.clientY];
- });
+ marqueeDown = (e: React.PointerEvent) => {
+ if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ setupMoveUpEvents(this, e, action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ }
+ }
finishMarquee = action(() => {
this._marqueeing = undefined;
@@ -561,7 +570,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
marqueeFitScaling = () => (this.props.scaling?.() || 1) * this.heightPercent / 100;
marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0];
- timelineDocFilter = () => ["_timelineLabel:true:x"];
+ timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`];
render() {
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
@@ -583,7 +592,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ScreenToLocalTransform={this.screenToLocalTransform}
docFilters={this.timelineDocFilter}
select={emptyFunction}
- isContentActive={this.isContentActive}
scaling={returnOne}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
@@ -614,4 +622,4 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
}
-VideoBox._showControls = true; \ No newline at end of file
+VideoBox._nativeControls = false; \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index ca82c049c..19b69ff5a 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,4 +1,4 @@
-@import "../globalCssVariables.scss";
+@import "../global/globalCssVariables.scss";
.webBox {
@@ -17,6 +17,7 @@
justify-content: center;
border-radius: 3px;
pointer-events: all;
+ z-index: 1; // so it appears on top of the document's title, if shown
}
.pdfViewerDash-dragAnnotationBox {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index cb7e58559..e4d4557af 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -13,7 +13,7 @@ import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
+import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils, returnEmptyString, returnEmptyFilter } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -43,7 +43,8 @@ const WebDocument = makeInterface(documentSchema);
@observer
export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
+ public static openSidebarWidth = 250;
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -54,12 +55,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _scrollTimer: any;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private _marqueeing: number[] | undefined;
- @observable private _url: string = "hello";
@observable private _isAnnotating = false;
@observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable private _scrollHeight = 1500;
+ @computed get _url() { return this.webField?.toString() || ""; }
+ @computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; }
@computed get scrollHeight() { return this._scrollHeight; }
@computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
@computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
@@ -67,7 +69,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
constructor(props: any) {
super(props);
- if (this.webField) {
+ if (true) {// his.webField) {
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
@@ -80,17 +82,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
runInAction(() => {
- this._url = this.webField?.toString() || "";
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
+ this._annotationKeySuffix = () => this._urlHash + "-annotations";
// bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created)
- this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-annotations-"+urlHash(this["${this.fieldKey}"]?.url?.toString()))`);
+ this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`);
+ this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`);
});
- this._disposers.selection = reaction(() => this.props.isSelected(),
- selected => !selected && setTimeout(() => {
- Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.clear();
- }));
+ this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight,
+ autoHeight => {
+ if (autoHeight) {
+ this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
+ this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+ }
+ });
if (this.webField?.href.indexOf("youtube") !== -1) {
const youtubeaspect = 400 / 315;
@@ -159,9 +163,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
scrollFocus = (doc: Doc, smooth: boolean) => {
+ if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
+ if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+ this.toggleSidebar(!smooth);
+ }
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
- const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1);
if (scrollTo !== undefined) {
const focusSpeed = smooth ? 500 : 0;
this._initialScroll !== undefined && (this._initialScroll = scrollTo);
@@ -169,17 +178,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return focusSpeed;
}
}
- this._initialScroll = NumCast(doc.y);
+ this._initialScroll = NumCast(this.layoutDoc._scrollTop);
return 0;
}
getAnchor = () => {
- const anchor = Docs.Create.TextanchorDocument({
- title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
- y: NumCast(this.layoutDoc._scrollTop),
- });
- this.addDocument(anchor);
+ const anchor =
+ AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
+ Docs.Create.WebanchorDocument(this._url, {
+ title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
+ annotationOn: this.rootDoc,
+ y: NumCast(this.layoutDoc._scrollTop)
+ });
+ this.addDocumentWrapper(anchor);
return anchor;
}
@@ -202,6 +213,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
+ this._setPreviewCursor?.(e.clientX, e.clientY, false, true);
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
if (word) {
@@ -229,8 +242,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown);
this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
- if (this._initialScroll !== undefined && this._outerRef.current) {
- this._outerRef.current.scrollTop = this._initialScroll;
+ const initialScroll = this._initialScroll;
+ if (initialScroll !== undefined && this._outerRef.current) {
+ // bcz: not sure why this happens, but if the webpage isn't ready yet, it's scroll height seems to be limited. So we need to wait tp set scroll location to what we want.
+ setTimeout(() => this._outerRef.current!.scrollTop = initialScroll);
this._initialScroll = undefined;
}
iframe.setAttribute("enable-annotation", "true");
@@ -291,9 +306,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
if (future.length) {
- history.push(this._url);
- this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
+ this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]);
+ this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!));
return true;
}
return false;
@@ -305,9 +319,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
if (history.length) {
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
- else future.push(this._url);
- this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
+ else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]);
+ this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
return true;
}
return false;
@@ -318,25 +331,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
@action
- submitURL = (newUrl?: string) => {
+ submitURL = (newUrl?: string, preview?: boolean) => {
if (!newUrl) return;
if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
try {
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"));
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"));
const url = this.webField?.toString();
- if (url) {
- if (history === undefined) {
- this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
- } else {
- history.push(url);
- }
+ if (url && !preview) {
+ this.dataDoc[this.fieldKey + "-history"] = new List<string>([...(history || []), url]);
this.layoutDoc._scrollTop = 0;
future && (future.length = 0);
}
- this._url = newUrl;
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
- this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
+ if (!preview) this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
} catch (e) {
console.log("WebBox URL error:" + this._url);
}
@@ -395,16 +402,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.isContentActive(true)) {
- this._marqueeing = [e.clientX, e.clientY];
- this.props.select(false);
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ setupMoveUpEvents(this, e, action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
}
}
@action finishMarquee = (x?: number, y?: number) => {
this._marqueeing = undefined;
this._isAnnotating = false;
this._iframeClick = undefined;
- x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false);
+ x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
}
@computed
@@ -414,7 +424,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (field instanceof HtmlField) {
view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
+ const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._url) : this._url;
view = <iframe className="webBox-iframe" enable-annotation={"true"}
style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
@@ -429,9 +439,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return view;
}
+ addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => doc.webUrl = this._url);
+ return this.addDocument(doc, annotationKey);
+ }
+
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
- return this.addDocument(doc, sidebarKey);
+ return this.addDocumentWrapper(doc, sidebarKey);
}
sidebarBtnDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
@@ -445,20 +460,32 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
}
return false;
- }, emptyFunction, this.toggleSidebar);
+ }, emptyFunction, () => this.toggleSidebar());
}
- toggleSidebar = action(() => {
+ @observable _previewNativeWidth: Opt<number> = undefined;
+ @observable _previewWidth: Opt<number> = undefined;
+ toggleSidebar = action((preview: boolean = false) => {
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
+ const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ if (preview) {
+ this._previewNativeWidth = nativeWidth * ratio;
+ this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this._showSidebar = true;
+ }
+ else {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
});
- sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+ sidebarWidth = () => !this.SidebarShown ? 0 :
+ this._previewWidth ? WebBox.openSidebarWidth :
+ (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() /
+ NumCast(this.layoutDoc.nativeWidth)
@computed get content() {
- return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+ return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / (this.props.scaling?.() || 1)}%`, }}>
{this.urlContent}
</div>;
@@ -471,60 +498,68 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
}
</div>;
+
}
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+ transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
+ opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
render() {
- const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
- const scale = this.props.scaling?.() || 1;
+ const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined;
+ const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+ const scale = previewScale * (this.props.scaling?.() || 1);
+ const renderAnnotations = (docFilters?: () => string[]) =>
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ renderDepth={this.props.renderDepth + 1}
+ isAnnotationOverlay={true}
+ fieldKey={this.annotationKey}
+ CollectionView={undefined}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ ScreenToLocalTransform={this.scrollXf}
+ scaling={returnOne}
+ dropAction={"alias"}
+ docFilters={docFilters || this.props.docFilters}
+ dontRenderDocuments={docFilters ? false : true}
+ select={emptyFunction}
+ ContentScaling={returnOne}
+ bringToFront={emptyFunction}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.sidebarAddDocument}
+ childPointerEvents={true}
+ pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
return (
- <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.isContentActive() ? "all" : this.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
- <div className={`webBox-container`}
- style={{ pointerEvents: inactiveLayer ? "none" : undefined }}
- onContextMenu={this.specificContextMenu}>
+ <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.props.isContentActive() ? "all" : this.props.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
+ <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
<base target="_blank" />
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
- width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale}px)`,
+ width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
height: `${100 / scale}%`,
transform: `scale(${scale})`,
- pointerEvents: inactiveLayer ? "none" : undefined
+ pointerEvents
}}
onWheel={e => { e.stopPropagation(); e.preventDefault(); }} // block wheel events from propagating since they're handled by the iframe
onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
onPointerDown={this.onMarqueeDown}
>
- <div className={"webBox-innerContent"} style={{
- height: NumCast(this.scrollHeight, 50),
- pointerEvents: inactiveLayer ? "none" : undefined
- }}>
+ <div className={"webBox-innerContent"} style={{ height: NumCast(this.scrollHeight, 50), pointerEvents }}>
{this.content}
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- isAnnotationOverlay={true}
- fieldKey={this.annotationKey}
- setPreviewCursor={this.setPreviewCursor}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- dropAction={"alias"}
- select={emptyFunction}
- isContentActive={returnFalse}
- ContentScaling={returnOne}
- bringToFront={emptyFunction}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.sidebarAddDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.scrollXf}
- renderDepth={this.props.renderDepth + 1}
- scaling={returnOne}
- childPointerEvents={true}
- pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />
+ <div style={{ mixBlendMode: "multiply" }}>
+ {renderAnnotations(this.transparentFilter)}
+ </div>
+ {renderAnnotations(this.opaqueFilter)}
+ {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
{this.annotationLayer}
</div>
</div>
@@ -535,7 +570,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
anchorMenuClick={this.anchorMenuClick}
scrollTop={0}
down={this._marqueeing} scaling={returnOne}
- addDocument={this.addDocument}
+ addDocument={this.addDocumentWrapper}
docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
savedAnnotations={this._savedAnnotations}
@@ -544,17 +579,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</div >
<SidebarAnnos ref={this._sidebarRef}
{...this.props}
- fieldKey={this.annotationKey}
+ fieldKey={this.fieldKey + "-" + this._urlHash}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ setHeight={emptyFunction}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
- isContentActive={this.isContentActive}
/>
<button className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
- style={{ display: !this.isContentActive() ? "none" : undefined }}
+ style={{ display: !this.props.isContentActive() ? "none" : undefined }}
onPointerDown={this.sidebarBtnDown} >
<FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" />
</button>
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index e16036000..e7dd286a5 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,6 +1,7 @@
.dashFieldView {
position: relative;
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
.dashFieldView-enumerables {
width: 10px;
@@ -13,6 +14,8 @@
min-width: 12px;
position: relative;
display: inline-block;
+ margin: 0;
+ transform: scale(0.7);
background-color: rgba(155, 155, 155, 0.24);
}
.dashFieldView-labelSpan {
@@ -22,11 +25,11 @@
background: rgba(0,0,0,0.1);
}
.dashFieldView-fieldSpan {
- min-width: 20px;
+ min-width: 8px;
margin-left: 2px;
margin-right: 5px;
- position: relative;
- display: inline;
+ padding-left: 2px;
+ display: inline-block;
background-color: rgba(155, 155, 155, 0.24);
font-weight: bold;
span {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 53aceb533..3cedab1a4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import "../../globalCssVariables";
+@import "../../global/globalCssVariables";
.ProseMirror {
width: 100%;
@@ -31,7 +31,7 @@ audiotag:hover {
padding: 0;
border-width: 0px;
border-radius: inherit;
- border-color: $intermediate-color;
+ border-color: $medium-gray;
box-sizing: border-box;
background-color: inherit;
border-style: solid;
@@ -363,7 +363,7 @@ footnote::after {
@media only screen and (max-width: 1000px) {
- @import "../../globalCssVariables";
+ @import "../../global/globalCssVariables";
.ProseMirror {
width: 100%;
@@ -381,7 +381,7 @@ footnote::after {
padding: 0;
border-width: 0px;
border-radius: inherit;
- border-color: $intermediate-color;
+ border-color: $medium-gray;
box-sizing: border-box;
background-color: inherit;
border-style: solid;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 911ec1560..4b1d76d00 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { ReplaceStep } from 'prosemirror-transform';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
+import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -71,7 +71,8 @@ export interface FormattedTextBoxProps {
xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yPadding?: number;
noSidebar?: boolean;
- dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded
+ dontScale?: boolean;
+ dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
}
export const GoogleRef = "googleDocId";
@@ -119,13 +120,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public get EditorView() { return this._editorView; }
public get SidebarKey() { return this.fieldKey + "-sidebar"; }
- @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
+ @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
@computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
@computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; }
@computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
@computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
- @computed get sidebarHeight() { return NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
+ @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
@computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
+ @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); }
@computed get _recording() { return this.dataDoc?.mediaState === "recording"; }
set _recording(value) {
!this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined);
@@ -215,6 +217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
return undefined;
});
+ AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
/**
* This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
@@ -251,7 +254,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) {
const accumTags = [] as string[];
state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
@@ -368,7 +371,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
const anchor = Docs.Create.TextanchorDocument();
const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
- const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }];
const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location });
tr = tr.addMark(flattened[i].from, flattened[i].to, link);
});
@@ -429,6 +432,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.ProseRef = ele;
this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
}
@undoBatch
@@ -532,7 +536,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
- setTimeout(() => this.updateHighlights());
+ setTimeout(this.updateHighlights);
}
if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
@@ -541,11 +545,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+
@action
- toggleSidebar = () => {
+ toggleSidebar = (preview: boolean = false) => {
const prevWidth = this.sidebarWidth();
- this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
- this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ if (preview) this._showSidebar = true;
+ else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+
+ this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
}
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
@@ -702,7 +711,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) });
- const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+ const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
@@ -723,6 +732,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+ if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) {
+ this.toggleSidebar(!smooth);
+ }
const textAnchorId = textAnchor[Id];
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
@@ -780,12 +792,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.rootDoc[this.fieldKey + "-height"] = scrollHeight + this.titleHeight;
+ this.rootDoc[this.fieldKey + "-height"] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
}
componentDidMount() {
- this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+ !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
@@ -793,8 +805,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight)
);
this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight }),
- ({ sidebarHeight, textHeight, autoHeight }) => autoHeight && this.props.setHeight(Math.max(sidebarHeight, textHeight)));
+ () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
+ ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => autoHeight && this.props.setHeight(marginsHeight + Math.max(sidebarHeight, textHeight)));
this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
@@ -871,12 +883,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
},
);
- if (this._recording) setTimeout(() => this.recordDictation());
+ if (this._recording) setTimeout(this.recordDictation);
}
var quickScroll: string | undefined = "";
this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
pos => {
- if (!this._ignoreScroll && this._scrollRef.current) {
+ if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -1039,7 +1051,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
+ const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
@@ -1199,7 +1211,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if ((e.nativeEvent as any).formattedHandled) {
console.log("handled");
}
- if (!(e.nativeEvent as any).formattedHandled && this.isContentActive(true)) {
+ if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) {
const editor = this._editorView!;
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
!this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
@@ -1391,6 +1403,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._rules!.EnteringStyle = false;
}
e.stopPropagation();
+ for (var i = state.selection.from; i < state.selection.to; i++) {
+ const node = state.doc.resolve(i);
+ if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
+ mark.attrs.userid !== Doc.CurrentUserEmail) &&
+ [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+ e.preventDefault();
+ }
+ }
switch (e.key) {
case "Escape":
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
@@ -1413,23 +1433,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onScroll = (e: React.UIEvent) => {
if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
- this._ignoreScroll = true;
- this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
- this._ignoreScroll = false;
+ if (!this.props.dontSelectOnLoad) {
+ this._ignoreScroll = true;
+ this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
+ this._ignoreScroll = false;
+ }
}
}
- tryUpdateScrollHeight() {
+ tryUpdateScrollHeight = () => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- setTimeout(() => { // bcz: don't know why this is needed, but without it, the size of the textbox is too big as it includes the size of the title header. after the timeout, the size seems to get computed correctly.
- const proseHeight = this.ProseRef?.scrollHeight || 0;
- const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+ const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+ const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
+ if (children) {
+ var proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ var scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
+ setTimeout(() => {
+ proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+ scrollHeight && setScrollHeight();
+ }, 10);
} else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
}
- });
+ }
}
}
fitToBox = () => this.props.Document._fitToBox;
@@ -1452,7 +1481,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@computed get sidebarHandle() {
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
- return (!annotated && !this.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ return (!annotated && !this.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
style={{
left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
@@ -1464,15 +1493,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return ComponentTag === CollectionStackingView ?
<SidebarAnnos ref={this._sidebarRef}
{...this.props}
- fieldKey={this.annotationKey}
+ fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
PanelWidth={this.sidebarWidth}
+ setHeight={this.setSidebarHeight}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
- isContentActive={this.isContentActive}
/> :
<ComponentTag
{...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
@@ -1485,7 +1516,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
scaleField={this.SidebarKey + "-scale"}
isAnnotationOverlay={false}
select={emptyFunction}
- isContentActive={this.isContentActive}
scaling={this.sidebarContentScaling}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.sidebarRemDocument}
@@ -1507,7 +1537,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
render() {
TraceMobx();
const selected = this.props.isSelected();
- const active = this.isContentActive();
+ const active = this.props.isContentActive();
const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
@@ -1517,16 +1547,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const selPad = Math.min(margins, 10);
const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0);
const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
- const col = this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
- const back = this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
return (
<div className="formattedTextBox-cont"
- onWheel={e => this.isContentActive() && e.stopPropagation()}
+ onWheel={e => this.props.isContentActive() && e.stopPropagation()}
style={{
- transform: `scale(${scale})`,
- transformOrigin: "top left",
- width: `${100 / scale}%`,
- height: `${100 / scale}%`,
+ transform: this.props.dontScale ? undefined : `scale(${scale})`,
+ transformOrigin: this.props.dontScale ? undefined : "top left",
+ width: this.props.dontScale ? undefined : `${100 / scale}%`,
+ height: this.props.dontScale ? undefined : `${100 / scale}%`,
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
...this.styleFromLayoutString(scale) // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
}}>
@@ -1554,7 +1582,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
>
<div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
style={{
- width: `calc(100% - ${this.sidebarWidthPercent})`,
+ width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`,
pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
}}
@@ -1566,8 +1594,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
- {(this.props.noSidebar || this.Document._noSidebar) || this.Document._singleLine ? (null) : this.sidebarHandle}
+ {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
+ {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle}
{!this.layoutDoc._showAudio ? (null) : this.audioHandle}
</div>
</div >
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index d5c77786c..76a5675de 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,13 +7,14 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "../../../util/SelectionManager";
import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast } from "../../../../fields/Doc";
+import { Doc, DataSym, DocListCast, AclAugment, AclSelfEdit } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
import { Docs } from "../../../documents/Documents";
import { Utils } from "../../../../Utils";
import { listSpec } from "../../../../fields/Schema";
import { List } from "../../../../fields/List";
+import { GetEffectiveAcl } from "../../../../fields/util";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -70,25 +71,42 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
};
+ const canEdit = (state: any) => {
+ switch (GetEffectiveAcl(props.Document)) {
+ case AclAugment: return false;
+ case AclSelfEdit:
+ for (var i = state.selection.from; i < state.selection.to; i++) {
+ const marks = state.doc.resolve(i)?.marks?.();
+ if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) {
+ return false;
+ }
+ }
+ break;
+ }
+ return true;
+ };
+
+ const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+
//History commands
bind("Mod-z", undo);
bind("Shift-Mod-z", redo);
!mac && bind("Mod-y", redo);
//Commands to modify Mark
- bind("Mod-b", toggleMark(schema.marks.strong));
- bind("Mod-B", toggleMark(schema.marks.strong));
+ bind("Mod-b", toggleEditableMark(schema.marks.strong));
+ bind("Mod-B", toggleEditableMark(schema.marks.strong));
- bind("Mod-e", toggleMark(schema.marks.em));
- bind("Mod-E", toggleMark(schema.marks.em));
+ bind("Mod-e", toggleEditableMark(schema.marks.em));
+ bind("Mod-E", toggleEditableMark(schema.marks.em));
- bind("Mod-*", toggleMark(schema.marks.code));
+ bind("Mod-*", toggleEditableMark(schema.marks.code));
- bind("Mod-u", toggleMark(schema.marks.underline));
- bind("Mod-U", toggleMark(schema.marks.underline));
+ bind("Mod-u", toggleEditableMark(schema.marks.underline));
+ bind("Mod-U", toggleEditableMark(schema.marks.underline));
//Commands for lists
- bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
+ bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch as any));
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
/// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
@@ -96,6 +114,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (!props.LayoutTemplateString) return addTextBox(false, true);
return true;
}
+ if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -121,6 +140,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
/// bcz; Argh!! replace with a onShiftTab prop conditionally handles Tab);
if (props.Document._singleLine) return true;
+ if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
@@ -140,24 +160,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
//Commands to modify BlockType
- bind("Ctrl->", wrapIn(schema.nodes.blockquote));
- bind("Alt-\\", setBlockType(schema.nodes.paragraph));
- bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+ bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state, dispatch as any)));
+ bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch as any));
+ bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch as any));
- bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })));
- });
+ bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+ bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch as any));
}
//Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
- return true;
- });
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
//Command to unselect all
bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -173,13 +188,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
//Command to create a text document to the right of the selected textbox
- bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => addTextBox(false, true));
+ bind("Alt-Enter", () => addTextBox(false, true));
//Command to create a text document to the bottom of the selected textbox
- bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => addTextBox(true, true));
+ bind("Ctrl-Enter", () => addTextBox(true, true));
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ if (!canEdit(state)) return true;
+
if (!deleteSelection(state, (tx: Transaction<Schema<any, any>>) => {
dispatch(updateBullets(tx, schema));
})) {
@@ -200,6 +217,9 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//command to break line
bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
if (addTextBox(true, false)) return true;
+
+ if (!canEdit(state)) return true;
+
const trange = state.selection.$from.blockRange(state.selection.$to);
const path = (state.selection.$from as any).path;
const depth = trange ? liftTarget(trange) : undefined;
@@ -238,18 +258,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a blank space
bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
- bind("Alt-ArrowUp", joinUp);
- bind("Alt-ArrowDown", joinDown);
- bind("Mod-BracketLeft", lift);
+ bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
+ bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
+ bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+ canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
return true;
}
return false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 1d24d6833..c94e93541 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -1,4 +1,4 @@
-@import "../../globalCssVariables";
+@import "../../global/globalCssVariables";
.button-dropdown-wrapper {
position: relative;
@@ -24,7 +24,7 @@
top: 35px;
left: 0;
background-color: #323232;
- color: $light-color-secondary;
+ color: $light-gray;
border: 1px solid #4d4d4d;
border-radius: 0 6px 6px 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 071491463..82ad2b7db 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -352,7 +352,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
function onClick(e: React.PointerEvent) {
e.preventDefault();
e.stopPropagation();
- self.TextView.endUndoTypingBatch();
+ self.TextView?.endUndoTypingBatch();
UndoManager.RunInBatch(() => {
self.view && command && command(self.view.state, self.view.dispatch, self.view);
self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
@@ -821,8 +821,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (link) {
const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ if (href.indexOf(Doc.localServerPath()) === 0) {
+ const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0];
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
@@ -852,6 +852,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
+ console.log((this.view as any)?.TextView);
}
@undoBatch
@@ -863,8 +864,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const allAnchors = linkAnchor.attrs.allAnchors.slice();
this.TextView.RemoveAnchorFromSelection(allAnchors);
// bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
- allAnchors.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
- const anchorId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => {
+ const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0];
anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
});
}
@@ -963,7 +964,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.createHighlighterButton(),
this.createLinkButton(),
this.createBrushButton(),
- <div className="richTextMenu-divider" key="divider 2" />,
+ <div className="collectionMenu-divider" key="divider 2" />,
this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
@@ -976,7 +977,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const row2 = <div className="antimodeMenu-row row-2" key="row2">
{this.collapsed ? this.getDragger() : (null)}
<div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
- <div className="richTextMenu-divider" key="divider 3" />
+ <div className="collectionMenu-divider" key="divider 3" />
{[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
this.activeFontSize = val;
SelectionManager.Views().map(dv => dv.props.Document._fontSize = val);
@@ -985,12 +986,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeFontFamily = val;
SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val);
})),
- <div className="richTextMenu-divider" key="divider 4" />,
+ <div className="collectionMenu-divider" key="divider 4" />,
this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
- this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
- <div className="richTextMenu-divider" key="divider 5" />,]}
+ this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
+ ]}
</div>
{/* <div key="collapser">
{<div key="collapser">
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index 0e4b752ac..8c4d77da9 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -1,4 +1,4 @@
-@import "../views/globalCssVariables";
+@import "../views/global/globalCssVariables";
.ProseMirror-menu-dropdown-wrap {
display: inline-block;
position: relative;
@@ -50,7 +50,7 @@
padding: 3px;
&:hover {
- background-color: $light-color-secondary;
+ background-color: $light-gray;
}
}
}
@@ -294,9 +294,9 @@
top: 31px;
background-color: #323232;
border: 1px solid #4d4d4d;
- color: $light-color-secondary;
+ color: $light-gray;
// border: none;
- // border: 1px solid $light-color-secondary;
+ // border: 1px solid $light-gray;
border-radius: 0 6px 6px 6px;
padding: 3px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
@@ -323,7 +323,7 @@
}
.separated-button {
- border-top: 1px solid $light-color-secondary;
+ border-top: 1px solid $light-gray;
padding-top: 6px;
}
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 1ba86232b..06932d145 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1,7 +1,4 @@
-$light-blue: #AEDDF8;
-$dark-blue: #5B9FDD;
-$light-background: #ececec;
-$dark-grey: #656565;
+@import "../../global/globalCssVariables";
.presBox-cont {
cursor: auto;
@@ -10,7 +7,6 @@ $dark-grey: #656565;
pointer-events: inherit;
z-index: 2;
font-family: Roboto;
- box-shadow: #AAAAAA .2vw .2vw .4vw;
width: 100%;
min-width: 20px;
height: 100%;
@@ -47,8 +43,8 @@ $dark-grey: #656565;
align-items: center;
height: 30px;
width: 100%;
- color: white;
- background-color: #323232;
+ color: $white;
+ background-color: $dark-gray;
.toolbar-button {
cursor: pointer;
@@ -110,7 +106,7 @@ $dark-grey: #656565;
}
.toolbar-divider {
- border-left: solid #ffffff70 0.5px;
+ border-left: solid $medium-gray 0.5px;
height: 20px;
}
}
@@ -118,7 +114,7 @@ $dark-grey: #656565;
.dropdown {
font-size: 10;
margin-left: 5px;
- color: darkgrey;
+ color: $medium-gray;
transition: 0.5s ease;
}
@@ -174,7 +170,7 @@ $dark-grey: #656565;
.ribbon-colorBox {
cursor: pointer;
- border: solid 1px black;
+ border: solid 1px $black;
display: flex;
margin-left: 5px;
margin-top: 5px;
@@ -191,9 +187,9 @@ $dark-grey: #656565;
font-size: 11;
font-weight: 200;
height: 20;
- background-color: #ececec;
- color: black;
- border: solid 1px black;
+ background-color: $white;
+ color: $black;
+ border: solid 1px $black;
display: flex;
margin-left: 5px;
margin-top: 5px;
@@ -220,11 +216,11 @@ $dark-grey: #656565;
align-items: center;
height: 100%;
width: 100%;
- background: black;
+ background: $black;
}
.ribbon-propertyUpDownItem:hover {
- background: darkgrey;
+ background: $medium-gray;
transform: scale(1.05);
}
}
@@ -239,7 +235,7 @@ $dark-grey: #656565;
.multiThumb-slider {
display: grid;
- background-color: $light-background;
+ background-color: $white;
height: 10px;
border-radius: 10px;
overflow: hidden;
@@ -257,8 +253,8 @@ $dark-grey: #656565;
-webkit-appearance: none;
height: 10px;
cursor: ew-resize;
- background: $dark-blue;
- box-shadow: -100vw 0 0 100vw $light-background;
+ background: $medium-blue;
+ box-shadow: -100vw 0 0 100vw $white;
}
.toolbar-slider.end::-webkit-slider-thumb {
@@ -267,7 +263,7 @@ $dark-grey: #656565;
-webkit-appearance: none;
height: 10px;
cursor: ew-resize;
- background: $dark-blue;
+ background: $medium-blue;
box-shadow: -100vw 0 0 100vw $light-blue;
}
}
@@ -282,7 +278,7 @@ $dark-grey: #656565;
height: 10px;
border-radius: 10px;
-webkit-appearance: none;
- background-color: $light-background;
+ background-color: $white;
}
.toolbar-slider:focus {
@@ -301,7 +297,7 @@ $dark-grey: #656565;
-webkit-appearance: none;
height: 10px;
cursor: ew-resize;
- background: $dark-blue;
+ background: $medium-blue;
box-shadow: -100vw 0 0 100vw $light-blue;
}
@@ -318,7 +314,7 @@ $dark-grey: #656565;
width: 15px;
min-width: 15px;
cursor: pointer;
- background: $light-background;
+ background: $white;
}
.presBox-checkbox:focus {
@@ -326,7 +322,7 @@ $dark-grey: #656565;
}
.presBox-checkbox:hover {
- background: #c0c0c0;
+ background: $light-gray;
}
.presBox-checkbox:checked {
@@ -381,9 +377,9 @@ $dark-grey: #656565;
text-align: center;
font-size: 16;
width: 90%;
- color: black;
+ color: $black;
transform: translate(5%, 0px);
- border-bottom: solid 2px darkgrey;
+ border-bottom: solid 2px $medium-gray;
}
@@ -396,8 +392,8 @@ $dark-grey: #656565;
justify-self: left;
margin-top: 5px;
padding-left: 10px;
- background-color: $light-background;
- border: solid 1px black;
+ background-color: $white;
+ border: solid 1px $black;
min-width: 80px;
max-width: 200px;
width: 100%;
@@ -416,7 +412,7 @@ $dark-grey: #656565;
}
.ribbon-frameSelector {
- border: black solid 1px;
+ border: $black solid 1px;
width: 60px;
height: 20px;
margin-top: 5px;
@@ -433,12 +429,12 @@ $dark-grey: #656565;
cursor: pointer;
position: relative;
height: 100%;
- background: $light-background;
+ background: $white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
- color: black;
+ color: $black;
}
.numKeyframe {
@@ -446,7 +442,7 @@ $dark-grey: #656565;
font-size: 10;
font-weight: 600;
position: relative;
- color: black;
+ color: $black;
display: flex;
width: 100%;
height: 100%;
@@ -489,7 +485,7 @@ $dark-grey: #656565;
padding-left: 10;
padding-right: 10;
border-radius: 10px;
- background-color: #979797;
+ background-color: $medium-gray;
}
.ribbon-final-button:hover {
@@ -508,13 +504,13 @@ $dark-grey: #656565;
align-items: center;
margin-bottom: 5px;
height: 25px;
- color: lightgrey;
+ color: $light-gray;
width: 100%;
max-width: 120;
padding-left: 10;
padding-right: 10;
border-radius: 10px;
- background-color: black;
+ background-color: $black;
}
.ribbon-final-button-hidden:hover {
@@ -525,15 +521,15 @@ $dark-grey: #656565;
.ribbon-frameList {
width: calc(100% - 5px);
height: 50px;
- background-color: #ececec;
- border: 1px solid #9f9f9f;
+ background-color: $white;
+ border: 1px solid $medium-gray;
grid-template-rows: max-content;
.frameList-header {
display: grid;
width: 100%;
height: 20px;
- background-color: #9f9f9f;
+ background-color: $medium-gray;
.frameList-headerButtons {
display: flex;
@@ -588,7 +584,7 @@ $dark-grey: #656565;
font-size: 10.5;
font-weight: 300;
height: 20;
- background-color: #979797;
+ background-color: $medium-gray;
color: white;
display: flex;
margin-top: 5px;
@@ -607,8 +603,8 @@ $dark-grey: #656565;
transition: all 0.4s;
font-weight: 400;
opacity: 1;
- color: white;
- background-color: black;
+ color: $white;
+ background-color: $black;
}
.ribbon-toggle {
@@ -616,10 +612,10 @@ $dark-grey: #656565;
font-size: 10.5;
font-weight: 200;
height: 20;
- background-color: $light-background;
+ background-color: $white;
border: solid 1px rgba(0, 0, 0, 0.5);
display: flex;
- color: black;
+ color: $black;
margin-top: 5px;
margin-bottom: 5px;
border-radius: 5px;
@@ -660,13 +656,13 @@ $dark-grey: #656565;
position: relative;
font-size: 13;
padding-bottom: 10px;
- border-bottom: solid 1px $dark-grey;
+ border-bottom: solid 1px $dark-gray;
.presBox-dropdown:hover {
- border: solid 1px $dark-blue;
+ border: solid 1px $medium-blue;
.presBox-dropdownIcon {
- color: $dark-blue;
+ color: $medium-blue;
}
}
@@ -675,12 +671,12 @@ $dark-grey: #656565;
display: grid;
grid-template-columns: auto 20%;
position: relative;
- border: solid 1px black;
- background-color: $light-background;
+ border: solid 1px $black;
+ background-color: $light-gray;
border-radius: 5px;
font-size: 10;
height: 25;
- color: black;
+ color: $black;
padding-left: 5px;
align-items: center;
margin-top: 5px;
@@ -744,7 +740,7 @@ $dark-grey: #656565;
height: 100px;
padding-top: 5px;
padding-bottom: 5px;
- border: solid 1px black;
+ border: solid 1px $black;
// overflow: auto;
::-webkit-scrollbar {
@@ -794,7 +790,7 @@ $dark-grey: #656565;
cursor: pointer;
position: relative;
text-align: center;
- border-left: solid 1px darkgrey;
+ border-left: solid 1px $medium-gray;
width: 20%;
height: 100%;
display: flex;
@@ -825,7 +821,7 @@ $dark-grey: #656565;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.8);
z-index: 200;
background-color: white;
- color: black;
+ color: $black;
position: absolute;
overflow: hidden;
}
@@ -841,12 +837,12 @@ $dark-grey: #656565;
align-items: center;
justify-content: center;
transform: translate(0px, -1px);
- background-color: $light-background;
+ background-color: $white;
width: 40px;
height: 15px;
align-self: center;
justify-self: center;
- border: solid 1px black;
+ border: solid 1px $black;
border-top: 0px;
border-bottom-right-radius: 7px;
border-bottom-left-radius: 7px;
@@ -855,15 +851,15 @@ $dark-grey: #656565;
.layout-container {
padding: 5px;
display: grid;
- background-color: $light-background;
+ background-color: $white;
grid-template-columns: repeat(auto-fit, minmax(90px, 100px));
width: 100%;
- border: solid 1px black;
+ border: solid 1px $black;
min-width: 100px;
overflow: hidden;
.layout:hover {
- border: solid 2px #5c9edd;
+ border: solid 2px $medium-blue;
}
.layout {
@@ -878,7 +874,7 @@ $dark-grey: #656565;
width: 90px;
overflow: hidden;
background-color: white;
- border: solid darkgrey 1px;
+ border: solid $medium-gray 1px;
display: grid;
grid-template-rows: auto;
align-items: center;
@@ -893,7 +889,7 @@ $dark-grey: #656565;
height: 13;
font-size: 12;
display: flex;
- background-color: #f1efec;
+ background-color: $white;
}
.subtitle {
@@ -906,7 +902,7 @@ $dark-grey: #656565;
height: 13;
font-size: 9;
display: flex;
- background-color: #f1efec;
+ background-color: $white;
}
.content {
@@ -919,7 +915,7 @@ $dark-grey: #656565;
height: 13;
font-size: 10;
display: flex;
- background-color: #f1efec;
+ background-color: $white;
height: 33;
text-align: left;
font-size: 8px;
@@ -930,7 +926,7 @@ $dark-grey: #656565;
.presBox-buttons {
position: relative;
width: 100%;
- background: gray;
+ background: $medium-gray;
min-height: 35px;
padding-top: 5px;
padding-bottom: 5px;
@@ -960,8 +956,8 @@ $dark-grey: #656565;
select {
- background: #323232;
- color: white;
+ background: $dark-gray;
+ color: $white;
}
.presBox-button {
@@ -975,8 +971,8 @@ $dark-grey: #656565;
text-align: center;
letter-spacing: normal;
width: inherit;
- background: #323232;
- color: white;
+ background: $dark-gray;
+ color: $white;
}
.presBox-button.active {
@@ -984,7 +980,7 @@ $dark-grey: #656565;
}
.presBox-button.active:hover {
- background-color: #233163;
+ background-color: $medium-blue;
}
.presBox-button.edit {
@@ -1053,8 +1049,8 @@ $dark-grey: #656565;
font-size: 100;
display: flex;
align-items: center;
- background: #323232;
- color: white;
+ background: $dark-gray;
+ color: $white;
}
.presBox-viewPicker {
@@ -1086,7 +1082,7 @@ $dark-grey: #656565;
top: 10;
opacity: 0.1;
transition: all 0.4s;
- color: white;
+ color: $white;
}
.miniPres:hover {
@@ -1094,8 +1090,8 @@ $dark-grey: #656565;
}
.presPanelOverlay {
- background-color: #323232;
- color: white;
+ background-color: $dark-gray;
+ color: $white;
border-radius: 5px;
grid-template-rows: 100%;
height: 25;
@@ -1129,7 +1125,7 @@ $dark-grey: #656565;
.presPanel-divider {
width: 0.5px;
height: 80%;
- border-right: solid 1px #5a5a5a;
+ border-right: solid 1px $medium-gray;
}
.presPanel-button-frame {
@@ -1161,12 +1157,12 @@ $dark-grey: #656565;
}
.presPanel-button:hover {
- background-color: #5a5a5a;
+ background-color: $medium-gray;
transform: scale(1.2);
}
.presPanel-button-text:hover {
- background-color: #5a5a5a;
+ background-color: $medium-gray;
}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f3fb6ff17..cfc3e75cc 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -5,67 +5,33 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
-import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../fields/Doc";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { InkTool } from "../../../fields/InkField";
-import { List } from "../../../fields/List";
-import { PrefetchProxy } from "../../../fields/Proxy";
-import { listSpec, makeInterface } from "../../../fields/Schema";
-import { ScriptField } from "../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { returnFalse, returnOne, returnTrue, emptyFunction } from '../../../Utils';
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { Scripting } from "../../util/Scripting";
-import { SelectionManager } from "../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { TabDocView } from "../collections/TabDocView";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { FieldView, FieldViewProps } from './FieldView';
+import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../../fields/Doc";
+import { documentSchema } from "../../../../fields/documentSchemas";
+import { InkTool } from "../../../../fields/InkField";
+import { List } from "../../../../fields/List";
+import { PrefetchProxy } from "../../../../fields/Proxy";
+import { listSpec, makeInterface } from "../../../../fields/Schema";
+import { ScriptField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { emptyFunction, returnFalse, returnOne, returnTrue } from '../../../../Utils';
+import { Docs } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { Scripting } from "../../../util/Scripting";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { CollectionDockingView } from "../../collections/CollectionDockingView";
+import { CollectionView, CollectionViewType } from "../../collections/CollectionView";
+import { TabDocView } from "../../collections/TabDocView";
+import { ViewBoxBaseComponent } from "../../DocComponent";
+import { Colors } from "../../global/globalEnums";
+import { LightboxView } from "../../LightboxView";
+import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentView";
+import { FieldView, FieldViewProps } from '../FieldView';
import "./PresBox.scss";
import Color = require("color");
-import { LightboxView } from "../LightboxView";
-
-export enum PresMovement {
- Zoom = "zoom",
- Pan = "pan",
- Jump = "jump",
- None = "none",
-}
-
-export enum PresEffect {
- Zoom = "Zoom",
- Lightspeed = "Lightspeed",
- Fade = "Fade in",
- Flip = "Flip",
- Rotate = "Rotate",
- Bounce = "Bounce",
- Roll = "Roll",
- None = "None",
- Left = "left",
- Right = "right",
- Center = "center",
- Top = "top",
- Bottom = "bottom"
-}
-
-enum PresStatus {
- Autoplay = "auto",
- Manual = "manual",
- Edit = "edit"
-}
-
-export enum PresColor {
- LightBlue = "#AEDDF8",
- DarkBlue = "#5B9FDD",
- LightBackground = "#ececec",
- SlideBackground = "#d5dce2",
-}
+import { PresEffect, PresStatus, PresMovement } from "./PresEnums";
export class PinProps {
audioRange?: boolean;
@@ -639,7 +605,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.layoutDoc.presStatus = PresStatus.Edit;
Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
CollectionDockingView.AddSplit(this.rootDoc, "right");
- } else if (this.layoutDoc.context && docView) {
+ } else if ((true || this.layoutDoc.context) && docView) {
console.log("case 2");
this.layoutDoc.presStatus = PresStatus.Edit;
clearTimeout(this._presTimer);
@@ -1198,9 +1164,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
{this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"}
</div>
:
- <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}>
+ <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
{this.setMovementName(activeItem.presMovement, activeItem)}
- <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} />
+ <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}>
<div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div>
<div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div>
@@ -1245,7 +1211,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-doubleButton">
{isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>}
{isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>}
- <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip>
</div>
{(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <>
<div className="ribbon-doubleButton" >
@@ -1280,9 +1246,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
{isPresCollection ? (null) : <div className="ribbon-box">
Effects
- <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}>
+ <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
{effect}
- <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} />
+ <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}>
<div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
<div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
@@ -1299,11 +1265,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
</div>
<div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${PresColor.LightBlue}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
</div>
</div>}
<div className="ribbon-final-box">
@@ -1356,7 +1322,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<>
{this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)}
<div className="ribbon-doubleButton">
- <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? PresColor.LightBlue : "" }}
+ <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : "" }}
onClick={() => {
activeItem.presPinView = !activeItem.presPinView;
targetDoc.presPinView = activeItem.presPinView;
@@ -1496,7 +1462,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="slider-text" style={{ fontWeight: 500 }}>
Start time (s)
</div>
- <div id={"startTime"} className="slider-number" style={{ backgroundColor: PresColor.LightBackground }}>
+ <div id={"startTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
<input className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presStartTime)}
@@ -1508,7 +1474,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="slider-text" style={{ fontWeight: 500 }}>
Duration (s)
</div>
- <div className="slider-number" style={{ backgroundColor: PresColor.LightBlue }}>
+ <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}>
{Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
</div>
</div>
@@ -1516,7 +1482,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="slider-text" style={{ fontWeight: 500 }}>
End time (s)
</div>
- <div id={"endTime"} className="slider-number" style={{ backgroundColor: PresColor.LightBackground }}>
+ <div id={"endTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
<input className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presEndTime)}
@@ -1534,16 +1500,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this._batch = UndoManager.StartBatch("presEndTime");
const endBlock = document.getElementById("endTime");
if (endBlock) {
- endBlock.style.color = PresColor.LightBackground;
- endBlock.style.backgroundColor = PresColor.DarkBlue;
+ endBlock.style.color = Colors.LIGHT_GRAY;
+ endBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
}
}}
onPointerUp={() => {
this._batch?.end();
const endBlock = document.getElementById("endTime");
if (endBlock) {
- endBlock.style.color = "black";
- endBlock.style.backgroundColor = PresColor.LightBackground;
+ endBlock.style.color = Colors.BLACK;
+ endBlock.style.backgroundColor = Colors.LIGHT_GRAY;
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1558,16 +1524,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this._batch = UndoManager.StartBatch("presStartTime");
const startBlock = document.getElementById("startTime");
if (startBlock) {
- startBlock.style.color = PresColor.LightBackground;
- startBlock.style.backgroundColor = PresColor.DarkBlue;
+ startBlock.style.color = Colors.LIGHT_GRAY;
+ startBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
}
}}
onPointerUp={() => {
this._batch?.end();
const startBlock = document.getElementById("startTime");
if (startBlock) {
- startBlock.style.color = "black";
- startBlock.style.backgroundColor = PresColor.LightBackground;
+ startBlock.style.color = Colors.BLACK;
+ startBlock.style.backgroundColor = Colors.LIGHT_GRAY;
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1651,15 +1617,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div>
<div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div className="layout-container" style={{ height: 'max-content' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} />
+ <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}>
+ <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}>
<div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}>
+ <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}>
<div className="title" style={{ alignSelf: 'center' }}>Title</div>
<div className="content">Text goes here</div>
</div>
@@ -1691,26 +1657,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-box">
Choose type:
<div className="ribbon-doubleButton">
- <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : PresColor.LightBlue }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div>
- <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? PresColor.LightBlue : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div>
+ <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : Colors.LIGHT_BLUE }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div>
+ <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div>
</div>
</div>
<div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}>
Preset layouts:
<div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'blank')} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'title')}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'blank')} />
+ <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'title')}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'header')}>
+ <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'header')}>
<div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'content')}>
+ <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'content')}>
<div className="title" style={{ alignSelf: 'center' }}>Title</div>
<div className="content">Text goes here</div>
</div>
- <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'twoColumns')}>
+ <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'twoColumns')}>
<div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div>
<div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div>
<div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div>
@@ -1869,8 +1835,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-box">
{this.stringType} selected
<div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeChild}>Contents</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? PresColor.LightBlue : "" }} onClick={this.editProgressivize}>Edit</div>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeChild}>Contents</div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editProgressivize}>Edit</div>
</div>
<div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}>
<div className="presBox-subheading">Active text color</div>
@@ -1885,12 +1851,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
{this.viewedColorPicker}
<div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeZoom}>Zoom</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? PresColor.LightBlue : "" }} onClick={this.editZoomProgressivize}>Edit</div>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeZoom}>Zoom</div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editZoomProgressivize}>Edit</div>
</div>
<div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeScroll}>Scroll</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.editScrollProgressivize}>Edit</div>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeScroll}>Scroll</div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editScrollProgressivize}>Edit</div>
</div>
</div>
<div className="ribbon-final-box">
@@ -1900,7 +1866,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}>
<FontAwesomeIcon icon={"caret-left"} size={"lg"} />
</div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? PresColor.DarkBlue : PresColor.LightBlue }}
+ <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }}
onClick={action(() => targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing)} >
{NumCast(targetDoc._currentFrame)}
</div>
@@ -1914,7 +1880,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
{this.frameListHeader}
{this.frameList}
</div>
- <div className="ribbon-toggle" style={{ height: 20, backgroundColor: PresColor.LightBlue }} onClick={() => console.log(" TODO: play frames")}>Play</div>
+ <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(" TODO: play frames")}>Play</div>
</div>
</div>
</div>
@@ -2130,7 +2096,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>);
}
tags.push(
- <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? PresColor.LightBlue : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
+ <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
<div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div>
<div className="progressivizeButton-frame">{doc.appearFrame}</div>
<div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div>
@@ -2213,6 +2179,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation);
+ const activeColor = Colors.LIGHT_BLUE;
+ const inactiveColor = Colors.WHITE;
return (mode === CollectionViewType.Carousel3D) ? (null) : (
<div id="toolbarContainer" className={'presBox-toolbar'}>
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
@@ -2220,7 +2188,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
</div></Tooltip> */}
<Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}>
- <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? PresColor.DarkBlue : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
+ <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
<FontAwesomeIcon icon={"exchange-alt"} />
</div>
</Tooltip>
@@ -2229,7 +2197,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="toolbar-divider" />
{/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}>
<div className={"toolbar-button"}
- style={{ color: this._expandBoolean ? PresColors.DarkBlue : 'white' }}
+ style={{ color: this._expandBoolean ? Colors.MEDIUM_BLUE : 'white' }}
onClick={this.toggleExpandMode}>
<FontAwesomeIcon icon={"eye"} />
</div>
@@ -2237,12 +2205,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="toolbar-divider" /> */}
<Tooltip title={<><div className="dash-tooltip">{presKeyEvents ? "Keys are active" : "Keys are not active - click anywhere on the presentation trail to activate keys"}</div></>}>
<div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
- <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? PresColor.DarkBlue : 'white' }} />
+ <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? activeColor : inactiveColor }} />
</div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}>
<div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
- <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? PresColor.DarkBlue : 'white' }} />
+ <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
@@ -2379,7 +2347,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
// Case 1: There are still other frames and should go through all frames before going to next slide
return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? PresColor.DarkBlue : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
<div className="presPanel-divider"></div>
<div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
<Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? "pause" : "play"} /></div></Tooltip>
@@ -2418,8 +2386,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ?
<div className="miniPres">
- <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + PresColor.DarkBlue : undefined }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? PresColor.DarkBlue : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
<div className="presPanel-divider"></div>
<div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
<Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
new file mode 100644
index 000000000..1ad4b820e
--- /dev/null
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -0,0 +1,235 @@
+$light-blue: #AEDDF8;
+$dark-blue: #5B9FDD;
+$light-background: #ececec;
+$slide-background: #d5dce2;
+$slide-active: #5B9FDD;
+
+.presItem-container {
+ cursor: grab;
+ display: grid;
+ grid-template-columns: 20px auto;
+ font-family: Roboto;
+ letter-spacing: normal;
+ position: relative;
+ pointer-events: all;
+ width: 100%;
+ height: 100%;
+ font-weight: 400;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ align-items: center;
+
+ .presItem-number {
+ margin-top: 3.5px;
+ font-size: 12px;
+ font-weight: 700;
+ text-align: center;
+ justify-self: center;
+ align-self: flex-start;
+ position: relative;
+ display: inline-block;
+ overflow: hidden;
+ }
+
+}
+
+.presItem-slide {
+ position: relative;
+ background-color: #d5dce2;
+ border-radius: 5px;
+ height: calc(100% - 7px);
+ width: calc(100% - 15px);
+ display: grid;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
+
+ .presItem-name {
+ min-width: 20px;
+ z-index: 300;
+ top: 2px;
+ align-self: center;
+ font-size: 11px;
+ font-family: Roboto;
+ font-weight: 500;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ }
+
+ .presItem-docName {
+ min-width: 20px;
+ z-index: 300;
+ align-self: center;
+ font-size: 9px;
+ font-family: Roboto;
+ font-weight: 300;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ grid-row: 2;
+ grid-column: 1/6;
+ }
+
+ .presItem-time {
+ align-self: center;
+ position: relative;
+ padding-right: 10px;
+ top: 1px;
+ font-size: 10;
+ font-weight: 300;
+ font-family: Roboto;
+ z-index: 300;
+ letter-spacing: normal;
+ }
+
+ .presItem-embedded {
+ overflow: hidden;
+ grid-row: 3;
+ grid-column: 1/8;
+ position: relative;
+ display: flex;
+ width: auto;
+ justify-content: center;
+ margin: auto;
+ margin-bottom: 2px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ }
+
+ .presItem-embeddedMask {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ border-radius: 3px;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ overflow: hidden;
+ }
+
+
+ .presItem-slideButtons {
+ display: flex;
+ grid-column: 7;
+ grid-row: 1/3;
+ width: max-content;
+ justify-self: right;
+ justify-content: flex-end;
+
+ .slideButton {
+ cursor: pointer;
+ position: relative;
+ border-radius: 100px;
+ z-index: 300;
+ width: 18px;
+ height: 18px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+ }
+
+ .slideButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+ }
+ }
+}
+
+// .presItem-slide:hover {
+// .presItem-slideButtons {
+// display: flex;
+// grid-column: 7;
+// grid-row: 1/3;
+// width: max-content;
+// justify-self: right;
+// justify-content: flex-end;
+
+// .slideButton {
+// cursor: pointer;
+// position: relative;
+// border-radius: 100px;
+// z-index: 300;
+// width: 18px;
+// height: 18px;
+// display: flex;
+// font-size: 12px;
+// justify-self: center;
+// align-self: center;
+// background-color: rgba(0, 0, 0, 0.5);
+// color: white;
+// justify-content: center;
+// align-items: center;
+// transition: 0.2s;
+// margin-right: 3px;
+// }
+
+// .slideButton:hover {
+// background-color: rgba(0, 0, 0, 1);
+// transform: scale(1.2);
+// }
+// }
+// }
+
+.presItem-slide.active {
+ box-shadow: 0 0 0px 1.5px $dark-blue;
+}
+
+.presItem-multiDrag {
+ font-family: Roboto;
+ font-weight: 600;
+ color: white;
+ text-align: center;
+ justify-content: center;
+ align-content: center;
+ width: 100px;
+ height: 30px;
+ position: absolute;
+ background-color: $dark-blue;
+ z-index: 4000;
+ border-radius: 10px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+ line-height: 30px;
+}
+
+.presItem-miniSlide {
+ font-weight: 700;
+ font-size: 12;
+ grid-column: 1/8;
+ align-self: center;
+ justify-self: center;
+ background-color: #d5dce2;
+ width: 26px;
+ text-align: center;
+ height: 26px;
+ line-height: 28px;
+ border-radius: 100%;
+}
+
+.presItem-miniSlide.active {
+ box-shadow: 0 0 0px 1.5px $dark-blue;
+}
+
+// .presItem-slide:hover {
+// background: $slide-hover;
+// } \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
new file mode 100644
index 000000000..5e713c3cf
--- /dev/null
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -0,0 +1,388 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
+import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from "../../../../fields/FieldSymbols";
+import { createSchema, makeInterface } from '../../../../fields/Schema';
+import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath, returnEmptyDoclist } from "../../../../Utils";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DragManager } from "../../../util/DragManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { EditableView } from "../../EditableView";
+import { DocumentView, DocumentViewProps } from "../../nodes/DocumentView";
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { PresBox } from "./PresBox";
+import { Colors } from "../../global/globalEnums";
+import { StyleProp } from "../../StyleProvider";
+import "./PresElementBox.scss";
+import React = require("react");
+import { DocUtils } from "../../../documents/Documents";
+import { PresMovement } from "./PresEnums";
+
+export const presSchema = createSchema({
+ presentationTargetDoc: Doc,
+ presBox: Doc,
+ presZoomButton: "boolean",
+ presNavButton: "boolean",
+ presHideTillShownButton: "boolean",
+ presFadeButton: "boolean",
+ presHideAfterButton: "boolean",
+ presGroupButton: "boolean",
+ presExpandInlineButton: "boolean"
+});
+
+type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>;
+const PresDocument = makeInterface(presSchema, documentSchema);
+/**
+ * This class models the view a document added to presentation will have in the presentation.
+ * It involves some functionality for its buttons and options.
+ */
+@observer
+export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
+ _heightDisposer: IReactionDisposer | undefined;
+
+ @observable _dragging = false;
+ // these fields are conditionally computed fields on the layout document that take this document as a parameter
+ @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+ @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
+ @computed get presStatus() { return StrCast(this.lookupField("presStatus")); }
+ @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
+ @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
+ @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
+
+ componentDidMount() {
+ this.layoutDoc.hideLinkButton = true;
+ this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
+ params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._heightDisposer?.();
+ }
+
+ /**
+ * Returns a local transformed coordinate array for given coordinates.
+ */
+ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
+
+ @action
+ presExpandDocumentClick = () => {
+ this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton;
+ }
+
+ embedHeight = (): number => 97;
+ // embedWidth = () => this.props.PanelWidth();
+ // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
+ embedWidth = (): number => this.props.PanelWidth() - 35;
+ styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ if (property === StyleProp.Opacity) return 1;
+ return this.props.styleProvider?.(doc, props, property);
+ }
+ /**
+ * The function that is responsible for rendering a preview or not for this
+ * presentation element.
+ */
+ @computed get renderEmbeddedInline() {
+ return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
+ <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
+ <DocumentView
+ Document={this.targetDoc}
+ DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+ styleProvider={this.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ isContentActive={this.props.isContentActive}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ PanelWidth={this.embedWidth}
+ PanelHeight={this.embedHeight}
+ ScreenToLocalTransform={Transform.Identity}
+ moveDocument={this.props.moveDocument!}
+ renderDepth={this.props.renderDepth + 1}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ hideLinkButton={true}
+ />
+ <div className="presItem-embeddedMask" />
+ </div>;
+ }
+
+ @computed get duration() {
+ let durationInS: number;
+ if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; }
+ else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
+ else durationInS = 2;
+ return "D: " + durationInS + "s";
+ }
+
+ @computed get transition() {
+ let transitionInS: number;
+ if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
+ else transitionInS = 0.5;
+ return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s";
+ }
+
+ private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _titleRef: React.RefObject<EditableView> = React.createRef();
+
+
+ @action
+ headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ const element = e.target as any;
+ e.stopPropagation();
+ e.preventDefault();
+ if (element && !(e.ctrlKey || e.metaKey)) {
+ if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
+ PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
+ setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
+ } else {
+ setupMoveUpEvents(this, e, ((e: PointerEvent) => {
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
+ return this.startDrag(e);
+ }), emptyFunction, emptyFunction);
+ }
+ }
+ }
+
+ headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ startDrag = (e: PointerEvent) => {
+ const miniView: boolean = this.toolbarWidth <= 100;
+ const activeItem = this.rootDoc;
+ const dragArray = PresBox.Instance._dragArray;
+ const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+ const dragItem: HTMLElement[] = [];
+ if (dragArray.length === 1) {
+ const doc = dragArray[0];
+ doc.className = miniView ? "presItem-miniSlide" : "presItem-slide";
+ dragItem.push(doc);
+ } else if (dragArray.length >= 1) {
+ const doc = document.createElement('div');
+ doc.className = "presItem-multiDrag";
+ doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides";
+ doc.style.position = 'absolute';
+ doc.style.top = (e.clientY) + 'px';
+ doc.style.left = (e.clientX - 50) + 'px';
+ dragItem.push(doc);
+ }
+
+ // const dropEvent = () => runInAction(() => this._dragging = false);
+ if (activeItem) {
+ DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined);
+ // runInAction(() => this._dragging = true);
+ return true;
+ }
+ return false;
+ }
+
+ onPointerOver = (e: any) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
+
+ onPointerMove = (e: PointerEvent) => {
+ const slide = this._itemRef.current!;
+ let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
+ for (const doc of DragManager.docsBeingDragged) {
+ if (!doc.presentationTargetDoc) dragIsPresItem = false;
+ }
+ if (slide && dragIsPresItem) {
+ const rect = slide.getBoundingClientRect();
+ const y = e.clientY - rect.top; //y position within the element.
+ const height = slide.clientHeight;
+ const halfLine = height / 2;
+ if (y <= halfLine) {
+ slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ slide.style.borderBottom = "0px";
+ } else if (y > halfLine) {
+ slide.style.borderTop = "0px";
+ slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ }
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ onPointerLeave = (e: any) => {
+ this._itemRef.current!.style.borderTop = "0px";
+ this._itemRef.current!.style.borderBottom = "0px";
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ @action
+ toggleProperties = () => {
+ if (CurrentUserUtils.propertiesWidth < 5) {
+ action(() => (CurrentUserUtils.propertiesWidth = 250));
+ }
+ }
+
+ @undoBatch
+ removeItem = action((e: React.MouseEvent) => {
+ this.props.removeDocument?.(this.rootDoc);
+ if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
+ PresBox.Instance._selectedArray.delete(this.rootDoc);
+ }
+ e.stopPropagation();
+ });
+
+ @undoBatch
+ @action
+ onSetValue = (value: string) => {
+ this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
+ return true;
+ }
+
+ /**
+ * Method called for updating the view of the currently selected document
+ *
+ * @param targetDoc
+ * @param activeItem
+ */
+ @undoBatch
+ @action
+ updateView = (targetDoc: Doc, activeItem: Doc) => {
+ if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
+ const scroll = targetDoc._scrollTop;
+ activeItem.presPinViewScroll = scroll;
+ } else if (targetDoc.type === DocumentType.VID) {
+ activeItem.presPinTimecode = targetDoc._currentTimecode;
+ } else if (targetDoc.type === DocumentType.COMPARISON) {
+ const clipWidth = targetDoc._clipWidth;
+ activeItem.presPinClipWidth = clipWidth;
+ } else {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
+ }
+ }
+
+ @computed
+ get toolbarWidth(): number {
+ const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
+ let width: number = NumCast(this.presBox._width);
+ if (presBoxDocView) width = presBoxDocView.props.PanelWidth();
+ if (width === 0) width = 300;
+ return width;
+ }
+
+ @computed get mainItem() {
+ const isSelected: boolean = PresBox.Instance._selectedArray.has(this.rootDoc);
+ const toolbarWidth: number = this.toolbarWidth;
+ const showMore: boolean = this.toolbarWidth >= 300;
+ const miniView: boolean = this.toolbarWidth <= 110;
+ const presBox: Doc = this.presBox; //presBox
+ const presBoxColor: string = StrCast(presBox._backgroundColor);
+ const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false;
+ const targetDoc: Doc = this.targetDoc;
+ const activeItem: Doc = this.rootDoc;
+ return (
+ <div className={`presItem-container`}
+ key={this.props.Document[Id] + this.indexInPres}
+ ref={this._itemRef}
+ style={{
+ backgroundColor: presColorBool ? isSelected ? "rgba(250,250,250,0.3)" : "transparent" : isSelected ? Colors.LIGHT_BLUE : "transparent",
+ opacity: this._dragging ? 0.3 : 1
+ }}
+ onClick={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ }}
+ onDoubleClick={action(e => {
+ this.toggleProperties();
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+ })}
+ onPointerOver={this.onPointerOver}
+ onPointerLeave={this.onPointerLeave}
+ onPointerDown={this.headerDown}
+ onPointerUp={this.headerUp}
+ >
+ {miniView ?
+ // when width is LESS than 110 px
+ <div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}>
+ {`${this.indexInPres + 1}.`}
+ </div>
+ :
+ // when width is MORE than 110 px
+ <div className="presItem-number">
+ {`${this.indexInPres + 1}.`}
+ </div>}
+ {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
+ style={{
+ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
+ }}>
+ <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <EditableView
+ ref={this._titleRef}
+ editing={!isSelected ? false : undefined}
+ contents={activeItem.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(activeItem.title)}
+ SetValue={this.onSetValue}
+ />
+ </div>
+ <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip>
+ <div className={"presItem-slideButtons"}>
+ <Tooltip title={<><div className="dash-tooltip">{"Update view"}</div></>}>
+ <div className="slideButton"
+ onClick={() => this.updateView(targetDoc, activeItem)}
+ style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V</div>
+ </Tooltip>
+ {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
+ <div className="slideButton"
+ onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
+ style={{
+ zIndex: 1000 - this.indexInPres,
+ fontWeight: 700,
+ backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined,
+ height: activeItem.groupWithUp ? 53 : 18,
+ transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined
+ }}>
+ <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}>
+ <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </div>
+ </Tooltip>}
+ <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"slideButton"} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}>
+ <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? "eye-slash" : "eye"} onPointerDown={e => e.stopPropagation()} />
+ </div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div
+ className={"slideButton"}
+ onClick={this.removeItem}>
+ <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
+ </div></Tooltip>
+ </div>
+ <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div>
+ {this.renderEmbeddedInline}
+ </div>}
+ </div >);
+ }
+
+ render() {
+ return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : this.mainItem;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts
new file mode 100644
index 000000000..93ab323fb
--- /dev/null
+++ b/src/client/views/nodes/trails/PresEnums.ts
@@ -0,0 +1,28 @@
+export enum PresMovement {
+ Zoom = "zoom",
+ Pan = "pan",
+ Jump = "jump",
+ None = "none",
+}
+
+export enum PresEffect {
+ Zoom = "Zoom",
+ Lightspeed = "Lightspeed",
+ Fade = "Fade in",
+ Flip = "Flip",
+ Rotate = "Rotate",
+ Bounce = "Bounce",
+ Roll = "Roll",
+ None = "None",
+ Left = "left",
+ Right = "right",
+ Center = "center",
+ Top = "top",
+ Bottom = "bottom"
+}
+
+export enum PresStatus {
+ Autoplay = "auto",
+ Manual = "manual",
+ Edit = "edit"
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
new file mode 100644
index 000000000..8f3f7b03a
--- /dev/null
+++ b/src/client/views/nodes/trails/index.ts
@@ -0,0 +1,3 @@
+export * from "./PresBox";
+export * from "./PresElementBox";
+export * from "./PresEnums"; \ No newline at end of file