aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
authormehekj <mehek.jethani@gmail.com>2021-09-12 12:32:49 -0400
committermehekj <mehek.jethani@gmail.com>2021-09-12 12:32:49 -0400
commitd71bc56628c2193b537b92a186785eaffa3a1eef (patch)
tree0e346527d01ef4d32a28cf9738323c6f49656dfb /src/client/views/nodes
parent23db5275a9477d5894e76a2ad6f1795fb0f99b71 (diff)
parent9ac6931dabac40a7e530d144c1d1123548de373f (diff)
Merge branch 'master' into temporalmedia-mehek
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/AudioBox.tsx79
-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.tsx6
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss1
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx28
-rw-r--r--src/client/views/nodes/DocumentView.tsx171
-rw-r--r--src/client/views/nodes/FieldView.tsx11
-rw-r--r--src/client/views/nodes/FilterBox.tsx36
-rw-r--r--src/client/views/nodes/FontIconBox.scss103
-rw-r--r--src/client/views/nodes/FontIconBox.tsx96
-rw-r--r--src/client/views/nodes/ImageBox.tsx30
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx1
-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.scss16
-rw-r--r--src/client/views/nodes/PDFBox.tsx137
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx30
-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.scss9
-rw-r--r--src/client/views/nodes/WebBox.tsx224
-rw-r--r--src/client/views/nodes/button/ButtonInterface.ts12
-rw-r--r--src/client/views/nodes/button/ButtonScripts.ts14
-rw-r--r--src/client/views/nodes/button/FontIconBadge.scss11
-rw-r--r--src/client/views/nodes/button/FontIconBadge.tsx37
-rw-r--r--src/client/views/nodes/button/FontIconBox.scss407
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx925
-rw-r--r--src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx77
-rw-r--r--src/client/views/nodes/button/colorDropdown/index.ts1
-rw-r--r--src/client/views/nodes/button/textButton/TextButton.tsx17
-rw-r--r--src/client/views/nodes/button/textButton/index.ts1
-rw-r--r--src/client/views/nodes/button/toggleButton/ToggleButton.tsx34
-rw-r--r--src/client/views/nodes/button/toggleButton/index.ts1
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss28
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx137
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts73
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss1
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx517
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx8
44 files changed, 2389 insertions, 1170 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 4b1ab9d30..05dd64b68 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -16,7 +16,7 @@ import { makeInterface } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime, Utils } from "../../../Utils";
+import { emptyFunction, formatTime } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -163,7 +163,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
: undefined)
) || this.rootDoc
);
- };
+ }
componentWillUnmount() {
Object.values(this._disposers).forEach((disposer) => disposer?.());
@@ -240,7 +240,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this.layoutDoc._currentTimecode = htmlEle.currentTime;
}
- };
+ }
// pause play back
Pause = action(() => {
@@ -252,7 +252,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
playFromTime = (absoluteTime: number) => {
this.recordingStart &&
this.playFrom((absoluteTime - this.recordingStart) / 1000);
- };
+ }
// play back the audio from time
@action
@@ -277,7 +277,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this._play = setTimeout(
() => {
this._ended = fullPlay ? true : this._ended;
- this.Pause()
+ this.Pause();
},
(end - start) * 1000
); // use setTimeout to play a specific duration
@@ -286,7 +286,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this.Pause();
}
}
- };
+ }
// update the recording time
updateRecordTime = () => {
@@ -299,7 +299,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
(new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
}
}
- };
+ }
// starts recording
recordAudioAnnotation = async () => {
@@ -312,9 +312,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
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();
@@ -322,7 +320,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
setTimeout(this.updateRecordTime, 0);
this._recorder.start();
setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour
- };
+ }
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
@@ -355,7 +353,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
subitems: funcs,
icon: "asterisk",
});
- };
+ }
// stops the recording
stopRecording = action(() => {
@@ -378,12 +376,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
e.stopPropagation();
}
- };
+ }
// for play button
Play = (e?: any) => {
let start;
- if (this._ended || this._ele!.currentTime == this.duration) {
+ if (this._ended || this._ele!.currentTime === this.duration) {
start = this._trimStart;
this._ended = false;
}
@@ -393,7 +391,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this.playFrom(start, this._trimEnd, true);
e?.stopPropagation?.();
- };
+ }
// creates a text document for dictation
onFile = (e: any) => {
@@ -415,14 +413,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
);
this.props.addDocument?.(newDoc);
e.stopPropagation();
- };
+ }
// ref for updating time
setRef = (e: HTMLAudioElement | null) => {
e?.addEventListener("timeupdate", this.timecodeChanged);
e?.addEventListener("ended", this.Pause);
this._ele = e;
- };
+ }
// returns the path of the audio file
@computed get path() {
@@ -433,16 +431,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
// returns the html audio element
@computed get audio() {
- return (
- <audio
- ref={this.setRef}
- className={`audiobox-control${this.isContentActive() ? "-interactive" : ""
- }`}
- >
- <source src={this.path} type="audio/mpeg" />
- Not supported.
- </audio>
- );
+ return <audio ref={this.setRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
+ <source src={this.path} type="audio/mpeg" />
+ Not supported.
+ </audio>;
}
// pause the time during recording phase
@@ -452,7 +444,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this._paused = true;
this._recorder.pause();
e.stopPropagation();
- };
+ }
// continue the recording
@action
@@ -461,17 +453,18 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this._paused = false;
this._recorder.resume();
e.stopPropagation();
- };
+ }
playing = () => this.mediaState === "playing";
playLink = (link: Doc) => {
const stack = this._stackedTimeline.current;
if (link.annotationOn === this.rootDoc) {
- if (!this.layoutDoc.dontAutoPlayFollowedLinks)
+ if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
- else
+ } else {
this._ele!.currentTime = this.layoutDoc._currentTimecode =
stack?.anchorStart(link) || 0;
+ }
} else {
this.links
.filter((l) => l.anchor1 === link || l.anchor2 === link)
@@ -480,17 +473,18 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
if (startTime !== undefined) {
- if (!this.layoutDoc.dontAutoPlayFollowedLinks)
+ if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
endTime
? this.playFrom(startTime, endTime)
: this.playFrom(startTime);
- else
+ } else {
this._ele!.currentTime = this.layoutDoc._currentTimecode =
startTime;
+ }
}
});
}
- };
+ }
// shows trim controls
@action
@@ -502,7 +496,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this.Pause();
}
this._trimming = true;
- };
+ }
// hides trim controls and displays new clip
@action
@@ -514,7 +508,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
this.layoutDoc.clipEnd = this._trimEnd;
this._trimming = false;
this.setAnchorTime(Math.max(Math.min(this._trimEnd, this._ele!.currentTime), this._trimStart));
- };
+ }
@action
setStartTrim = (newStart: number) => {
@@ -530,14 +524,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
timelineWhenChildContentsActiveChanged = (isActive: boolean) =>
this.props.whenChildContentsActiveChanged(
runInAction(() => (this._isAnyChildContentActive = isActive))
- );
+ )
timelineScreenToLocal = () =>
this.props
.ScreenToLocalTransform()
.translate(
-AudioBox.playheadWidth,
(-(100 - this.heightPercent) / 200) * this.props.PanelHeight()
- );
+ )
setAnchorTime = (time: number) => {
(this._ele!.currentTime = this.layoutDoc._currentTimecode = time);
}
@@ -545,7 +539,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
timelineHeight = () =>
(((this.props.PanelHeight() * this.heightPercent) / 100) *
this.heightPercent) /
- 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline)
+ 100 // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline)
timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth;
@computed get renderTimeline() {
return (
@@ -572,7 +566,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
ScreenToLocalTransform={this.timelineScreenToLocal}
Play={this.Play}
Pause={this.Pause}
- isContentActive={this.isContentActive}
+ isContentActive={this.props.isContentActive}
+ isAnyChildContentActive={this.isAnyChildContentActive}
playLink={this.playLink}
PanelWidth={this.timelineWidth}
PanelHeight={this.timelineHeight}
@@ -588,7 +583,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
render() {
const interactive =
- SnappingManager.GetIsDragging() || this.isContentActive()
+ SnappingManager.GetIsDragging() || this.props.isContentActive()
? "-interactive"
: "";
return (
@@ -656,7 +651,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
className="audiobox-controls"
style={{
pointerEvents:
- this._isAnyChildContentActive || this.isContentActive()
+ this._isAnyChildContentActive || this.props.isContentActive()
? "all"
: "none",
}}
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 3d2cdf5a4..dbab5e762 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,7 +23,7 @@ import "./DocumentView.scss";
import { EquationBox } from "./EquationBox";
import { FieldView, FieldViewProps } from "./FieldView";
import { FilterBox } from "./FilterBox";
-import { FontIconBox } from "./FontIconBox";
+import { FontIconBox } from "./button/FontIconBox";
import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox";
import { FunctionPlotBox } from "./FunctionPlotBox";
import { ImageBox } from "./ImageBox";
@@ -113,7 +113,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
scaling?: () => number,
setHeight: (height: number) => void,
layoutKey: string,
- hideOnLeave?: boolean,
}> {
@computed get layout(): string {
TraceMobx();
@@ -201,7 +200,8 @@ 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, scale: "number", value: "string" });
+ const script = code[1].value.replace(/^‘/, "").replace(/’$/, ""); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
+ return ScriptField.MakeScript(script, { 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 b37b68249..228e1bdcb 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -50,6 +50,7 @@
width: 80%;
height: 80%;
font-size: 100%;
+ font-family: 'Roboto';
transition: 0.2s ease all;
&:hover {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 482f551b5..93cd02d93 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -2,25 +2,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../fields/Doc";
-import { emptyFunction, setupMoveUpEvents, returnFalse, Utils, emptyPath } from "../../../Utils";
+import { Doc, DocListCast, DocListCastAsync, Opt, WidthSym } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { Cast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { DocUtils, Docs } from "../../documents/Documents";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
+import { Hypothesis } from "../../util/HypothesisUtils";
import { LinkManager } from "../../util/LinkManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { Colors } from "../global/globalEnums";
+import { LightboxView } from "../LightboxView";
+import './DocumentLinksButton.scss';
import { DocumentView } from "./DocumentView";
-import { StrCast, Cast } from "../../../fields/Types";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { Hypothesis } from "../../util/HypothesisUtils";
-import { Id } from "../../../fields/FieldSymbols";
import { TaskCompletionBox } from "./TaskCompletedBox";
import React = require("react");
-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;
@@ -114,7 +113,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
- };
+ }
}));
}
@@ -198,7 +197,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
}
@@ -266,6 +265,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
style={{
backgroundColor: Colors.LIGHT_BLUE,
color: Colors.BLACK,
+ fontSize: "20px",
width: btnDim,
height: btnDim,
}}>
@@ -291,7 +291,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
(null)
}
</div>
- )
+ );
}
render() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 23166335e..a2d2f17b6 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, returnTrue } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
@@ -25,6 +25,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
import { LinkManager } from '../../util/LinkManager';
import { Scripting } from '../../util/Scripting';
import { SelectionManager } from "../../util/SelectionManager";
+import { ColorScheme } from "../../util/SettingsManager";
import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from "../../util/Transform";
@@ -41,13 +42,13 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView
import { DocumentContentsView } from "./DocumentContentsView";
import { DocumentLinksButton } from './DocumentLinksButton';
import "./DocumentView.scss";
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
import { LinkAnchorBox } from './LinkAnchorBox';
import { LinkDocPreview } from "./LinkDocPreview";
-import { PresBox } from './trails/PresBox';
import { RadialMenu } from './RadialMenu';
-import React = require("react");
import { ScriptingBox } from "./ScriptingBox";
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import { PresBox } from './trails/PresBox';
+import React = require("react");
const { Howl } = require('howler');
interface Window {
@@ -64,7 +65,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,11 +85,15 @@ 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;
getTitle?: () => string;
+ getScrollHeight?: () => number;
}
export interface DocumentViewSharedProps {
renderDepth: number;
@@ -109,6 +114,7 @@ export interface DocumentViewSharedProps {
docFilters: () => string[];
docRangeFilters: () => string[];
searchFilterDocs: () => Doc[];
+ showTitle?: () => string;
whenChildContentsActiveChanged: (isActive: boolean) => void;
rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
addDocTab: (doc: Doc, where: string) => boolean;
@@ -133,7 +139,7 @@ export interface DocumentViewSharedProps {
export interface DocumentViewProps extends DocumentViewSharedProps {
// properties specific to DocumentViews but not to FieldView
freezeDimensions?: boolean;
- hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
+ hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
treeViewDoc?: Doc;
@@ -181,9 +187,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); }
@@ -203,7 +209,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
@computed get nativeWidth() { return this.props.NativeWidth(); }
@computed get nativeHeight() { return this.props.NativeHeight(); }
- @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onfClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
+ @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
@computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
@computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
@computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
@@ -421,11 +427,13 @@ 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
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
...options, afterFocus: (didFocus: boolean) =>
@@ -458,7 +466,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
} else if (!Doc.IsSystem(this.rootDoc)) {
UndoManager.RunInBatch(() =>
LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.())
- //this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox")
, "double tap");
SelectionManager.DeselectAll();
Doc.UnBrushDoc(this.props.Document);
@@ -599,7 +606,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
@undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
- @undoBatch toggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`);
+ @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
@undoBatch @action
drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -629,7 +636,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
makeIntoPortal = async () => {
const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
if (!portalLink) {
- const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + ".portal" });
+ const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
}
this.Document.followLinkLocation = "inPlace";
@@ -642,7 +649,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
e.preventDefault();
e.stopPropagation();
- !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -663,7 +670,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" }));
@@ -673,7 +680,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const appearance = cm.findByDescription("UI Controls...");
const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
!Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
- appearanceItems.push({
+ !Doc.UserDoc().noviceMode && appearanceItems.push({
description: "Add a Field", event: () => {
const alias = Doc.MakeAlias(this.rootDoc);
alias.layout = FormattedTextBox.LayoutString("newfield");
@@ -697,13 +704,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const zorders = cm.findByDescription("ZOrder...");
const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
- zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+ if (this.props.bringToFront !== emptyFunction) {
+ zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+ }
!zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
- onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
+ !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
if (!this.Document.annotationOn) {
@@ -747,7 +756,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" });
}
}
@@ -755,16 +764,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.
@@ -776,8 +784,16 @@ 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 ||
+ SnappingManager.GetIsDragging() ||
+ this.props.rootSelected() ||
+ 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) :
@@ -792,7 +808,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>;
return <div className="documentView-contentsView"
style={{
- pointerEvents: this.props.contentPointerEvents as any,
+ pointerEvents: this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none",
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView key={1} {...this.props}
@@ -809,7 +825,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>;
@@ -832,16 +850,28 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
default: return this.props.styleProvider?.(doc, props, property);
}
}
- @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
+ // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
+ // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
+ // - PDF text regions are rendered as an Annotations without generating a DocumentView, '
+ // - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
+ // - and links to PDF/Web docs at a certain scroll location never create an explicit view.
+ // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+ @computed get directLinks() {
+ TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
+ Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
+ Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
+ ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
+ ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
+ );
+ }
@computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
@computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links
TraceMobx();
- if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
+ if (this.layoutDoc.unrendered || 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.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
return filtered.map((link, i) =>
- <div className="documentView-anchorCont" key={i + 1}>
+ <div className="documentView-anchorCont" key={link[Id]}>
<DocumentView {...this.props}
Document={link}
PanelWidth={this.anchorPanelWidth}
@@ -886,7 +916,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) {
@@ -913,31 +943,50 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const showTitleHover = this.ShowTitle?.includes(":hover");
const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
const captionView = !showCaption ? (null) :
- <div className="documentView-captionWrapper">
+ <div className="documentView-captionWrapper"
+ style={{ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, }}>
<FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
yPadding={10}
xPadding={10}
fieldKey={showCaption}
fontSize={Math.min(32, 12 * this.props.ScreenToLocalTransform().Scale)}
- hideOnLeave={true}
styleProvider={this.captionStyleProvider}
dontRegisterView={true}
+ isContentActive={this.isContentActive}
onClick={this.onClickFunc}
/>
</div>;
+ const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
+ const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
const titleView = !showTitle ? (null) :
<div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
position: this.headerMargin ? "relative" : "absolute",
height: this.titleHeight,
- background: StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"),
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+ color: lightOrDark(background),
+ background,
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
}}>
<EditableView ref={this._titleRef}
- contents={showTitle === "title" ? StrCast((this.dataDoc || this.props.Document).title) : showTitle.split(";").map(field => field + ":" + (this.dataDoc || this.props.Document)[field]?.toString()).join(" ")}
+ contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
display={"block"}
fontSize={10}
- GetValue={() => Field.toString((this.dataDoc || this.props.Document)[showTitle.split(";")[0]] as any as Field)}
- SetValue={undoBatch((value) => showTitle.includes("Date") ? true : (Doc.GetProto(this.dataDoc || this.props.Document)[showTitle] = value) ? true : true)}
+ GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
+ SetValue={undoBatch(input => {
+ if (input?.startsWith("#")) {
+ if (this.props.showTitle) {
+ this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
+ } else {
+ Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
+ }
+ return true;
+ } else {
+ var value = input.replace(new RegExp(showTitle + "="), "");
+ if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
+ if (showTitle.includes("Date") || showTitle === "author") return true;
+ return Doc.SetInPlace(targetDoc, showTitle, value, true) ? true : true;
+ }
+ return true;
+ })}
/>
</div>;
return this.props.hideTitle || (!showTitle && !showCaption) ?
@@ -949,12 +998,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
@computed get renderDoc() {
TraceMobx();
+ const isButton: boolean = this.props.Document.type === DocumentType.FONTICON;
if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
return this.docContents ??
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
id={this.props.Document[Id]}
style={{
- background: this.backgroundColor,
+ background: isButton ? undefined : this.backgroundColor,
opacity: this.opacity,
color: StrCast(this.layoutDoc.color, "inherit"),
fontFamily: StrCast(this.Document._fontFamily, "inherit"),
@@ -969,10 +1019,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>;
}
render() {
+ TraceMobx();
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 ?
+ const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ?
["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;
@@ -980,8 +1031,10 @@ 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 surrounding highlight
return <div className={DocumentView.ROOT_DIV} ref={this._mainCont}
onContextMenu={this.onContextMenu}
onKeyDown={this.onKeyDown}
@@ -1044,7 +1097,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() {
@@ -1059,7 +1112,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
@computed get panelHeight() {
if (this.effectiveNativeHeight) {
- return Math.min(this.props.PanelHeight(), Math.max(NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
+ return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
}
return this.props.PanelHeight();
}
@@ -1135,21 +1188,22 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- !this.props.dontRegisterView && DocumentManager.Instance.RemoveView(this);
+ !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
}
render() {
TraceMobx();
const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+ const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
return (<div className="contentFittingDocumentView">
{!this.props.Document || !this.props.PanelWidth() ? (null) : (
<div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
style={{
position: this.props.Document.isInkMask ? "absolute" : undefined,
- transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+ transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
+ width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+ height: isButton ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
`${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
}}>
<DocumentViewInternal {...this.props}
@@ -1165,14 +1219,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ScreenToLocalTransform={this.screenToLocalTransform}
focus={this.props.focus || emptyFunction}
bringToFront={emptyFunction}
- ref={action((r: DocumentViewInternal | null) => this.docView = r)} />
+ ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
</div>)}
</div>);
}
}
-Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey: string = "layout") {
- const dv = DocumentManager.Instance.getDocumentView(doc);
- if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", ""));
- else dv?.switchViews(true, layoutKey.replace("layout_", ""));
+Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
+ if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
+ else dv.switchViews(true, detailLayoutKeySuffix);
}); \ No newline at end of file
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..e9f19bf9e 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../fields/List";
import { RichTextField } from "../../../fields/RichTextField";
import { listSpec, makeInterface } from "../../../fields/Schema";
import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../fields/Types";
+import { Cast, StrCast, NumCast } from "../../../fields/Types";
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
@@ -79,10 +79,16 @@ 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.Docs().length ? SelectionManager.Docs()[0] : CurrentUserUtils.ActiveDashboard;
+ }
+ @computed static get targetDocChildKey() {
if (SelectionManager.Views().length) {
- return SelectionManager.Views()[0]?.Document.type === DocumentType.COL ? SelectionManager.Views()[0].Document : SelectionManager.Views()[0]?.props.ContainingCollectionDoc!;
+ return SelectionManager.Views()[0].ComponentView?.annotationKey || SelectionManager.Views()[0].ComponentView?.fieldKey || "data";
}
- return CurrentUserUtils.ActiveDashboard;
+ return "data";
+ }
+ @computed static get targetDocChildren() {
+ return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data);
}
@observable _loaded = false;
@@ -100,8 +106,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 +139,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 +199,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 +253,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);
@@ -339,7 +344,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
);
}
setTreeHeight = (hgt: number) => {
- this.layoutDoc._height = hgt + 140; // 50? need to add all the border sizes together.
+ this.layoutDoc._height = NumCast(this.layoutDoc._autoHeightMargins) + 150; // need to add all the border sizes together.
}
/**
@@ -409,6 +414,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 +485,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
deleted file mode 100644
index 718af2c16..000000000
--- a/src/client/views/nodes/FontIconBox.scss
+++ /dev/null
@@ -1,103 +0,0 @@
-@import "../global/globalCssVariables";
-
-.fontIconBox-label {
- color: $white;
- margin-right: 4px;
- margin-top: 1px;
- position: relative;
- text-align: center;
- font-size: 7px;
- letter-spacing: normal;
- background-color: inherit;
- border-radius: 8px;
- margin-top: -8px;
- padding: 0;
- width: 100%;
-}
-
-.fontIconBadge-container {
- position:absolute;
- z-index: 1000;
- top: 12px;
-
- .fontIconBadge {
- position: absolute;
- top: -10px;
- right: -10px;
- color: $white;
- background: $pink;
- font-weight: 300;
- border-radius: 100%;
- width: 25px;
- height: 25px;
- text-align: center;
- padding-top: 4px;
- font-size: 12px;
- }
-}
-
-.menuButton-circle,
-.menuButton-round {
- border-radius: 100%;
- background-color: $dark-gray;
- padding: 0;
-
- .fontIconBox-label {
- //margin-left: -10px; // button padding is 10px;
- bottom: 0;
- position: absolute;
- }
-
- &:hover {
- background-color: $light-gray;
- }
-}
-
-.menuButton-square {
- padding-top: 3px;
- padding-bottom: 3px;
- background-color: $dark-gray;
-
- .fontIconBox-label {
- border-radius: 0px;
- margin-top: 0px;
- border-radius: "inherit";
- }
-}
-
-.menuButton,
-.menuButton-circle,
-.menuButton-round,
-.menuButton-square {
- margin-left: -5%;
- width: 110%;
- height: 100%;
- pointer-events: all;
- touch-action: none;
-
- .menuButton-wrap {
- touch-action: none;
- border-radius: 8px;
- width: 100%;
- }
-
- .menuButton-icon-square {
- width: auto;
- height: 29px;
- padding: 4px;
- }
-
- svg {
- width: 95% !important;
- height: 95%;
- }
-}
-.menuButton-round {
- width: 100%;
- svg {
- width: 50% !important;
- height: 50%;
- position: relative;
- bottom: 2px;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
deleted file mode 100644
index 0d415e238..000000000
--- a/src/client/views/nodes/FontIconBox.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { AclPrivate, Doc, DocListCast } from '../../../fields/Doc';
-import { createSchema, makeInterface } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, StrCast } from '../../../fields/Types';
-import { GetEffectiveAcl } from '../../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
-import { DragManager } from '../../util/DragManager';
-import { ContextMenu } from '../ContextMenu';
-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",
-});
-
-type FontIconDocument = makeInterface<[typeof FontIconSchema]>;
-const FontIconDocument = makeInterface(FontIconSchema);
-@observer
-export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(FontIconDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
- showTemplate = (): void => {
- const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, "add:right");
- }
- dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); };
- useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); };
-
- specificContextMenu = (): void => {
- if (!Doc.UserDoc().noviceMode) {
- const cm = ContextMenu.Instance;
- cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
- cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
- cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
- }
- }
-
- render() {
- const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle");
- 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 === Colors.DARK_GRAY ? "0%" : "100%"})`, marginBottom: "5px" }} />;
- const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu}
- style={{ backgroundColor: backgroundColor, }}>
- <div className="menuButton-wrap">
- {icon === 'pres-trail' ? presTrailsIcon : <FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={icon} color={color}
- size={this.layoutDoc.iconShape === "square" ? "sm" : "sm"} />}
- {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>}
- <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
- </div>
- </button>;
- return !this.layoutDoc.toolTip ? button :
- <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
- {button}
- </Tooltip>;
- }
-}
-
-interface FontIconBadgeProps {
- collection: Doc | undefined;
-}
-
-@observer
-export class FontIconBadge extends React.Component<FontIconBadgeProps> {
- _notifsRef = React.createRef<HTMLDivElement>();
-
- onPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- (e: PointerEvent) => {
- const dragData = new DragManager.DocumentDragData([this.props.collection!]);
- DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
- return true;
- },
- returnFalse, emptyFunction, false);
- }
-
- render() {
- if (!(this.props.collection instanceof Doc)) return (null);
- const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read
- return <div className="fontIconBadge-container" style={{ width: 15, height: 15, top: 12 }} ref={this._notifsRef}>
- <div className="fontIconBadge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
- onPointerDown={this.onPointerDown} >
- {length}
- </div>
- </div>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index cfd43bb62..38deb4a73 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,19 +1,21 @@
-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';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnOne, Utils, returnFalse } 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,15 +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 { InkTool } from '../../../fields/InkField';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { AnchorMenu } from '../pdf/AnchorMenu';
-import { Docs } from '../../documents/Documents';
+import { SnappingManager } from '../../util/SnappingManager';
const path = require('path');
export const pageSchema = createSchema({
@@ -238,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;
}
@@ -294,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}
@@ -330,11 +331,13 @@ 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) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- this._marqueeing = [e.clientX, e.clientY];
- e.stopPropagation();
+ 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
@@ -365,7 +368,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ScreenToLocalTransform={this.screenToLocalTransform}
scaling={returnOne}
select={emptyFunction}
- isContentActive={this.isContentActive}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 8d665b8a6..db1ae0537 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -21,7 +21,7 @@ type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
const LabelDocument = makeInterface(LabelSchema, documentSchema);
export interface LabelBoxProps {
- label?: string
+ label?: string;
}
@observer
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8f9959693..1e0172d24 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -3,7 +3,6 @@ import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from "../../../fields/FieldSymbols";
import { makeInterface } from "../../../fields/Schema";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
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 72dec6e4c..f44355929 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,3 +1,5 @@
+@import "../global/globalCssVariables.scss";
+
.pdfBox,
.pdfBox-interactive {
display: inline-block;
@@ -18,12 +20,13 @@
top: 0;
left: 0;
+ // glr: This should really be the same component as text and PDFs
.pdfBox-sidebarBtn {
- background: #121721;
+ background: $black;
height: 25px;
width: 25px;
- right: 0;
- color: white;
+ right: 5px;
+ color: $white;
display: flex;
position: absolute;
align-items: center;
@@ -31,6 +34,13 @@
border-radius: 3px;
pointer-events: all;
z-index: 1; // so it appears on top of the document's title, if shown
+
+ box-shadow: $standard-box-shadow;
+ transition: 0.2s;
+
+ &:hover{
+ filter: brightness(0.85);
+ }
}
.pdfBox-pageNums {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8f61e252b..9706d73c7 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, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
import { documentSchema } from '../../../fields/documentSchemas';
import { makeInterface } from "../../../fields/Schema";
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { PdfField } from "../../../fields/URLField";
import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { KeyCodes } from '../../util/KeyCodes';
import { undoBatch } from '../../util/UndoManager';
@@ -17,13 +17,14 @@ import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeF
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
+import { Colors } from '../global/globalEnums';
+import { AnchorMenu } from '../pdf/AnchorMenu';
import { PDFViewer } from "../pdf/PDFViewer";
import { SidebarAnnos } from '../SidebarAnnos';
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);
@@ -31,6 +32,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;
@@ -53,30 +55,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?.(); }
@@ -90,6 +68,9 @@ 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);
@@ -98,9 +79,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const anchor =
AnchorMenu.Instance?.GetAnchor() ??
Docs.Create.TextanchorDocument({
- title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
+ title: StrCast(this.rootDoc.title + "@" + this.layoutDoc._scrollTop?.toFixed(0)),
y: NumCast(this.layoutDoc._scrollTop),
+ unrendered: true
});
this.addDocument(anchor);
return anchor;
@@ -165,15 +146,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 = <>
@@ -188,9 +178,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}
@@ -219,14 +209,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
</div>
- <button className="pdfBox-sidebarBtn" title="Toggle Sidebar"
- style={{ display: !this.isContentActive() ? "none" : undefined }}
+ <div className="pdfBox-sidebarBtn" key="sidebar" title="Toggle Sidebar"
+ style={{
+ display: !this.props.isContentActive() ? "none" : undefined,
+ top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK
+ }}
onPointerDown={this.sidebarBtnDown} >
- <FontAwesomeIcon icon={"chevron-left"} size="sm" />
- </button>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+ </div>
</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[] = [];
@@ -238,7 +234,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>
@@ -247,43 +243,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/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 700f8a7d3..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";
+ 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 7e7ebcad3..90de3227f 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}
trimming={false}
@@ -548,9 +551,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) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) 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;
@@ -567,7 +576,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;
@@ -589,7 +598,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}
@@ -620,4 +628,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 19b69ff5a..417a17d96 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -10,7 +10,7 @@
background: #121721;
height: 25px;
width: 25px;
- right: 0;
+ right: 5px;
display: flex;
position: absolute;
align-items: center;
@@ -18,6 +18,13 @@
border-radius: 3px;
pointer-events: all;
z-index: 1; // so it appears on top of the document's title, if shown
+
+ box-shadow: $standard-box-shadow;
+ transition: 0.2s;
+
+ &:hover{
+ filter: brightness(0.85);
+ }
}
.pdfViewerDash-dragAnnotationBox {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f5b1f96f2..7e46d8433 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -15,7 +15,6 @@ import { WebField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
import { Docs } from "../../documents/Documents";
-import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { Scripting } from "../../util/Scripting";
import { SnappingManager } from "../../util/SnappingManager";
@@ -25,6 +24,7 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import { Colors } from "../global/globalEnums";
import { LightboxView } from "../LightboxView";
import { MarqueeAnnotator } from "../MarqueeAnnotator";
import { AnchorMenu } from "../pdf/AnchorMenu";
@@ -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;
+ @observable private _scrollHeight = NumCast(this.layoutDoc.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,18 +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;
@@ -160,6 +163,10 @@ 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 windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
@@ -171,19 +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 =
AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
- Docs.Create.TextanchorDocument({
+ Docs.Create.WebanchorDocument(this._url, {
title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
y: NumCast(this.layoutDoc._scrollTop),
+ unrendered: true
});
- this.addDocument(anchor);
+ this.addDocumentWrapper(anchor);
return anchor;
}
@@ -206,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) {
@@ -220,6 +229,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
+ getScrollHeight = () => this._scrollHeight;
+
isFirefox = () => {
return "InstallTrigger" in window; // navigator.userAgent.indexOf("Chrome") !== -1;
}
@@ -233,8 +244,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");
@@ -295,9 +308,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;
@@ -309,9 +321,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;
@@ -322,25 +333,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);
}
@@ -399,26 +404,28 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- 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
- get urlContent() {
+ @computed get urlContent() {
const field = this.dataDoc[this.props.fieldKey];
let view;
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}
@@ -433,9 +440,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) => {
@@ -449,20 +461,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>;
@@ -475,60 +499,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}
- 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"}
- select={emptyFunction}
- isContentActive={returnFalse}
- ContentScaling={returnOne}
- bringToFront={emptyFunction}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.sidebarAddDocument}
- 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>
@@ -539,7 +571,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}
@@ -548,20 +580,26 @@ 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 }}
+ <div className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
+ style={{
+ display: !this.props.isContentActive() ? "none" : undefined,
+ top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK
+ }}
onPointerDown={this.sidebarBtnDown} >
- <FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" />
- </button>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+ </div>
</div>);
}
}
diff --git a/src/client/views/nodes/button/ButtonInterface.ts b/src/client/views/nodes/button/ButtonInterface.ts
new file mode 100644
index 000000000..0aa2ac8e1
--- /dev/null
+++ b/src/client/views/nodes/button/ButtonInterface.ts
@@ -0,0 +1,12 @@
+import { Doc } from "../../../../fields/Doc";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { ButtonType } from "./FontIconBox";
+
+export interface IButtonProps {
+ type: string | ButtonType;
+ rootDoc: Doc;
+ label: any;
+ icon: IconProp;
+ color: string;
+ backgroundColor: string;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts
new file mode 100644
index 000000000..bb4dd8bc9
--- /dev/null
+++ b/src/client/views/nodes/button/ButtonScripts.ts
@@ -0,0 +1,14 @@
+import { Scripting } from "../../../util/Scripting";
+import { SelectionManager } from "../../../util/SelectionManager";
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function changeView(view: string) {
+ const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function toggleOverlay() {
+ const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed");
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBadge.scss b/src/client/views/nodes/button/FontIconBadge.scss
new file mode 100644
index 000000000..78f506e57
--- /dev/null
+++ b/src/client/views/nodes/button/FontIconBadge.scss
@@ -0,0 +1,11 @@
+.fontIconBadge {
+ background: red;
+ width: 15px;
+ height: 15px;
+ top: 8px;
+ display: block;
+ position: absolute;
+ right: 5;
+ border-radius: 50%;
+ text-align: center;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx
new file mode 100644
index 000000000..cf86b5e07
--- /dev/null
+++ b/src/client/views/nodes/button/FontIconBadge.tsx
@@ -0,0 +1,37 @@
+import { observer } from "mobx-react";
+import * as React from "react";
+import { AclPrivate, Doc, DocListCast } from "../../../../fields/Doc";
+import { GetEffectiveAcl } from "../../../../fields/util";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils";
+import { DragManager } from "../../../util/DragManager";
+import "./FontIconBadge.scss";
+
+interface FontIconBadgeProps {
+ collection: Doc | undefined;
+}
+
+@observer
+export class FontIconBadge extends React.Component<FontIconBadgeProps> {
+ _notifsRef = React.createRef<HTMLDivElement>();
+
+ onPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e,
+ (e: PointerEvent) => {
+ const dragData = new DragManager.DocumentDragData([this.props.collection!]);
+ DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
+ return true;
+ },
+ returnFalse, emptyFunction, false);
+ }
+
+ render() {
+ if (!(this.props.collection instanceof Doc)) return (null);
+ const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read
+ return <div className="fontIconBadge-container" ref={this._notifsRef}>
+ <div className="fontIconBadge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
+ onPointerDown={this.onPointerDown} >
+ {length}
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
new file mode 100644
index 000000000..a2da35fe1
--- /dev/null
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -0,0 +1,407 @@
+@import "../../global/globalCssVariables";
+
+.menuButton {
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 80%;
+ border-radius: $standard-border-radius;
+ transition: 0.15s;
+
+ .menuButton-wrap {
+ grid-column: 1;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ }
+
+ .fontIconBox-label {
+ color: $white;
+ position: relative;
+ text-align: center;
+ font-size: 7px;
+ letter-spacing: normal;
+ background-color: inherit;
+ margin-top: 5px;
+ border-radius: 8px;
+ padding: 0;
+ width: 100%;
+ font-family: 'ROBOTO';
+ text-transform: uppercase;
+ font-weight: bold;
+ transition: 0.15s;
+
+
+ }
+
+ .fontIconBox-icon {
+ width: 80%;
+ height: 80%;
+ }
+
+ &.clickBtn {
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.3) !important;
+ }
+
+ svg {
+ width: 50% !important;
+ height: 50%;
+ }
+ }
+
+ &.textBtn {
+ display: grid;
+ /* grid-row: auto; */
+ grid-auto-flow: column;
+ cursor: pointer;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ justify-items: center;
+
+ &:hover {
+ filter:brightness(0.85) !important;
+ }
+ }
+
+ &.tglBtn {
+ cursor: pointer;
+
+ &.switch {
+ //TOGGLE
+
+ .switch {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ height: 25px;
+ margin: 0;
+ }
+
+ .switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ }
+
+ .slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: lightgrey;
+ -webkit-transition: .4s;
+ transition: .4s;
+ }
+
+ .slider:before {
+ position: absolute;
+ content: "";
+ height: 21px;
+ width: 21px;
+ left: 2px;
+ bottom: 2px;
+ background-color: $white;
+ -webkit-transition: .4s;
+ transition: .4s;
+ }
+
+ input:checked+.slider {
+ background-color: $medium-blue;
+ }
+
+ input:focus+.slider {
+ box-shadow: 0 0 1px $medium-blue;
+ }
+
+ input:checked+.slider:before {
+ -webkit-transform: translateX(26px);
+ -ms-transform: translateX(26px);
+ transform: translateX(26px);
+ }
+
+ /* Rounded sliders */
+ .slider.round {
+ border-radius: $standard-border-radius;
+ }
+
+ .slider.round:before {
+ border-radius: $standard-border-radius;
+ }
+ }
+
+ svg {
+ width: 50% !important;
+ height: 50%;
+ }
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.3);
+ }
+ }
+
+ &.toolBtn {
+ cursor: pointer;
+ width: 100%;
+ border-radius: 100%;
+
+ svg {
+ width: 60% !important;
+ height: 60%;
+ }
+ }
+
+ &.menuBtn {
+ cursor: pointer !important;
+ border-radius: 0px;
+ flex-direction: column;
+
+ svg {
+ width: 45% !important;
+ height: 45%;
+ }
+
+ &:hover{
+ filter: brightness(0.85);
+ }
+ }
+
+
+
+ &.colorBtn {
+ color: black;
+ cursor: pointer;
+ flex-direction: column;
+ background: transparent;
+
+ .colorButton-color {
+ margin-top: 3px;
+ width: 80%;
+ height: 3px;
+ }
+
+ .menuButton-dropdownBox {
+ position: absolute;
+ width: fit-content;
+ height: fit-content;
+ color: black;
+ top: 100%;
+ left: 0;
+ z-index: 21;
+ background-color: #e3e3e3;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ border-radius: 3px;
+ }
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.3) !important;
+ }
+ }
+
+ &.drpdownList {
+ width: 100%;
+ display: grid;
+ grid-auto-columns: 80px 20px;
+ justify-items: center;
+ font-family: 'Roboto';
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ font-size: 13;
+ font-weight: 600;
+ overflow: hidden;
+ cursor: pointer;
+ background: transparent;
+ align-content: center;
+ align-items: center;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.3) !important;
+ }
+
+ .menuButton-dropdownList {
+ position: absolute;
+ width: 150px;
+ height: fit-content;
+ top: 100%;
+ z-index: 21;
+ background-color: $white;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ padding: 1px;
+
+ .list-item {
+ color: $black;
+ width: 100%;
+ height: 25px;
+ font-weight: 400;
+ display: flex;
+ justify-content: left;
+ align-items: center;
+ padding-left: 5px;
+ }
+
+ .list-item:hover {
+ background-color: lightgrey;
+ }
+ }
+ }
+
+ &.numBtn {
+ cursor: pointer;
+ background: transparent;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.3) !important;
+ }
+
+ &.slider {
+ color: $white;
+ cursor: pointer;
+ flex-direction: column;
+ background: transparent;
+
+ .menu-slider {
+ width: 100px;
+ }
+
+ .menuButton-dropdownBox {
+ position: absolute;
+ width: fit-content;
+ height: fit-content;
+ top: 100%;
+ z-index: 21;
+ background-color: #e3e3e3;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ border-radius: $standard-border-radius;
+ }
+ }
+
+ .button {
+ width: 25%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &.number {
+ width: 50%;
+
+ .button-input {
+ background: none;
+ border: none;
+ text-align: right;
+ width: 100%;
+ color: $white;
+ height: 100%;
+ text-align: center;
+ }
+
+ .button-input:focus {
+ outline: none;
+ }
+ }
+ }
+
+ &.list {
+ width: 100%;
+ justify-content: space-around;
+ border: $standard-border;
+
+ .menuButton-dropdownList {
+ position: absolute;
+ width: fit-content;
+ height: fit-content;
+ min-width: 50%;
+ max-height: 50vh;
+ overflow-y: scroll;
+ top: 100%;
+ z-index: 21;
+ background-color: $white;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ padding: 1px;
+
+ .list-item {
+ color: $black;
+ width: 100%;
+ height: 25px;
+ font-weight: 400;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .list-item:hover {
+ background-color: lightgrey;
+ }
+ }
+ }
+ }
+
+ &.editableText {
+ cursor: pointer;
+ background: transparent;
+ width: 100%;
+ height: 100%;
+
+ &:hover {
+ background-color: $close-red;
+ }
+ }
+
+ &.drpDownBtn {
+ cursor: pointer;
+ background: transparent;
+ border: solid 0.5px grey;
+
+ &.true {
+ background: rgba(0, 0, 0, 0.3);
+ }
+
+ .menuButton-dropdownBox {
+ position: absolute;
+ width: 150px;
+ height: 250px;
+ top: 100%;
+ background-color: #e3e3e3;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ border-radius: $standard-border-radius;
+ }
+ }
+
+ .menuButton-dropdown {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 15px;
+ grid-column: 2;
+ border-radius: 0px 7px 7px 0px;
+ width: 13px;
+ height: 100%;
+ right: 0;
+ }
+
+ .menuButton-dropdown-header {
+ width: 100%;
+ font-weight: 300;
+ padding: 5px;
+ overflow: hidden;
+ font-size: 12px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .dropbox-background {
+ width: 100vw;
+ height: 100vh;
+ top: 0;
+ z-index: 20;
+ left: 0;
+ background: transparent;
+ position: fixed;
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
new file mode 100644
index 000000000..b1d74261b
--- /dev/null
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -0,0 +1,925 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ColorState, SketchPicker } from 'react-color';
+import { Doc, StrListCast } from '../../../../fields/Doc';
+import { InkTool } from '../../../../fields/InkField';
+import { createSchema, makeInterface } from '../../../../fields/Schema';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { WebField } from '../../../../fields/URLField';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { Scripting } from "../../../util/Scripting";
+import { SelectionManager } from '../../../util/SelectionManager';
+import { ColorScheme } from '../../../util/SettingsManager';
+import { UndoManager, undoBatch } from '../../../util/UndoManager';
+import { CollectionViewType } from '../../collections/CollectionView';
+import { ContextMenu } from '../../ContextMenu';
+import { DocComponent } from '../../DocComponent';
+import { EditableView } from '../../EditableView';
+import { GestureOverlay } from '../../GestureOverlay';
+import { Colors } from '../../global/globalEnums';
+import { SetActiveInkColor, ActiveFillColor, SetActiveFillColor, ActiveInkWidth, ActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { StyleProp } from '../../StyleProvider';
+import { FieldView, FieldViewProps } from '.././FieldView';
+import { RichTextMenu } from '../formattedText/RichTextMenu';
+import { TextButton } from './textButton';
+import { ToggleButton } from './toggleButton';
+import { IButtonProps } from './ButtonInterface';
+import { FontIconBadge } from './FontIconBadge';
+import './FontIconBox.scss';
+import { undo } from 'prosemirror-history';
+import { WebBox } from '../WebBox';
+const FontIconSchema = createSchema({
+ icon: "string",
+});
+
+export enum ButtonType {
+ TextButton = "textBtn",
+ MenuButton = "menuBtn",
+ DropdownList = "drpdownList",
+ DropdownButton = "drpdownBtn",
+ ClickButton = "clickBtn",
+ DoubleButton = "dblBtn",
+ ToggleButton = "tglBtn",
+ ColorButton = "colorBtn",
+ ToolButton = "toolBtn",
+ NumberButton = "numBtn",
+ EditableText = "editableText"
+}
+
+export enum NumButtonType {
+ Slider = "slider",
+ DropdownOptions = "list",
+ Inline = "inline"
+}
+
+export interface ButtonProps extends FieldViewProps {
+ type?: ButtonType;
+}
+
+type FontIconDocument = makeInterface<[typeof FontIconSchema]>;
+const FontIconDocument = makeInterface(FontIconSchema);
+@observer
+export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(FontIconDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
+ showTemplate = (): void => {
+ const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
+ dragFactory && this.props.addDocTab(dragFactory, "add:right");
+ }
+ dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); };
+ useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); };
+
+ specificContextMenu = (): void => {
+ if (!Doc.UserDoc().noviceMode) {
+ const cm = ContextMenu.Instance;
+ cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
+ cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
+ cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
+ }
+ }
+
+ // Determining UI Specs
+ @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
+ @observable private icon = StrCast(this.dataDoc.icon, "user") as any;
+ @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen);
+ @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList);
+ @observable private type = StrCast(this.rootDoc.btnType);
+
+ /**
+ * Types of buttons in dash:
+ * - Main menu button (LHS)
+ * - Tool button
+ * - Expandable button (CollectionLinearView)
+ * - Button inside of CollectionLinearView vs. outside of CollectionLinearView
+ * - Action button
+ * - Dropdown button
+ * - Color button
+ * - Dropdown list
+ * - Number button
+ **/
+
+ _batch: UndoManager.Batch | undefined = undefined;
+ /**
+ * Number button
+ */
+ @computed get numberButton() {
+ const numBtnType: string = StrCast(this.rootDoc.numBtnType);
+ const setValue = (value: number) => {
+ // Script for running the toggle
+ const script: string = StrCast(this.rootDoc.script) + "(" + value + ")";
+ ScriptField.MakeScript(script)?.script.run();
+ };
+
+ // Script for checking the outcome of the toggle
+ const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)";
+ const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result;
+
+
+ if (numBtnType === NumButtonType.Slider) {
+ const dropdown =
+ <div
+ className="menuButton-dropdownBox"
+ onPointerDown={e => e.stopPropagation()}
+ >
+ <input type="range" step="1" min={NumCast(this.rootDoc.numBtnMin, 0)} max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult}
+ className={"menu-slider"} id="slider"
+ onPointerDown={() => this._batch = UndoManager.StartBatch("presDuration")}
+ onPointerUp={() => this._batch?.end()}
+ onChange={e => { e.stopPropagation(); setValue(Number(e.target.value)); }}
+ />
+ </div>;
+ return (
+ <div
+ className={`menuButton ${this.type} ${numBtnType}`}
+ onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
+ >
+ {checkResult}
+ {this.rootDoc.dropDownOpen ? dropdown : null}
+ </div>
+ );
+ } else if (numBtnType === NumButtonType.DropdownOptions) {
+ const items: number[] = [];
+ for (let i = 0; i < 100; i++) {
+ if (i % 2 === 0) {
+ items.push(i);
+ }
+ }
+ const list = items.map((value) => {
+ return <div className="list-item" key={`${value}`}
+ style={{
+ backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined
+ }}
+ onClick={() => setValue(value)}>
+ {value}
+ </div>;
+ });
+ return (
+ <div
+ className={`menuButton ${this.type} ${numBtnType}`}
+ >
+ <div className={`button`} onClick={action((e) => setValue(checkResult - 1))}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"minus"} />
+ </div>
+ <div
+ className={`button ${'number'}`}
+ onPointerDown={(e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
+ >
+ <input
+ style={{ width: 30 }}
+ className="button-input"
+ type="number"
+ value={checkResult}
+ onChange={action((e) => setValue(Number(e.target.value)))}
+ />
+ </div>
+ <div className={`button`} onClick={action((e) => setValue(checkResult + 1))}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"plus"} />
+ </div>
+
+ {this.rootDoc.dropDownOpen ?
+ <div>
+ <div className="menuButton-dropdownList"
+ style={{ left: "25%" }}>
+ {list}
+ </div>
+ <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
+ </div> : null}
+
+ </div>
+ );
+ } else {
+ return (
+ <div>
+
+ </div>
+ );
+ }
+
+
+ }
+
+ /**
+ * Dropdown button
+ */
+ @computed get dropdownButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ return (
+ <div className={`menuButton ${this.type} ${active}`}
+ style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
+ onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
+ <div
+ className="menuButton-dropdown"
+ style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+ <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
+ </div>
+ {this.rootDoc.dropDownOpen ?
+ <div className="menuButton-dropdownBox">
+ {/* DROPDOWN BOX CONTENTS */}
+ </div> : null}
+ </div>
+ );
+ }
+
+ /**
+ * Dropdown list
+ */
+ @computed get dropdownListButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ const script: string = StrCast(this.rootDoc.script);
+
+ let noviceList: string[] = [];
+ let text: string | undefined;
+ let dropdown = true;
+ let icon: IconProp = "caret-down";
+ let noneSelected: boolean = false;
+
+ if (script === 'setView') {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected) {
+ if (StrCast(selected.type) === DocumentType.COL) {
+ text = StrCast(selected._viewType);
+ } else {
+ dropdown = false;
+ text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type);
+ icon = Doc.toIcon(selected);
+ }
+ } else {
+ dropdown = false;
+ icon = "globe-asia";
+ text = "User Default";
+ }
+ noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
+ } else if (script === 'setFont') {
+ const selected = SelectionManager.Docs().lastElement();
+ text = StrCast((selected?.type === DocumentType.RTF ? selected : Doc.UserDoc())._fontFamily);
+ noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
+ "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
+ }
+
+ // Get items to place into the list
+ const list = this.buttonList.map((value) => {
+ if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) {
+ return;
+ }
+ const click = () => {
+ const s = ScriptField.MakeScript(script + '("' + value + '")');
+ if (s) {
+ s.script.run().result;
+ }
+ };
+ return <div className="list-item" key={`${value}`}
+ style={{
+ fontFamily: script === 'setFont' ? value : undefined,
+ backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined
+ }}
+ onClick={click}>
+ {value[0].toUpperCase() + value.slice(1)}
+ </div>;
+ });
+
+ const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+ {this.label}
+ </div>;
+
+ return (
+ <div className={`menuButton ${this.type} ${active}`}
+ style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
+ onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
+ {dropdown || noneSelected ? (null) : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
+ <div className="menuButton-dropdown-header">
+ {text && text[0].toUpperCase() + text.slice(1)}
+ </div>
+ {label}
+ {!dropdown ? (null) : <div className="menuButton-dropDown">
+ <FontAwesomeIcon icon={icon} color={color} size="sm" />
+ </div>}
+ {this.rootDoc.dropDownOpen ?
+ <div>
+ <div className="menuButton-dropdownList"
+ style={{ left: 0 }}>
+ {list}
+ </div>
+ <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
+ </div>
+ : null}
+ </div>
+ );
+ }
+
+ /**
+ * Color button
+ */
+ @computed get colorButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ const script: string = StrCast(this.rootDoc.script);
+ const scriptCheck: string = script + "(undefined, true)";
+ const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
+
+ let stroke: boolean = false;
+ let strokeIcon: any;
+ // if (script === "setStrokeColor") {
+ // stroke = true;
+ // const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
+ // const width = 20 + (checkWidth / 100) * 70;
+ // const height = 20 + (checkWidth / 100) * 70;
+ // strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />);
+ // }
+
+ const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
+ '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
+ '#FFFFFF', '#f1efeb', "transparent"];
+
+ const colorBox = (func: (color: ColorState) => void) => <SketchPicker
+ disableAlpha={!stroke}
+ onChange={func} color={boolResult ? boolResult : "#FFFFFF"}
+ presetColors={colorOptions} />;
+ const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+ {this.label}
+ </div>;
+
+ const dropdownCaret = <div
+ className="menuButton-dropDown"
+ style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+ <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
+ </div>;
+
+ const click = (value: ColorState) => {
+ const hex: string = value.hex;
+ const s = ScriptField.MakeScript(script + '("' + hex + '", false)');
+ if (s) {
+ undoBatch(() => s.script.run().result)();
+ }
+ };
+ return (
+ <div className={`menuButton ${this.type} ${active}`}
+ style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
+ onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}
+ onPointerDown={e => e.stopPropagation()}>
+ {stroke ? strokeIcon : <><FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ <div className="colorButton-color"
+ style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }}
+ ></div></>}
+ {label}
+ {/* {dropdownCaret} */}
+ {this.rootDoc.dropDownOpen ?
+ <div>
+ <div className="menuButton-dropdownBox"
+ onPointerDown={e => e.stopPropagation()}
+ onClick={e => e.stopPropagation()}>
+ {colorBox(click)}
+ </div>
+ <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
+ </div>
+ : null}
+ </div>
+ );
+ }
+
+ @computed get toggleButton() {
+ // Determine the type of toggle button
+ const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle);
+ const buttonText: string = StrCast(this.rootDoc.buttonText);
+ // Colors
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ // Button label
+ const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+ {this.label}
+ </div>;
+
+ if (switchToggle) {
+ return (
+ <div className={`menuButton ${this.type} ${'switch'}`}>
+ {buttonText ? buttonText : null}
+ <label className="switch">
+ <input type="checkbox"
+ checked={backgroundColor === Colors.MEDIUM_BLUE}
+ />
+ <span className="slider round"></span>
+ </label>
+ </div>
+ );
+ } else {
+ return (
+ <div className={`menuButton ${this.type}`}
+ style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {label}
+ </div>
+ );
+ }
+ }
+
+
+
+ /**
+ * Default
+ */
+ @computed get defaultButton() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ return (
+ <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu}
+ style={{ backgroundColor: "transparent", borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
+ <div className="menuButton-wrap">
+ <FontAwesomeIcon className={`menuButton-icon-${this.type}`} icon={this.icon} color={"black"} size={"sm"} />
+ {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
+ </div>
+ </div>
+ );
+ }
+
+ @computed get editableText() {
+ // Script for running the toggle
+ const script: string = StrCast(this.rootDoc.script);
+
+ // Script for checking the outcome of the toggle
+ const checkScript: string = StrCast(this.rootDoc.script) + "('', true)";
+
+ // Function to run the script
+ const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result;
+
+ const setValue = (value: string, shiftDown?: boolean): boolean => {
+ ScriptField.MakeScript(script + "('" + value + "')")?.script.run();
+ return true;
+ };
+ return (
+ <div className="menuButton editableText">
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} />
+ <div style={{ width: "calc(100% - .875em)", paddingLeft: "4px" }}>
+ <EditableView GetValue={() => checkResult} SetValue={setValue} contents={checkResult} />
+ </div>
+ </div>
+ );
+ }
+
+
+ render() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark;
+
+ const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+ {this.label}
+ </div>;
+
+ const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: "transparent" }}>
+ {this.label}
+ </div>;
+
+ const buttonProps: IButtonProps = {
+ type: this.type,
+ rootDoc: this.rootDoc,
+ label: label,
+ backgroundColor: backgroundColor,
+ icon: this.icon,
+ color: color
+ }
+
+ const buttonText = StrCast(this.rootDoc.buttonText);
+
+ // TODO:glr Add label of button type
+ let button = this.defaultButton;
+
+ switch (this.type) {
+ case ButtonType.TextButton:
+ button = (
+ <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {buttonText ?
+ <div className="button-text">
+ {buttonText}
+ </div>
+ : null}
+ {label}
+ </div>
+ );
+ // button = <TextButton {...buttonProps}></TextButton>
+ break;
+ case ButtonType.EditableText:
+ console.log("Editable text");
+ button = this.editableText;
+ break;
+ case ButtonType.NumberButton:
+ button = this.numberButton;
+ break;
+ case ButtonType.DropdownButton:
+ button = this.dropdownButton;
+ break;
+ case ButtonType.DropdownList:
+ button = this.dropdownListButton;
+ break;
+ case ButtonType.ColorButton:
+ button = this.colorButton;
+ break;
+ case ButtonType.ToolButton:
+ button = (
+ <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {label}
+ </div>
+ );
+ break;
+ case ButtonType.ToggleButton:
+ button = this.toggleButton;
+ // button = <ToggleButton {...buttonProps}></ToggleButton>
+ break;
+ case ButtonType.ClickButton:
+ button = (
+ <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1 }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {label}
+ </div>
+ );
+ break;
+ case ButtonType.MenuButton:
+ const trailsIcon = <img src={`/assets/${"presTrails.png"}`}
+ style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />;
+ button = (
+ <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor }}>
+ {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
+ {menuLabel}
+ <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
+ </div >
+ );
+ break;
+ default:
+ break;
+ }
+
+ return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
+ <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+ {button}
+ </Tooltip>;
+ }
+}
+
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setView(view: string) {
+ const selected = SelectionManager.Docs().lastElement();
+ selected ? selected._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
+});
+
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult) {
+ if (selected) {
+ console.log("[Background] (selected): " + StrCast(selected._backgroundColor));
+ return selected._backgroundColor;
+ } else {
+ return "#FFFFFF";
+ }
+ }
+ if (selected?.type === DocumentType.INK) selected.fillColor = color;
+ if (selected) selected._backgroundColor = color;
+ Doc.UserDoc()._fontColor = color;
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setHeaderColor(color?: string, checkResult?: boolean) {
+ Doc.SharingDoc().userColor = undefined;
+ Doc.GetProto(Doc.SharingDoc()).userColor = color;
+ Doc.UserDoc().showTitle = color === "transparent" ? undefined : StrCast(Doc.UserDoc().showTitle, "creationDate");
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
+ const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ if (checkResult && selected) {
+ if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
+});
+
+/** TEXT
+ * setFont
+ * setFontSize
+ * toggleBold
+ * toggleUnderline
+ * toggleItalic
+ * setAlignment
+ * toggleBold
+ * toggleItalic
+ * toggleUnderline
+ **/
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) {
+ SelectionManager.Docs().map(doc => doc._fontFamily = font);
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ editorView?.state && RichTextMenu.Instance.setFontFamily(font, editorView);
+ Doc.UserDoc()._fontFamily = font;
+ return Doc.UserDoc()._fontFamily;
+});
+
+Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") {
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
+ switch (info) {
+ case "family": return style?.activeColors[0];
+ case "size": return style?.activeSizes[0];
+ case "color": return style?.activeColors[0];
+ case "highlight": return style?.activeHighlights[0];
+ }
+});
+
+Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) {
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ let active: string;
+ if (editorView) {
+ active = editorView?.state && RichTextMenu.Instance.getActiveAlignment();
+ } else {
+ active = StrCast(Doc.UserDoc().textAlign);
+ }
+ if (active === align) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ SelectionManager.Docs().map(doc => doc.textAlign = align);
+ switch (align) {
+ case "left":
+ editorView?.state && RichTextMenu.Instance.alignLeft(editorView, editorView.dispatch);
+ break;
+ case "center":
+ editorView?.state && RichTextMenu.Instance.alignCenter(editorView, editorView.dispatch);
+ break;
+ case "right":
+ editorView?.state && RichTextMenu.Instance.alignRight(editorView, editorView.dispatch);
+ break;
+ default:
+ break;
+ }
+ Doc.UserDoc().textAlign = align;
+});
+
+Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", checkResult?: boolean) {
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
+ console.log(active, mapStyle);
+ if (active === mapStyle) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ if (editorView) {
+ const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
+ if (active === mapStyle) {
+ console.log("set bullet list");
+ editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: "" }));
+ } else {
+ editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: mapStyle }));
+ }
+ }
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+
+ if (checkResult) {
+ if (selected) {
+ console.log("[Font color] (selected): " + StrCast(selected._fontColor));
+ return selected._fontColor;
+ } else {
+ console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor));
+ return Doc.UserDoc()._fontColor;
+ }
+ }
+
+ if (selected) {
+ selected._fontColor = color;
+ if (color) {
+ editorView?.state && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
+ }
+ }
+ Doc.UserDoc()._fontColor = color;
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+
+ if (checkResult) {
+ if (selected) {
+ return selected._fontHighlight;
+ } else {
+ return Doc.UserDoc()._fontHighlight;
+ }
+ }
+ if (selected) {
+ selected._fontColor = color;
+ if (color) {
+ editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch);
+ }
+ }
+ Doc.UserDoc()._fontHighlight = color;
+});
+
+
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setFontSize(size: string, checkResult?: boolean) {
+ if (checkResult) {
+ const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10);
+ return size;
+ }
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ editorView?.state && RichTextMenu.Instance.setFontSize(Number(size), editorView);
+ Doc.UserDoc()._fontSize = size + "px";
+});
+
+Scripting.addGlobal(function toggleBold(checkResult?: boolean) {
+ if (checkResult) {
+ if (Doc.UserDoc().bold) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ if (editorView) {
+ console.log("editorView");
+ editorView?.state && RichTextMenu.Instance.toggleBold(editorView, true);
+ }
+ SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.bold = !doc.bold);
+ Doc.UserDoc().bold = !Doc.UserDoc().bold;
+ return Doc.UserDoc().bold;
+});
+
+Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) {
+ if (checkResult) {
+ if (Doc.UserDoc().underline) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ if (editorView) {
+ console.log("editorView");
+ editorView?.state && RichTextMenu.Instance.toggleUnderline(editorView, true);
+ }
+ SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.underline = !doc.underline);
+ Doc.UserDoc().underline = !Doc.UserDoc().underline;
+ return Doc.UserDoc().underline;
+});
+
+Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
+ if (checkResult) {
+ if (Doc.UserDoc().italic) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ if (editorView) {
+ console.log("editorView");
+ editorView?.state && RichTextMenu.Instance.toggleItalic(editorView, true);
+ }
+ SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.italic = !doc.italic);
+ Doc.UserDoc().italic = !Doc.UserDoc().italic;
+ return Doc.UserDoc().italic;
+});
+
+
+
+
+/** INK
+ * setActiveInkTool
+ * setStrokeWidth
+ * setStrokeColor
+ **/
+
+Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) {
+ if (checkResult) {
+ if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ if (tool === "circle") {
+ Doc.UserDoc().activeInkTool = "pen";
+ GestureOverlay.Instance.InkShape = tool;
+ } else if (tool === "square") {
+ Doc.UserDoc().activeInkTool = "pen";
+ GestureOverlay.Instance.InkShape = tool;
+ } else if (tool === "line") {
+ Doc.UserDoc().activeInkTool = "pen";
+ GestureOverlay.Instance.InkShape = tool;
+ } else if (tool) {
+ if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) {
+ GestureOverlay.Instance.InkShape = "";
+ Doc.UserDoc().activeInkTool = InkTool.None;
+ } else {
+ Doc.UserDoc().activeInkTool = tool;
+ }
+ } else {
+ Doc.UserDoc().activeInkTool = InkTool.None;
+ }
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setFillColor(color?: string, checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult) {
+ if (selected?.type === DocumentType.INK) {
+ return StrCast(selected._backgroundColor);
+ }
+ return ActiveFillColor();
+ }
+ SetActiveFillColor(StrCast(color));
+ SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.fillColor = color);
+});
+
+Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean) {
+ if (checkResult) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected?.type === DocumentType.INK) {
+ return NumCast(selected.strokeWidth);
+ }
+ return ActiveInkWidth();
+ }
+ SetActiveInkWidth(width.toString());
+ SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.strokeWidth = Number(width));
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolean) {
+ if (checkResult) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected?.type === DocumentType.INK) {
+ return StrCast(selected.color);
+ }
+ return ActiveInkColor();
+ }
+ SetActiveInkColor(StrCast(color));
+ SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.color = String(color));
+});
+
+
+/** WEB
+ * webSetURL
+ **/
+Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) {
+ const selected = SelectionManager.Views().lastElement();
+ if (selected?.rootDoc.type === DocumentType.WEB) {
+ if (checkResult) {
+ return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
+ }
+ (selected.ComponentView as WebBox).submitURL(url);
+ //selected.rootDoc.data = new WebField(url);
+ }
+});
+Scripting.addGlobal(function webForward() {
+ const selected = SelectionManager.Views().lastElement();
+ if (selected?.rootDoc.type === DocumentType.WEB) {
+ (selected.ComponentView as WebBox).forward();
+ }
+});
+Scripting.addGlobal(function webBack() {
+ const selected = SelectionManager.Views().lastElement();
+ if (selected?.rootDoc.type === DocumentType.WEB) {
+ (selected.ComponentView as WebBox).back();
+ }
+});
+
+
+/** Schema
+ * toggleSchemaPreview
+ **/
+Scripting.addGlobal(function toggleSchemaPreview(checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ console.log(selected && selected.title);
+ if (checkResult && selected) {
+ const result: boolean = NumCast(selected.schemaPreviewWidth) > 0;
+ if (result) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ else if (selected) {
+ if (NumCast(selected.schemaPreviewWidth) > 0) {
+ selected.schemaPreviewWidth = 200;
+ } else {
+ selected.schemaPreviewWidth = 0;
+ }
+ }
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
new file mode 100644
index 000000000..1809f4e2e
--- /dev/null
+++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
@@ -0,0 +1,77 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React, { Component } from 'react';
+import { BoolCast, StrCast } from '../../../../../fields/Types';
+import { IButtonProps } from '../ButtonInterface';
+import { ColorState, SketchPicker } from 'react-color';
+import { ScriptField } from '../../../../../fields/ScriptField';
+import { Doc } from '../../../../../fields/Doc';
+
+export class ColorDropdown extends Component<IButtonProps> {
+ render() {
+ const active: string = StrCast(this.props.rootDoc.dropDownOpen);
+
+ const script: string = StrCast(this.props.rootDoc.script);
+ const scriptCheck: string = script + "(undefined, true)";
+ const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
+
+ let stroke: boolean = false;
+ let strokeIcon: any;
+ // if (script === "setStrokeColor") {
+ // stroke = true;
+ // const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
+ // const width = 20 + (checkWidth / 100) * 70;
+ // const height = 20 + (checkWidth / 100) * 70;
+ // strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />);
+ // }
+
+ const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
+ '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
+ '#FFFFFF', '#f1efeb'];
+
+ const colorBox = (func: (color: ColorState) => void) => <SketchPicker
+ disableAlpha={!stroke}
+ onChange={func} color={boolResult ? boolResult : "#FFFFFF"}
+ presetColors={colorOptions} />;
+ const label = !this.props.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: "absolute" }}>
+ {this.props.label}
+ </div>;
+
+ const dropdownCaret = <div
+ className="menuButton-dropDown"
+ style={{ borderBottomRightRadius: active ? 0 : undefined }}>
+ <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" />
+ </div>;
+
+ const click = (value: ColorState) => {
+ const hex: string = value.hex;
+ const s = ScriptField.MakeScript(script + '("' + hex + '", false)');
+ if (s) {
+ s.script.run().result;
+ }
+ };
+ return (
+ <div className={`menuButton ${this.props.type} ${active}`}
+ style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }}
+ onClick={() => this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen}
+ onPointerDown={e => e.stopPropagation()}>
+ {stroke ? strokeIcon : <><FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
+ <div className="colorButton-color"
+ style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }}
+ ></div></>}
+ {label}
+ {/* {dropdownCaret} */}
+ {this.props.rootDoc.dropDownOpen ?
+ <div>
+ <div className="menuButton-dropdownBox"
+ onPointerDown={e => e.stopPropagation()}
+ onClick={e => e.stopPropagation()}>
+ {colorBox(click)}
+ </div>
+ <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.props.rootDoc.dropDownOpen = false; }} />
+ </div>
+ : null}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/colorDropdown/index.ts b/src/client/views/nodes/button/colorDropdown/index.ts
new file mode 100644
index 000000000..1147d6457
--- /dev/null
+++ b/src/client/views/nodes/button/colorDropdown/index.ts
@@ -0,0 +1 @@
+export * from './ColorDropdown'; \ No newline at end of file
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
new file mode 100644
index 000000000..e18590a95
--- /dev/null
+++ b/src/client/views/nodes/button/textButton/TextButton.tsx
@@ -0,0 +1,17 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React, { Component } from 'react';
+import { BoolCast } from '../../../../../fields/Types';
+import { IButtonProps } from '../ButtonInterface';
+
+export class TextButton extends Component<IButtonProps> {
+ render() {
+ const type = this.props.type;
+ // Determine the type of toggle button
+ const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle);
+
+ return (<div className={`menuButton ${this.props.type}`} style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
+ {this.props.label}
+ </div>);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/textButton/index.ts b/src/client/views/nodes/button/textButton/index.ts
new file mode 100644
index 000000000..01d62eb7e
--- /dev/null
+++ b/src/client/views/nodes/button/textButton/index.ts
@@ -0,0 +1 @@
+export * from './TextButton'; \ No newline at end of file
diff --git a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx b/src/client/views/nodes/button/toggleButton/ToggleButton.tsx
new file mode 100644
index 000000000..dca6487d8
--- /dev/null
+++ b/src/client/views/nodes/button/toggleButton/ToggleButton.tsx
@@ -0,0 +1,34 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React, { Component } from 'react';
+import { BoolCast } from '../../../../../fields/Types';
+import { Colors } from '../../../global/globalEnums';
+import { IButtonProps } from '../ButtonInterface';
+
+export class ToggleButton extends Component<IButtonProps> {
+ render() {
+ const type = this.props.type;
+ // Determine the type of toggle button
+ const switchToggle: boolean = BoolCast(this.props.rootDoc.switchToggle);
+
+ if (switchToggle) {
+ return (
+ <div className={`menuButton ${type} ${'switch'}`}>
+ <label className="switch">
+ <input type="checkbox"
+ checked={this.props.backgroundColor === Colors.MEDIUM_BLUE}
+ />
+ <span className="slider round"></span>
+ </label>
+ </div>
+ );
+ } else {
+ return (
+ <div className={`menuButton ${type}`}
+ style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${type}`} icon={this.props.icon} color={this.props.color} />
+ {this.props.label}
+ </div>
+ );
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/button/toggleButton/index.ts b/src/client/views/nodes/button/toggleButton/index.ts
new file mode 100644
index 000000000..cdb9c527c
--- /dev/null
+++ b/src/client/views/nodes/button/toggleButton/index.ts
@@ -0,0 +1 @@
+export * from './ToggleButton'; \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 62f65cdae..34908e54b 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -82,7 +82,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
if (this._dashDoc) {
- const dashVal = this._dashDoc[DataSym][this._fieldKey] ?? this._dashDoc[this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
+ const dashVal = this._dashDoc[this._fieldKey] ?? this._dashDoc[DataSym][this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
const fval = dashVal instanceof List ? dashVal.join(this.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
const boolVal = Cast(fval, "boolean", null);
const strVal = Field.toString(fval as Field) || "";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 3cedab1a4..4134e3c67 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -67,13 +67,26 @@ audiotag:hover {
.formattedTextBox-sidebar-handle {
position: absolute;
top: 0;
+ left: 17px;
//top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views
- width: 10px;
- height: 100%;
- max-height: 35px;
- background: lightgray;
- border-radius: 20px;
+ width: 17px;
+ height: 17px;
+ border-radius: 3px;
+ color: white;
+ background: $medium-gray;
+ border-radius: 5px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
cursor:grabbing;
+ box-shadow: $standard-box-shadow;
+ // transition: 0.2s;
+ opacity: 0.3;
+ &:hover{
+ opacity: 1 !important;
+ filter: brightness(0.85);
+ }
+
}
.formattedTextBox-sidebar,
@@ -414,12 +427,7 @@ footnote::after {
.formattedTextBox-sidebar-handle {
position: absolute;
- top: 0;
- //top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views
- width: 10px;
- height: 35px;
background: lightgray;
- border-radius: 20px;
cursor: grabbing;
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 140d39929..e7a44f113 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, reaction, runInAction, observable } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap, selectAll } from "prosemirror-commands";
import { history } from "prosemirror-history";
@@ -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';
@@ -63,11 +63,12 @@ import { SummaryView } from "./SummaryView";
import applyDevTools = require("prosemirror-dev-tools");
import React = require("react");
import { SidebarAnnos } from '../../SidebarAnnos';
+import { Colors } from '../../global/globalEnums';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
const translateGoogleApi = require("translate-google-api");
export interface FormattedTextBoxProps {
makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
- hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yPadding?: number;
noSidebar?: boolean;
@@ -120,7 +121,7 @@ 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"]); }
@@ -214,10 +215,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = "marquee";
AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
- this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
- console.log("highlight")
+ this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, 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.
@@ -254,7 +255,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("#")) {
@@ -371,7 +372,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);
});
@@ -430,8 +431,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
protected createDropTarget = (ele: HTMLDivElement) => {
this.ProseRef = ele;
+ this.setupEditor(this.config, this.props.fieldKey);
this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
}
@undoBatch
@@ -535,7 +538,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" });
@@ -544,11 +547,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);
@@ -565,14 +573,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const cm = ContextMenu.Instance;
const changeItems: ContextMenuProps[] = [];
- changeItems.push({ description: "plain", event: undoBatch(() => Doc.setNativeView(this.rootDoc)), icon: "eye" });
+ changeItems.push({
+ description: "plain", event: undoBatch(() => {
+ Doc.setNativeView(this.rootDoc);
+ this.layoutDoc.autoHeightMargins = undefined;
+ }), icon: "eye"
+ });
+ changeItems.push({
+ description: "metadata", event: undoBatch(() => {
+ this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
+ this.rootDoc.layoutKey = "layout_meta";
+ setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50);
+ }), icon: "eye"
+ });
const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
DocListCast(noteTypesDoc?.data).forEach(note => {
+ const icon: IconProp = StrCast(note.icon) as IconProp;
changeItems.push({
description: StrCast(note.title), event: undoBatch(() => {
+ this.layoutDoc.autoHeightMargins = undefined;
Doc.setNativeView(this.rootDoc);
DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
- }), icon: "eye"
+ }), icon: icon
});
});
!Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
@@ -704,8 +726,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
- const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) });
- const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
+ const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
@@ -726,6 +748,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[] = [];
@@ -852,8 +877,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
- this.setupEditor(this.config, this.props.fieldKey);
-
this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
{ fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
@@ -874,7 +897,7 @@ 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),
@@ -893,7 +916,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}, { fireImmediately: true }
);
quickScroll = undefined;
- this.tryUpdateScrollHeight();
+ setTimeout(this.tryUpdateScrollHeight, 10);
}
pushToGoogleDoc = async () => {
@@ -1042,7 +1065,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);
@@ -1132,8 +1155,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
selectOnLoad && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
- if (!this._editorView!.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
- this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+ if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+ this._editorView.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
}
}
@@ -1202,7 +1225,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))));
@@ -1348,6 +1371,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._undoTyping = undefined;
return wasUndoing;
}
+
@action
onBlur = (e: any) => {
FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
@@ -1394,6 +1418,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)));
@@ -1423,16 +1455,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- tryUpdateScrollHeight() {
+ tryUpdateScrollHeight = () => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const proseHeight = !this.ProseRef ? 0 : Array.from(this.ProseRef.children[0].children).reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
- const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
- if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
- setScrollHeight();
- } 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...
+ const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
+ if (children) {
+ const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+ if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
+ if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
+ setScrollHeight();
+ } 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...
+ }
+ }
}
}
}
@@ -1456,11 +1493,18 @@ 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}
- style={{
- left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
- background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
- }} />;
+ const color = !annotated ? Colors.WHITE : Colors.BLACK;
+ const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
+ return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ style={{
+ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
+ backgroundColor: backgroundColor,
+ color: color,
+ opacity: annotated ? 1 : undefined
+ }} >
+ <FontAwesomeIcon icon={"comment-alt"} />
+ </div>;
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
@@ -1468,16 +1512,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}
@@ -1490,7 +1535,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}
@@ -1512,8 +1556,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
render() {
TraceMobx();
const selected = this.props.isSelected();
- const active = this.isContentActive();
- const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+ const active = this.props.isContentActive();
+ const scale = (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);
if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
@@ -1522,16 +1566,17 @@ 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" : "";
- return (
+ const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ return (styleFromString?.height === "0px" ? (null) :
<div className="formattedTextBox-cont"
- onWheel={e => this.isContentActive() && e.stopPropagation()}
+ onWheel={e => this.props.isContentActive() && e.stopPropagation()}
style={{
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' >
+ ...styleFromString
}}>
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
@@ -1569,7 +1614,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
+ {(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>
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 c94e93541..8afa0f6b5 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -2,6 +2,7 @@
.button-dropdown-wrapper {
position: relative;
+ display: flex;
.dropdown-button {
width: 15px;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 2523dda38..3919fbf94 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -37,11 +37,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
- private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
- private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
- private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
- private fontColors: (string | undefined)[];
- private highlightColors: (string | undefined)[];
@observable private collapsed: boolean = false;
@observable private boldActive: boolean = false;
@@ -76,70 +71,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._canFade = false;
//this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
runInAction(() => this.Pinned = true);
-
- this.fontSizeOptions = [
- { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48px", command: this.changeFontSize },
- { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72px", command: this.changeFontSize },
- { mark: null, title: "", label: "...", command: unimplementedFunction, hidden: true },
- { mark: null, title: "", label: "13px", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
- ];
-
- this.fontFamilyOptions = [
- { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
- { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
- { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
- { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
- { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
- { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
- { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
- { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
- // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
- ];
-
- this.listTypeOptions = [
- { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
- { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
- { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
- { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "<none>", command: this.changeListType },
- //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
- ];
-
- this.fontColors = [
- DarkPastelSchemaPalette.get("pink2"),
- DarkPastelSchemaPalette.get("purple4"),
- DarkPastelSchemaPalette.get("bluegreen1"),
- DarkPastelSchemaPalette.get("yellow4"),
- DarkPastelSchemaPalette.get("red2"),
- DarkPastelSchemaPalette.get("bluegreen7"),
- DarkPastelSchemaPalette.get("bluegreen5"),
- DarkPastelSchemaPalette.get("orange1"),
- "#757472",
- "#000"
- ];
-
- this.highlightColors = [
- PastelSchemaPalette.get("pink2"),
- PastelSchemaPalette.get("purple4"),
- PastelSchemaPalette.get("bluegreen1"),
- PastelSchemaPalette.get("yellow4"),
- PastelSchemaPalette.get("red2"),
- PastelSchemaPalette.get("bluegreen7"),
- PastelSchemaPalette.get("bluegreen5"),
- PastelSchemaPalette.get("orange1"),
- "white",
- "transparent"
- ];
}
componentDidMount() {
@@ -277,6 +208,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const found = new Set<Mark>();
const { from, to } = state.selection as TextSelection;
state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+ console.log("Marks: " + found);
return found;
}
@@ -347,104 +279,58 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
});
}
- createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
- const self = this;
- function onClick(e: React.PointerEvent) {
- e.preventDefault();
- e.stopPropagation();
- 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);
- }, "rich text menu command");
- self.setActiveMarkButtons(self.getActiveMarksOnSelection());
- }
-
- return (
- <Tooltip title={<div className="dash-tooltip">{title}</div>} key={title} placement="bottom">
- <button className={"antimodeMenu-button" + (isActive ? " active" : "")} onPointerDown={onClick}>
- <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
- </button>
- </Tooltip>
- );
+ toggleBold = (view: EditorView, forceBool?: boolean) => {
+ const mark = view.state.schema.mark(view.state.schema.marks.strong, { strong: forceBool });
+ this.setMark(mark, view.state, view.dispatch, false);
+ view.focus();
}
- createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => void): JSX.Element {
- const items = options.map(({ title, label, hidden, style }) => {
- if (hidden) {
- return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
- }
- return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
- });
-
- const self = this;
- function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
- e.stopPropagation();
- e.preventDefault();
- self.TextView?.endUndoTypingBatch();
- UndoManager.RunInBatch(() => {
- options.forEach(({ label, mark, command }) => {
- if (e.target.value === label && mark) {
- if (!self.TextView?.props.isSelected(true)) {
- switch (mark.type) {
- case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
- case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "px"); break;
- }
- }
- else self.view && mark && command(mark, self.view);
- }
- });
- }, "text mark dropdown");
- }
-
- return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
- <select onChange={onChange} value={activeOption}>{items}</select>
- </Tooltip>;
+ toggleUnderline = (view: EditorView, forceBool?: boolean) => {
+ const mark = view.state.schema.mark(view.state.schema.marks.underline, { underline: forceBool });
+ this.setMark(mark, view.state, view.dispatch, false);
+ view.focus();
}
- createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
- const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>";
- const items = options.map(({ title, label, hidden, style }) => {
- if (hidden) {
- return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
- }
- return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
- });
-
- const self = this;
- function onChange(val: string) {
- self.TextView.endUndoTypingBatch();
- options.forEach(({ label, node, command }) => {
- if (val === label && node) {
- if (self.TextView.props.isSelected(true)) {
- UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
- setter(val);
- }
- }
- });
- }
-
- return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
- <select value={activeOption} onChange={e => onChange(e.target.value)}>{items}</select>
- </Tooltip>;
+ toggleItalic = (view: EditorView, forceBool?: boolean) => {
+ const mark = view.state.schema.mark(view.state.schema.marks.em, { em: forceBool });
+ this.setMark(mark, view.state, view.dispatch, false);
+ view.focus();
}
- changeFontSize = (mark: Mark, view: EditorView) => {
- const fmark = view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize });
+
+ setFontSize = (size: number, view: EditorView) => {
+ const fmark = view.state.schema.marks.pFontSize.create({ fontSize: size });
this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
view.focus();
this.updateMenu(view, undefined, this.props);
}
- changeFontFamily = (mark: Mark, view: EditorView) => {
- const fmark = view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family });
+ setFontFamily = (family: string, view: EditorView) => {
+ const fmark = view.state.schema.marks.pFontFamily.create({ family: family });
this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
view.focus();
this.updateMenu(view, undefined, this.props);
}
+ setHighlight(color: String, view: EditorView, dispatch: any) {
+ const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
+ if (view.state.selection.empty) return false;
+ view.focus();
+ this.setMark(highlightMark, view.state, dispatch, false);
+ }
+
+ setColor(color: String, view: EditorView, dispatch: any) {
+ const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+ if (view.state.selection.empty) {
+ dispatch(view.state.tr.addStoredMark(colorMark));
+ return false;
+ }
+ this.setMark(colorMark, view.state, dispatch, true);
+ view.focus();
+ }
+
// TODO: remove doesn't work
- //remove all node type and apply the passed-in one to the selected text
+ // remove all node type and apply the passed-in one to the selected text
changeListType = (nodeType: Node | undefined) => {
if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
@@ -490,25 +376,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
return true;
}
- alignCenter = (state: EditorState<any>, dispatch: any) => {
- return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);
+ alignCenter = (view: EditorView, dispatch: any) => {
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "center", dispatch);
}
- alignLeft = (state: EditorState<any>, dispatch: any) => {
- return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);
+ alignLeft = (view: EditorView, dispatch: any) => {
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "left", dispatch);
}
- alignRight = (state: EditorState<any>, dispatch: any) => {
- return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "right", dispatch);
+ alignRight = (view: EditorView, dispatch: any) => {
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "right", dispatch);
}
- alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ alignParagraphs(view: EditorView, align: "left" | "right" | "center", dispatch: any) {
+ var tr = view.state.tr;
+ view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks);
return false;
}
+ view.focus();
return true;
});
+ view.focus();
dispatch?.(tr);
return true;
}
@@ -597,47 +485,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
_brushNameRef = React.createRef<HTMLInputElement>();
- createBrushButton() {
- const self = this;
- const onBrushClick = (e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
- self.TextView.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush");
- };
-
- let label = "Stored marks: ";
- if (this.brushMarks && this.brushMarks.size > 0) {
- this.brushMarks.forEach((mark: Mark) => {
- const markType = mark.type;
- label += markType.name;
- label += ", ";
- });
- label = label.substring(0, label.length - 2);
- } else {
- label = "No marks are currently stored";
- }
-
- //onPointerDown={onBrushClick}
-
- const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom">
- <button className="antimodeMenu-button" onClick={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
- <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
- </button>
- </Tooltip>;
-
- const dropdownContent =
- <div className="dropdown">
- <p>{label}</p>
- <button onPointerDown={this.clearBrush}>Clear brush</button>
- <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress} />
- </div>;
-
- return (
- <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} openDropdownOnButton={false} dropdownContent={dropdownContent} />
- );
- }
-
@action
clearBrush() {
RichTextMenu.Instance.brushMarks = new Set();
@@ -666,123 +513,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
- @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
- @action setActiveColor(color: string) { this.activeFontColor = color; }
get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
get TextViewFieldKey() { return this.TextView?.props.fieldKey; }
- createColorButton() {
- const self = this;
- function onColorClick(e: React.PointerEvent) {
- e.preventDefault();
- e.stopPropagation();
- self.TextView.endUndoTypingBatch();
- if (self.view) {
- UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
- self.view.focus();
- self.updateMenu(self.view, undefined, self.props);
- }
- }
- function changeColor(e: React.PointerEvent, color: string) {
- e.preventDefault();
- e.stopPropagation();
- self.setActiveColor(color);
- self.TextView.endUndoTypingBatch();
- if (self.view) {
- UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
- self.view.focus();
- self.updateMenu(self.view, undefined, self.props);
- }
- }
-
- // onPointerDown={onColorClick}
- const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="palette" size="lg" />
- <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
- </button>
- </Tooltip>;
-
- const dropdownContent =
- <div className="dropdown" >
- <p>Change font color:</p>
- <div className="color-wrapper">
- {this.fontColors.map(color => {
- if (color) {
- return this.activeFontColor === color ?
- <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
- <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
- }
- })}
- </div>
- </div>;
-
- return (
- <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
- );
- }
- public insertColor(color: String, state: EditorState<any>, dispatch: any) {
- const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
- if (state.selection.empty) {
- dispatch(state.tr.addStoredMark(colorMark));
- return false;
- }
- this.setMark(colorMark, state, dispatch, true);
- }
- @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
@action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
- createHighlighterButton() {
- const self = this;
- function onHighlightClick(e: React.PointerEvent) {
- e.preventDefault();
- e.stopPropagation();
- self.TextView.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highligher");
- }
- function changeHighlight(e: React.PointerEvent, color: string) {
- e.preventDefault();
- e.stopPropagation();
- self.setActiveHighlight(color);
- self.TextView.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
- }
-
- //onPointerDown={onHighlightClick}
- const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button" key="highilghter-button" >
- <FontAwesomeIcon icon="highlighter" size="lg" />
- <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
- </button>
- </Tooltip>;
-
- const dropdownContent =
- <div className="dropdown">
- <p>Change highlight color:</p>
- <div className="color-wrapper">
- {this.highlightColors.map(color => {
- if (color) {
- return this.activeHighlightColor === color ?
- <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
- <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
- }
- })}
- </div>
- </div>;
-
- return (
- <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
- );
- }
- insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
- toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
- }
-
- @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
@action setCurrentLink(link: string) { this.currentLink = link; }
createLinkButton() {
@@ -821,14 +559,14 @@ 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) {
const anchor1 = await Cast(linkDoc.anchor1, Doc);
const anchor2 = await Cast(linkDoc.anchor2, Doc);
- const currentDoc = SelectionManager.Views().length && SelectionManager.Views()[0].props.Document;
+ const currentDoc = SelectionManager.Docs().lastElement();
if (currentDoc && anchor1 && anchor2) {
if (Doc.AreProtosEqual(currentDoc, anchor1)) {
return StrCast(anchor2.title);
@@ -864,8 +602,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));
});
}
@@ -921,95 +659,70 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return ref_node;
}
- @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
- @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
-
- @action
- toggleMenuPin = (e: React.MouseEvent) => {
- Doc.UserDoc()["menuRichText-pinned"] = this.Pinned = !this.Pinned;
- if (!this.Pinned) {
- this.fadeOut(true);
- }
- }
-
- @action
- protected toggleCollapse = (e: React.MouseEvent) => {
- this.collapsed = !this.collapsed;
- setTimeout(() => {
- const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
- RichTextMenu.Instance.jumpTo(x, this._top, true);
- }, 0);
- }
-
render() {
- TraceMobx();
- const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
- //!this.collapsed ? this.getDragger() : (null),
- // !this.Pinned ? (null) : <div key="frag1"> {[
- // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- // <div className="richTextMenu-divider" key="divider" />
- // ]}</div>,
- this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- this.createColorButton(),
- this.createHighlighterButton(),
- this.createLinkButton(),
- this.createBrushButton(),
- <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),
- this.createButton("indent", "Inset More", undefined, this.insetParagraph),
- this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
- this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
- this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
- ]}</div>;
-
- 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="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);
- })),
- this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
- this.activeFontFamily = val;
- SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val);
- })),
- <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>
- {/* <div key="collapser">
- {<div key="collapser">
- <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
- <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
- </button>
- </div> }
- <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- </button>
- </div> */}
- </div>;
-
- return (
- <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} >
- {this.getElementWithRows([row1, row2], 2, false)}
- </div>
- );
+ return null;
+ // TraceMobx();
+ // const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+ // //!this.collapsed ? this.getDragger() : (null),
+ // // !this.Pinned ? (null) : <div key="frag1"> {[
+ // // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+ // // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+ // // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+ // // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+ // // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+ // // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+ // // <div className="richTextMenu-divider" key="divider" />
+ // // ]}</div>,
+ // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+ // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+ // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+ // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+ // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+ // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+ // this.createColorButton(),
+ // this.createHighlighterButton(),
+ // this.createLinkButton(),
+ // this.createBrushButton(),
+ // <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),
+ // this.createButton("indent", "Inset More", undefined, this.insetParagraph),
+ // this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
+ // this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
+ // this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
+ // ]}</div>;
+
+ // 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="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);
+ // })),
+ // this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
+ // this.activeFontFamily = val;
+ // SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val);
+ // })),
+ // <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>
+ // {/* <div key="collapser">
+ // {<div key="collapser">
+ // <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
+ // <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
+ // </button>
+ // </div> }
+ // <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
+ // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+ // </button>
+ // </div> */}
+ // </div>;
}
}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 5cb9866f8..1abe26c20 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -166,8 +166,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.layoutDoc._gridGap = 0;
this.layoutDoc._yMargin = 0;
this.turnOffEdit(true);
- DocListCastAsync((Doc.UserDoc().myPresentations as Doc).data).then(pres =>
- !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myPresentations as Doc, "data", this.rootDoc));
+ DocListCastAsync((Doc.UserDoc().myTrails as Doc).data).then(pres =>
+ !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myTrails as Doc, "data", this.rootDoc));
this._disposers.selection = reaction(() => SelectionManager.Views(),
views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation());
}
@@ -605,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);
@@ -2229,7 +2229,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const isMini: boolean = this.toolbarWidth <= 100;
return (
<div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? "none" : undefined }}>
- {isMini ? (null) : <select className="presBox-viewPicker"
+ {isMini || Doc.UserDoc().noviceMode ? (null) : <select className="presBox-viewPicker"
style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
onPointerDown={e => e.stopPropagation()}
onChange={this.viewChanged}