aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/AudioWaveform.tsx119
-rw-r--r--src/client/views/ContextMenu.scss12
-rw-r--r--src/client/views/ContextMenuItem.tsx6
-rw-r--r--src/client/views/DocComponent.tsx2
-rw-r--r--src/client/views/DocumentButtonBar.tsx5
-rw-r--r--src/client/views/DocumentDecorations.scss6
-rw-r--r--src/client/views/DocumentDecorations.tsx12
-rw-r--r--src/client/views/EditableView.tsx3
-rw-r--r--src/client/views/GestureOverlay.tsx9
-rw-r--r--src/client/views/GlobalKeyHandler.ts4
-rw-r--r--src/client/views/InkingStroke.tsx4
-rw-r--r--src/client/views/LightboxView.tsx3
-rw-r--r--src/client/views/MainView.scss5
-rw-r--r--src/client/views/MainView.tsx20
-rw-r--r--src/client/views/MainViewModal.scss7
-rw-r--r--src/client/views/PropertiesButtons.scss50
-rw-r--r--src/client/views/PropertiesButtons.tsx67
-rw-r--r--src/client/views/PropertiesView.scss5
-rw-r--r--src/client/views/PropertiesView.tsx21
-rw-r--r--src/client/views/SidebarAnnos.tsx16
-rw-r--r--src/client/views/StyleProvider.tsx24
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx4
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx193
-rw-r--r--src/client/views/collections/CollectionMenu.scss1255
-rw-r--r--src/client/views/collections/CollectionMenu.tsx137
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss142
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx752
-rw-r--r--src/client/views/collections/CollectionStackingView.scss24
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx120
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx8
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx95
-rw-r--r--src/client/views/collections/CollectionView.tsx3
-rw-r--r--src/client/views/collections/TabDocView.scss30
-rw-r--r--src/client/views/collections/TabDocView.tsx21
-rw-r--r--src/client/views/collections/TreeView.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx21
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx19
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx7
-rw-r--r--src/client/views/collections/collectionFreeForm/index.ts7
-rw-r--r--src/client/views/collections/collectionGrid/index.ts2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss (renamed from src/client/views/collections/CollectionLinearView.scss)63
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx226
-rw-r--r--src/client/views/collections/collectionLinear/index.ts1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx10
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx6
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss120
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx4
-rw-r--r--src/client/views/global/globalCssVariables.scss15
-rw-r--r--src/client/views/global/globalEnums.tsx5
-rw-r--r--src/client/views/nodes/AudioBox.scss338
-rw-r--r--src/client/views/nodes/AudioBox.tsx614
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx5
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss1
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx22
-rw-r--r--src/client/views/nodes/DocumentView.tsx116
-rw-r--r--src/client/views/nodes/FilterBox.tsx11
-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/LabelBox.tsx21
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx1
-rw-r--r--src/client/views/nodes/PDFBox.scss16
-rw-r--r--src/client/views/nodes/PDFBox.tsx25
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
-rw-r--r--src/client/views/nodes/WebBox.scss9
-rw-r--r--src/client/views/nodes/WebBox.tsx27
-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.tsx901
-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.tsx71
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss1
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx509
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx6
-rw-r--r--src/client/views/pdf/AnchorMenu.scss29
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx13
-rw-r--r--src/client/views/pdf/PDFViewer.tsx5
-rw-r--r--src/client/views/search/SearchBox.tsx2
-rw-r--r--src/client/views/topbar/TopBar.scss24
-rw-r--r--src/client/views/topbar/TopBar.tsx5
92 files changed, 4873 insertions, 2455 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 7ff9c1071..8f3b7c2cd 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -6,56 +6,121 @@ import Waveform from "react-audio-waveform";
import { Doc } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
-import { Cast } from "../../fields/Types";
+import { Cast, NumCast } from "../../fields/Types";
import { numberRange } from "../../Utils";
import "./AudioWaveform.scss";
+import { Colors } from "./global/globalEnums";
export interface AudioWaveformProps {
duration: number;
mediaPath: string;
- dataDoc: Doc;
+ layoutDoc: Doc;
+ trimming: boolean;
PanelHeight: () => number;
}
@observer
export class AudioWaveform extends React.Component<AudioWaveformProps> {
public static NUMBER_OF_BUCKETS = 100;
- @computed get _waveHeight() { return Math.max(50, this.props.PanelHeight()); }
+ @computed get _waveHeight() {
+ return Math.max(50, this.props.PanelHeight());
+ }
componentDidMount() {
- const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []);
+ const audioBuckets = Cast(
+ this.props.layoutDoc.audioBuckets,
+ listSpec("number"),
+ []
+ );
if (!audioBuckets.length) {
- this.props.dataDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
+ this.props.layoutDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
setTimeout(this.createWaveformBuckets);
}
}
+
// decodes the audio file into peaks for generating the waveform
createWaveformBuckets = async () => {
- axios({ url: this.props.mediaPath, responseType: "arraybuffer" })
- .then(response => {
+ axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
+ (response) => {
const context = new window.AudioContext();
- context.decodeAudioData(response.data,
- action(buffer => {
+ context.decodeAudioData(
+ response.data,
+ action((buffer) => {
const decodedAudioData = buffer.getChannelData(0);
- const bucketDataSize = Math.floor(decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS);
+
+ const bucketDataSize = Math.floor(
+ decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
+ );
const brange = Array.from(Array(bucketDataSize));
- this.props.dataDoc.audioBuckets = new List<number>(
- numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map((i: number) =>
- brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2));
- }));
- });
+ this.props.layoutDoc.audioBuckets = new List<number>(
+ numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
+ (i: number) =>
+ brange.reduce(
+ (p, x, j) =>
+ Math.abs(
+ Math.max(p, decodedAudioData[i * bucketDataSize + j])
+ ),
+ 0
+ ) / 2
+ )
+ );
+ })
+ );
+ }
+ );
+ }
+
+ @action
+ createTrimBuckets = () => {
+ const audioBuckets = Cast(
+ this.props.layoutDoc.audioBuckets,
+ listSpec("number"),
+ []
+ );
+
+ const start = Math.floor(
+ (NumCast(this.props.layoutDoc.clipStart) / this.props.duration) * 100
+ );
+ const end = Math.floor(
+ (NumCast(this.props.layoutDoc.clipEnd) / this.props.duration) * 100
+ );
+ return audioBuckets.slice(start, end);
}
render() {
- const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []);
- return <div className="audioWaveform">
- <Waveform
- color={"darkblue"}
- height={this._waveHeight}
- barWidth={0.1}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
- progressColor={"blue"} />
- </div>;
+ const audioBuckets = Cast(
+ this.props.layoutDoc.audioBuckets,
+ listSpec("number"),
+ []
+ );
+
+ return (
+ <div className="audioWaveform">
+ {this.props.trimming || !this.props.layoutDoc.clipEnd ? (
+ <Waveform
+ color={Colors.MEDIUM_BLUE}
+ height={this._waveHeight}
+ barWidth={0.1}
+ pos={this.props.duration}
+ duration={this.props.duration}
+ peaks={
+ audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS
+ ? audioBuckets
+ : undefined
+ }
+ progressColor={Colors.MEDIUM_BLUE}
+ />
+ ) : (
+ <Waveform
+ color={Colors.MEDIUM_BLUE}
+ height={this._waveHeight}
+ barWidth={0.1}
+ pos={this.props.duration}
+ duration={this.props.duration}
+ peaks={this.createTrimBuckets()}
+ progressColor={Colors.MEDIUM_BLUE}
+ />
+ )}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 795529780..47ae0424b 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -3,14 +3,12 @@
.contextMenu-cont {
position: absolute;
display: flex;
- z-index: $contextMenu-zindex;
- box-shadow: $medium-gray 0.2vw 0.2vw 0.4vw;
+ z-index: 100000;
+ box-shadow: 0px 3px 4px rgba(0,0,0,30%);
flex-direction: column;
background: whitesmoke;
- padding-top: 10px;
- padding-bottom: 10px;
- border-radius: 15px;
- border: solid #BBBBBBBB 1px;
+ border-radius: 3px;
+ border: solid $light-gray 1px;
}
// .contextMenu-item:first-child {
@@ -132,7 +130,7 @@
}
.contextMenu-inlineMenu {
- border-top: solid 1px;
+ // border-top: solid 1px; //TODO:glr clean
}
.contextMenu-item:hover {
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 6fe2abd21..c3921d846 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -90,7 +90,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
</span>
) : null}
<div className="contextMenu-description">
- {this.props.description}
+ {this.props.description.replace(":","")}
</div>
</div>
);
@@ -116,12 +116,12 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
- <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center" }}>
+ <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center", alignSelf: "center" }}>
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
<div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
- style={{ alignItems: "center" }} >
+ style={{ alignItems: "center", alignSelf: "center" }} >
{this.props.description}
<FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
</div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 33dff9da5..cb36f4270 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -115,7 +115,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const style: { [key: string]: any } = {};
const divKeys = ["width", "height", "fontSize", "transform", "left", "background", "left", "right", "top", "bottom", "pointerEvents", "position"];
const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result as string || "";
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? "";
};
divKeys.map((prop: string) => {
const p = (this.props as any)[prop];
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 5f09a322c..5640e5132 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -27,6 +27,7 @@ import React = require("react");
import { PresBox } from './nodes/trails/PresBox';
import { undoBatch } from '../util/UndoManager';
import { CollectionViewType } from './collections/CollectionView';
+import { Colors } from './global/globalEnums';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -187,9 +188,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get followLinkButton() {
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={
- <div className="dash-tooltip">{"follow primary link on click"}</div>}>
+ <div className="dash-tooltip">{"Set onClick to follow primary link"}</div>}>
<div className="documentButtonBar-icon"
- style={{ color: targetDoc.isLinkButton ? "black" : "white" }}
+ style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
</div>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 316f63240..d34efd01a 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -51,6 +51,7 @@ $linkGap : 3px;
pointer-events: auto;
background: $medium-gray;
opacity: 0.1;
+
&:hover {
opacity: 1;
}
@@ -94,6 +95,7 @@ $linkGap : 3px;
position: absolute;
}
}
+
.documentDecorations-rotation {
background: transparent;
right: -15;
@@ -189,6 +191,7 @@ $linkGap : 3px;
margin-left: 5px;
height: 22px;
position: absolute;
+
.documentDecorations-titleSpan {
width: 100%;
border-radius: 8px;
@@ -263,7 +266,7 @@ $linkGap : 3px;
}
.link-button-container {
- border-radius: 10px;
+ border-radius: 13px;
width: max-content;
height: auto;
display: flex;
@@ -338,6 +341,7 @@ $linkGap : 3px;
.documentdecorations-icon {
margin: 0px;
}
+
.templating-button,
.docDecs-tagButton {
width: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 118d2e7c7..a570bdb34 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from '@material-ui/core';
import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc";
+import { AclAdmin, AclEdit, DataSym, Doc, Field, HeightSym, WidthSym, DocListCast } from "../../fields/Doc";
import { Document } from '../../fields/documentSchemas';
import { HtmlField } from '../../fields/HtmlField';
import { InkField } from "../../fields/InkField";
@@ -159,9 +159,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
const selectedDocs = SelectionManager.Views();
if (selectedDocs.length) {
if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
- selectedDocs[0].props.Document._fullScreenView = Doc.MakeAlias(selectedDocs[0].props.Document);
- (selectedDocs[0].props.Document._fullScreenView as Doc).context = undefined;
- CollectionDockingView.AddSplit(selectedDocs[0].props.Document._fullScreenView as Doc, "right");
+ const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
} else if (e.shiftKey) { // open centered in a new workspace with Shift Key
const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
alias.context = undefined;
@@ -169,7 +168,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
alias.y = -alias[HeightSym]() / 2;
CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
} else if (e.altKey) { // open same document in new tab
- CollectionDockingView.ToggleSplit(Cast(selectedDocs[0].props.Document._fullScreenView, Doc, null) || selectedDocs[0].props.Document, "right");
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
} else {
LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document));
}
@@ -404,6 +403,9 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
get selectionTitle(): string {
if (SelectionManager.Views().length === 1) {
const selected = SelectionManager.Views()[0];
+ if (selected.ComponentView?.getTitle?.()) {
+ return selected.ComponentView.getTitle();
+ }
if (this._titleControlString.startsWith("=")) {
return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 03d9efff3..83336c180 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -155,7 +155,10 @@ export class EditableView extends React.Component<EditableProps> {
return wasFocused !== this._editing;
}
+
+
renderEditor() {
+ console.log("render editor", this.props.autosuggestProps);
return this.props.autosuggestProps
? <Autosuggest
{...this.props.autosuggestProps.autosuggestProps}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index bbf21f22c..b401a7f03 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -14,6 +14,7 @@ import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
import { Scripting } from "../util/Scripting";
+import { SelectionManager } from "../util/SelectionManager";
import { Transform } from "../util/Transform";
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
import "./GestureOverlay.scss";
@@ -23,7 +24,6 @@ import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import { SelectionManager } from "../util/SelectionManager";
@observer
export class GestureOverlay extends Touchable {
@@ -31,7 +31,7 @@ export class GestureOverlay extends Touchable {
@observable public InkShape: string = "";
@observable public SavedColor?: string;
- @observable public SavedWidth?: string;
+ @observable public SavedWidth?: number;
@observable public Tool: ToolglassTools = ToolglassTools.None;
@observable private _thumbX?: number;
@@ -588,8 +588,9 @@ export class GestureOverlay extends Touchable {
this.makePolygon(this.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
- if (!CollectionFreeFormViewChrome.Instance._keepPrimitiveMode) {
+ if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
this.InkShape = "";
+ Doc.UserDoc().activeInkTool = InkTool.None;
}
}
// if we're not drawing in a toolglass try to recognize as gesture
@@ -952,7 +953,7 @@ Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStar
Scripting.addGlobal(function resetPen() {
runInAction(() => {
SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
- SetActiveInkWidth(GestureOverlay.Instance.SavedWidth ?? "2");
+ SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? "2");
});
}, "resets the pen tool");
Scripting.addGlobal(function createText(text: any, x: any, y: any) {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 0127d3080..f66c9c788 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -229,8 +229,8 @@ export class KeyManager {
}
break;
case "o":
- const target = SelectionManager.Views()[0];
- target && CollectionDockingView.OpenFullScreen(target.props.Document);
+ const target = SelectionManager.Docs().lastElement();
+ target && CollectionDockingView.OpenFullScreen(target);
break;
case "r":
preventDefault = false;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 5fc159f14..db09849fd 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
import { makeInterface } from "../../fields/Schema";
-import { Cast, StrCast } from "../../fields/Types";
+import { Cast, StrCast, NumCast } from "../../fields/Types";
import { TraceMobx } from "../../fields/util";
import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
@@ -168,7 +168,7 @@ export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activ
export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
+export function ActiveInkWidth(): number { return NumCast(ActiveInkPen()?.activeInkWidth); }
export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 88739fe91..ec30a6a5d 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -16,6 +16,7 @@ import { TabDocView } from './collections/TabDocView';
import "./LightboxView.scss";
import { DocumentView } from './nodes/DocumentView';
import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
+import { CollectionMenu } from './collections/CollectionMenu';
interface LightboxViewProps {
PanelWidth: number;
@@ -213,6 +214,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
LightboxView.SetLightboxDoc(undefined);
}
}} >
+
<div className="lightboxView-contents" style={{
left: this.leftBorder,
top: this.topBorder,
@@ -220,6 +222,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
height: this.lightboxHeight(),
clipPath: `path('${Doc.UserDoc().renderStyle === "comic" ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`
}}>
+ {/* <CollectionMenu /> TODO:glr This is where it would go*/}
<DocumentView ref={action((r: DocumentView | null) => {
LightboxView._docView = r !== null ? r : undefined;
r && setTimeout(action(() => {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index d913f2069..817e45699 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -265,11 +265,6 @@
height: 35px;
padding: 5px;
}
-
- svg {
- width: 95% !important;
- height: 95%;
- }
}
.mainView-searchPanel {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8b5e18fb2..5c1a51052 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -32,7 +32,7 @@ import { Transform } from '../util/Transform';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import { CollectionLinearView } from './collections/CollectionLinearView';
+import { CollectionLinearView } from './collections/collectionLinear';
import { CollectionMenu } from './collections/CollectionMenu';
import { CollectionViewType } from './collections/CollectionView';
import "./collections/TreeView.scss";
@@ -62,6 +62,7 @@ import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
+import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -172,7 +173,7 @@ export class MainView extends React.Component {
fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll,
fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines,
- fa.faSave, fa.faBookmark);
+ fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen);
this.initAuthenticationRouters();
}
@@ -226,16 +227,16 @@ export class MainView extends React.Component {
@action
createNewPresentation = async () => {
- if (!await this.userDoc.myPresentations) {
- this.userDoc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "PRESENTATION TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true
+ if (!await this.userDoc.myTrails) {
+ this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true
}));
}
const pres = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
+ { title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
CollectionDockingView.AddSplit(pres, "right");
this.userDoc.activePresentation = pres;
- Doc.AddDocToList(this.userDoc.myPresentations as Doc, "data", pres);
+ Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres);
}
getPWidth = () => this._panelWidth - this.propertiesWidth();
@@ -278,7 +279,6 @@ export class MainView extends React.Component {
style={{
minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`,
transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
- //TODO:glr width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`
}}>
{!this.mainContainer ? (null) : this.mainDocView}
</div>;
@@ -331,7 +331,7 @@ export class MainView extends React.Component {
PanelHeight={this.getContentsHeight}
renderDepth={0}
isContentActive={returnTrue}
- scriptContext={CollectionDockingView.Instance.props.Document}
+ scriptContext={CollectionDockingView.Instance?.props.Document}
focus={DocUtils.DefaultFocus}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -439,6 +439,7 @@ export class MainView extends React.Component {
this._flyoutWidth = (this._flyoutWidth || 250);
this._sidebarContent.proto = button.target as any;
this.LastButton = button;
+ console.log(button.title);
});
closeFlyout = action(() => {
@@ -596,6 +597,7 @@ export class MainView extends React.Component {
<MarqueeOptionsMenu />
<OverlayView />
<TimelineMenu />
+ <RichTextMenu />
{this.snapLines}
<div className="mainView-webRef" ref={this.makeWebRef} />
<LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss
index 5f19590b4..0648e31c5 100644
--- a/src/client/views/MainViewModal.scss
+++ b/src/client/views/MainViewModal.scss
@@ -4,18 +4,17 @@
z-index: 10000;
width: 100%;
height: 100%;
+
.dialogue-box {
+ padding: 10px;
position: absolute;
z-index: 1000;
text-align: center;
justify-content: center;
align-self: center;
align-content: center;
- padding: 20px;
- // background: gainsboro;
background: white;
- border-radius: 10px;
- border: 0.5px solid black;
+ // border-radius: 10px;
box-shadow: #00000044 5px 5px 10px;
transform: translate(-50%, -50%);
top: 50%;
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index 484522bc7..36b2df73e 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -44,14 +44,15 @@ $linkGap : 3px;
}
}
.propertiesButtons-linkButton-empty.toggle-on {
- background-color: white;
- color: $dark-gray;
+ background-color: $medium-blue;
+ color: $white;
}
.propertiesButtons-linkButton-empty.toggle-hover {
- background-color: gray;
- color: $dark-gray;
+ background-color: $light-blue;
+ color: $black;
}
.propertiesButtons-linkButton-empty.toggle-off {
+ background-color: $dark-gray;
color: white;
}
@@ -67,10 +68,12 @@ $linkGap : 3px;
}
.onClickFlyout-editScript {
+ cursor: pointer;
text-align: center;
- border: 0.5px solid grey;
+ margin-top: 5px;
+ border: 0.5px solid $medium-gray;
background-color: rgb(230, 230, 230);
- border-radius: 9px;
+ border-radius: 5px;
padding: 4px;
}
@@ -84,9 +87,32 @@ $linkGap : 3px;
margin-bottom: 8px;
}
+.propertiesButton-dropdownList {
+ width: 100%;
+ height: fit-content;
+ top: 100%;
+ z-index: 21;
+
+ .list-item {
+ cursor: pointer;
+ 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;
+ }
+}
+
.propertiesButtons-title {
- background: #121721;
- color: white;
+ background: $dark-gray;
+ color: $white;
font-size: 6px;
width: 37px;
padding: 3px;
@@ -111,17 +137,11 @@ $linkGap : 3px;
margin-left: 4px;
&:hover {
- background: $medium-gray;
- transform: scale(1.05);
+ filter:brightness(0.85);
cursor: pointer;
}
}
-.propertiesButtons-linker:hover {
- cursor: pointer;
- transform: scale(1.05);
-}
-
@-moz-keyframes spin {
100% {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 5c41a96d0..dd737dc9d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -15,6 +15,7 @@ import { InkingStroke } from './InkingStroke';
import { DocumentView } from './nodes/DocumentView';
import './PropertiesButtons.scss';
import React = require("react");
+import { Colors } from "./global/globalEnums";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -65,10 +66,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn("Lock\xA0View", "_lockedTransform", on => `${on ? "Unlock" : "Lock"} panning of view`, on => "lock");
}
@computed get fitContentButton() {
- return this.propertyToggleBtn("View All", "_fitToBox", on => `${on ? "Don't" : ""} fit content to container visible area`, on => "eye");
+ return this.propertyToggleBtn("View All", "_fitToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye");
}
@computed get fitWidthButton() {
- return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : ""} fit content to width of container`, on => "arrows-alt-h");
+ return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : "Do"} fit content to width of container`, on => "arrows-alt-h");
}
@computed get captionButton() {
return this.propertyToggleBtn("Caption", "_showCaption", on => `${on ? "Hide" : "Show"} caption footer`, on => "closed-captioning", (dv, doc) => (dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? "caption" : undefined);
@@ -83,7 +84,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn("Auto\xA0Size", "_autoHeight", on => `Automatical vertical sizing to show all content`, on => "arrows-alt-v");
}
@computed get gridButton() {
- return this.propertyToggleBtn("Grid", "_backgroundGrid-show", on => `Display background grid in collection`, on => "border-all");
+ return this.propertyToggleBtn("Grid", "_backgroundGridShow", on => `Display background grid in collection`, on => "border-all");
}
@computed get snapButton() {
return this.propertyToggleBtn("Snap\xA0Lines", "showSnapLines", on => `Display snapping lines when objects are dragged`, on => "border-all", undefined, true);
@@ -128,13 +129,13 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
@action
- handleOptionChange = (e: any) => {
- this.selectedDoc && (this.selectedDoc.onClickBehavior = e.target.value);
+ handleOptionChange = (onClick: string) => {
+ this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick);
SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => {
docView.noOnClick();
- switch (e.target.value) {
+ switch (onClick) {
case "enterPortal": docView.makeIntoPortal(); break;
- case "toggleDetail": docView.toggleDetail(); break;
+ case "toggleDetail": docView.setToggleDetail(); break;
case "linkInPlace": docView.toggleFollowLink("inPlace", true, false); break;
case "linkOnRight": docView.toggleFollowLink("add:right", false, false); break;
}
@@ -149,20 +150,34 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed
get onClickFlyout() {
- const makeLabel = (value: string, label: string) => <div className="radio">
- <label>
- <input type="radio" value={value} checked={(this.selectedDoc?.onClickBehavior ?? "nothing") === value} onChange={this.handleOptionChange} />
- {label}
- </label>
- </div>;
+ const buttonList = [
+ ["nothing", "Select Document"],
+ ["enterPortal", "Enter Portal"],
+ ["toggleDetail", "Toggle Detail"],
+ ["linkInPlace", "Follow Link"],
+ ["linkOnRight", "Open Link on Right"]
+ ];
+ const currentSelection = this.selectedDoc.onClickBehavior;
+ // Get items to place into the list
+
+ const list = buttonList.map((value) => {
+ const click = () => {
+ this.handleOptionChange(value[0]);
+ };
+ return <div className="list-item" key={`${value}`}
+ style={{
+ backgroundColor: value[0] === currentSelection ? Colors.LIGHT_BLUE : undefined
+ }}
+ onClick={click}>
+ {value[1]}
+ </div>;
+ });
return <div>
- <form>
- {makeLabel("nothing", "Select Document")}
- {makeLabel("enterPortal", "Enter Portal")}
- {makeLabel("toggleDetail", "Toggle Detail")}
- {makeLabel("linkInPlace", "Follow Link")}
- {makeLabel("linkOnRight", "Open Link on Right")}
- </form>
+ <div>
+ <div className="propertiesButton-dropdownList">
+ {list}
+ </div>
+ </div>
{Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>}
</div>;
}
@@ -190,24 +205,24 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform;
const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree;
const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => <div className="propertiesButtons-button" style={style}> {ele} </div>;
-
+ const isNovice = Doc.UserDoc().noviceMode;
return !this.selectedDoc ? (null) :
<div className="propertiesButtons">
{toggle(this.titleButton)}
{toggle(this.captionButton)}
{toggle(this.lockButton)}
- {toggle(this.dictationButton)}
+ {toggle(this.dictationButton, { display: isNovice ? "none" : "" })}
{toggle(this.onClickButton)}
{toggle(this.fitWidthButton)}
{toggle(this.fitContentButton, { display: !isFreeForm ? "none" : "" })}
{toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })}
{toggle(this.maskButton, { display: !isInk ? "none" : "" })}
- {toggle(this.chromeButton, { display: isCollection ? "" : "none" })}
- {toggle(this.gridButton, { display: isCollection ? "" : "none" })}
- {toggle(this.snapButton, { display: isCollection ? "" : "none" })}
+ {toggle(this.chromeButton, { display: !isCollection || isNovice ? "none" : "" })}
+ {toggle(this.gridButton, { display: !isCollection ? "none" : "" })}
+ {toggle(this.snapButton, { display: !isCollection ? "none" : "" })}
{toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })}
{toggle(this.panButton, { display: !isFreeForm ? "none" : "" })}
- {toggle(this.perspectiveButton, { display: !isCollection ? "none" : "" })}
+ {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? "none" : "" })}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 321b83f52..554f137cb 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -825,9 +825,8 @@
}
.editable-title {
- padding: 6px;
- padding-bottom: 2px;
- border: solid 1px $dark-gray;
+ border: solid 1px #323232;
+ height: fit-content;
&:hover {
border: 0.75px solid $medium-blue;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 17b137c31..ab9022a84 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -2,17 +2,19 @@ import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Checkbox, Tooltip } from "@material-ui/core";
import { intersection } from "lodash";
-import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, autorun, computed, Lambda, observable } from "mobx";
import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
-import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym, AclSelfEdit } from "../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { InkField } from "../../fields/InkField";
+import { List } from "../../fields/List";
import { ComputedField } from "../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils";
import { DocumentType } from "../documents/DocumentTypes";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { DocumentManager } from "../util/DocumentManager";
import { SelectionManager } from "../util/SelectionManager";
import { SharingManager } from "../util/SharingManager";
@@ -29,8 +31,6 @@ import { PropertiesButtons } from "./PropertiesButtons";
import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
import "./PropertiesView.scss";
import { DefaultStyleProvider } from "./StyleProvider";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { List } from "../../fields/List";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -366,7 +366,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}>
<div className="expansion-button" onPointerDown={() => {
if (this.selectedDocumentView || this.selectedDoc) {
- SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc);
+ SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
}
}}>
<FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
@@ -1086,6 +1086,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
// }
render() {
+ const isNovice = BoolCast(Doc.UserDoc().noviceMode);
if (!this.selectedDoc && !this.isPres) {
return <div className="propertiesView" style={{ width: this.props.width }}>
<div className="propertiesView-title" style={{ width: this.props.width }}>
@@ -1110,15 +1111,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{this.sharingSubMenu}
- {this.filtersSubMenu}
+ {isNovice ? null : this.filtersSubMenu}
{this.inkSubMenu}
- {this.fieldsSubMenu}
+ {isNovice ? null : this.fieldsSubMenu}
- {this.contextsSubMenu}
+ {isNovice ? null : this.contextsSubMenu}
- {this.layoutSubMenu}
+ {isNovice ? null : this.layoutSubMenu}
</div>;
}
if (this.isPres) {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1f9763d18..dd851c764 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -83,13 +83,8 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
-
- sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
- if (property === StyleProp.ShowTitle) {
- return doc === this.props.rootDoc ? undefined : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
- }
- return this.props.styleProvider?.(doc, props, property);
- }
+ showTitle = () => "title";
+ setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
render() {
const renderTag = (tag: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
@@ -105,9 +100,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
{user}
</div>;
};
+ // TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
return !this.props.showSidebar ? (null) :
<div style={{
- position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: 0, right: 0,
+ position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: this.props.rootDoc.type !== DocumentType.RTF && StrCast(this.props.rootDoc._showTitle) === "title" ? 15 : 0, right: 0,
background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
width: `${this.panelWidth()}px`,
height: "100%"
@@ -123,13 +119,13 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
NativeHeight={returnZero}
PanelHeight={this.panelHeight}
PanelWidth={this.panelWidth}
- styleProvider={this.sidebarStyleProvider}
docFilters={this.docFilters}
scaleField={this.sidebarKey + "-scale"}
- setHeight={(height) => this.props.setHeight(height + this.filtersHeight())}
+ setHeight={this.setHeightCallback}
isAnnotationOverlay={false}
select={emptyFunction}
scaling={returnOne}
+ childShowTitle={this.showTitle}
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
childHideDecorationTitle={returnTrue}
removeDocument={this.removeDocument}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index c9e532745..3c88a4830 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -21,6 +21,7 @@ import "./nodes/FilterBox.scss";
import "./StyleProvider.scss";
import React = require("react");
import Color = require('color');
+import { lightOrDark } from '../../Utils';
export enum StyleLayers {
Background = "background"
@@ -88,34 +89,33 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
switch (property.split(":")[0]) {
case StyleProp.TreeViewIcon: return Doc.toIcon(doc, isOpen);
case StyleProp.DocContents: return undefined;
- case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey";
+ case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey";
case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"]);
- case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle,
- !Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
- (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "") || "";
+ case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
+ StrCast(doc._showTitle,
+ props?.showTitle?.() ||
+ (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ?
+ (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) :
+ "author;creationDate") : "")) || "");
case StyleProp.Color:
+ if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color));
if (docColor) return docColor;
const backColor = backgroundCol();
if (!backColor) return undefined;
- const nonAlphaColor = backColor.startsWith("#") ? (backColor as string).substring(0, 7) :
- backColor.startsWith("rgba") ? backColor.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : backColor;
- const col = Color(nonAlphaColor).rgb();
- const colsum = (col.red() + col.green() + col.blue());
- if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
- return Colors.WHITE;
+ return lightOrDark(backColor);
case StyleProp.Hidden: return BoolCast(doc?._hidden);
case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"], doc?._viewType === CollectionViewType.Pile ? "50%" : "");
case StyleProp.TitleHeight: return 15;
case StyleProp.BorderPath: return comicStyle() && props?.renderDepth ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
- case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) ||
+ case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
case StyleProp.BackgroundColor: {
+ if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY;
let docColor: Opt<string> = StrCast(doc?.[fieldKey + "backgroundColor"], StrCast(doc?._backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : ""));
- if (MainView.Instance.LastButton === doc) return darkScheme() ? Colors.MEDIUM_GRAY : Colors.LIGHT_GRAY;
switch (doc?.type) {
case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break;
case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index cae08e1f4..5325d5827 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -428,7 +428,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
const emptyPane = CurrentUserUtils.EmptyPane;
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
CollectionDockingView.AddSplit(docToAdd, "", stack);
@@ -453,7 +453,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
const emptyPane = CurrentUserUtils.EmptyPane;
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
CollectionDockingView.AddSplit(docToAdd, "", stack);
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
deleted file mode 100644
index 52c836556..000000000
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import { Tooltip } from '@material-ui/core';
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
-import { documentSchema } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
-import { makeInterface } from '../../../fields/Schema';
-import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, Utils } from '../../../Utils';
-import { DragManager } from '../../util/DragManager';
-import { Transform } from '../../util/Transform';
-import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { DocumentView } from '../nodes/DocumentView';
-import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
-import { StyleProp } from '../StyleProvider';
-import "./CollectionLinearView.scss";
-import { CollectionSubView } from './CollectionSubView';
-import { CollectionViewType } from './CollectionView';
-
-
-type LinearDocument = makeInterface<[typeof documentSchema,]>;
-const LinearDocument = makeInterface(documentSchema);
-
-@observer
-export class CollectionLinearView extends CollectionSubView(LinearDocument) {
- @observable public addMenuToggle = React.createRef<HTMLInputElement>();
- @observable private _selectedIndex = -1;
- private _dropDisposer?: DragManager.DragDropDisposer;
- private _widthDisposer?: IReactionDisposer;
- private _selectedDisposer?: IReactionDisposer;
-
- componentWillUnmount() {
- this._dropDisposer?.();
- this._widthDisposer?.();
- this._selectedDisposer?.();
- this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
- }
-
- componentDidMount() {
- this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10),
- width => this.childDocs.length && (this.layoutDoc._width = width),
- { fireImmediately: true }
- );
-
- this._selectedDisposer = reaction(
- () => NumCast(this.layoutDoc.selectedIndex),
- (i) => runInAction(() => {
- this._selectedIndex = i;
- let selected: any = undefined;
- this.childLayoutPairs.map(async (pair, ind) => {
- const isSelected = this._selectedIndex === ind;
- if (isSelected) {
- selected = pair;
- }
- else {
- ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
- }
- });
- if (selected && selected.layout) {
- ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
- }
- }),
- { fireImmediately: true }
- );
- }
- protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
- this._dropDisposer && this._dropDisposer();
- if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
- }
- }
-
- dimension = () => NumCast(this.rootDoc._height); // 2 * the padding
- getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
- if (!ele.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
- return new Transform(-translateX, -translateY, 1);
- }
-
- @action
- exitLongLinks = () => {
- if (DocumentLinksButton.StartLink) {
- if (DocumentLinksButton.StartLink.Document) {
- action((e: React.PointerEvent<HTMLDivElement>) => {
- Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
- });
- }
- }
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- }
-
- @action
- changeDescriptionSetting = () => {
- if (LinkDescriptionPopup.showDescriptions) {
- if (LinkDescriptionPopup.showDescriptions === "ON") {
- LinkDescriptionPopup.showDescriptions = "OFF";
- LinkDescriptionPopup.descriptionPopup = false;
- } else {
- LinkDescriptionPopup.showDescriptions = "ON";
- }
- } else {
- LinkDescriptionPopup.showDescriptions = "OFF";
- LinkDescriptionPopup.descriptionPopup = false;
- }
- }
-
- render() {
- const guid = Utils.GenerateGuid();
- const flexDir: any = StrCast(this.Document.flexDirection);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-
- const menuOpener = <label htmlFor={`${guid}`} style={{ pointerEvents: "all", cursor: "pointer", background: backgroundColor === color ? "black" : backgroundColor, }}
- onPointerDown={e => e.stopPropagation()} >
- <p>{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "–" : "+"}</p>
- </label>;
-
- return <div className="collectionLinearView-outer">
- <div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top">
- {menuOpener}
- </Tooltip>
- <input id={`${guid}`} type="checkbox" checked={BoolCast(this.layoutDoc.linearViewIsExpanded)} ref={this.addMenuToggle}
- onChange={action(() => this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
-
- <div className="collectionLinearView-content" style={{ height: this.dimension(), flexDirection: flexDir }}>
- {this.childLayoutPairs.map((pair, ind) => {
- const nested = pair.layout._viewType === CollectionViewType.Linear;
- const dref = React.createRef<HTMLDivElement>();
- const scalable = pair.layout.onClick || pair.layout.onDragStart;
- return <div className={`collectionLinearView-docBtn` + (scalable ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
- style={{
- pointerEvents: "all",
- minWidth: 30,
- width: nested ? pair.layout[WidthSym]() : this.dimension(),
- height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension(),
- }} >
- <DocumentView
- Document={pair.layout}
- DataDoc={pair.data}
- isContentActive={returnFalse}
- isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={this.getTransform(dref)}
- PanelWidth={nested ? pair.layout[WidthSym] : this.dimension}
- PanelHeight={nested ? pair.layout[HeightSym] : this.dimension}
- renderDepth={this.props.renderDepth + 1}
- focus={emptyFunction}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>;
- })}
- </div>
- {DocumentLinksButton.StartLink ? <span className="bottomPopup-background" style={{
- pointerEvents: "all"
- }}
- onPointerDown={e => e.stopPropagation()} >
- <span className="bottomPopup-text" >
- Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
- </span>
-
- <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
- <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
- Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
- </span>
- </Tooltip>
-
- <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
- Stop
- </span>
- </Tooltip>
-
- </span> : null}
- </div>
- </div>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index f04b19ef7..c35f088a6 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,628 +1,659 @@
@import "../global/globalCssVariables";
-.collectionMenu-cont {
- position: relative;
- display: inline-flex;
- width: 100%;
- opacity: 0.9;
- z-index: 901;
- transition: top .5s;
- background: $dark-gray;
- color: $white;
- transform-origin: top left;
- top: 0;
- width: 100%;
-
- .recordButtonOutline {
- border-radius: 100%;
- width: 18px;
- height: 18px;
- border: solid 1px $white;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .recordButtonInner {
- border-radius: 100%;
- width: 70%;
- height: 70%;
- background: $white;
- }
-
- .collectionMenu {
- display: flex;
- height: 100%;
- overflow: visible;
- z-index: 901;
- border: unset;
-
- .collectionMenu-divider {
- height: 100%;
- margin-left: 3px;
- margin-right: 3px;
- width: 2px;
- background-color: $medium-gray;
- }
-
- .collectionViewBaseChrome {
- display: flex;
- align-items: center;
-
- .collectionViewBaseChrome-viewPicker {
- font-size: $small-text;
- outline-color: $black;
- color: $white;
- border: none;
- background: $dark-gray;
- }
-
- .collectionViewBaseChrome-viewPicker:focus {
- outline: none;
- border: none;
- }
-
- .collectionViewBaseChrome-viewPicker:active {
- outline-color: $black;
- }
-
- .collectionViewBaseChrome-button {
- font-size: $small-text;
- text-transform: uppercase;
- letter-spacing: 2px;
- background: $white;
- color: $pink;
- outline-color: $black;
- border: none;
- padding: 12px 10px 11px 10px;
- margin-left: 10px;
- }
-
- .collectionViewBaseChrome-cmdPicker {
- margin-left: 3px;
- margin-right: 0px;
- font-size: $small-text;
- text-transform: capitalize;
- color: $white;
- border: none;
- background: $dark-gray;
- }
-
- .collectionViewBaseChrome-cmdPicker:focus {
- border: none;
- outline: none;
- }
-
- .commandEntry-outerDiv {
- pointer-events: all;
- background-color: transparent;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- height: 100%;
- overflow: hidden;
-
- .commandEntry-drop {
- color: $white;
- width: 30px;
- margin-top: auto;
- margin-bottom: auto;
- }
- }
-
- .commandEntry-outerDiv:hover{
- background-color: $drop-shadow;
-
- .collectionViewBaseChrome-viewPicker,
- .collectionViewBaseChrome-cmdPicker{
- background: $dark-gray;
- }
- }
-
- .collectionViewBaseChrome-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 30px;
- transform-origin: top left;
- pointer-events: all;
- // margin-top: 10px;
- }
-
- @media only screen and (max-device-width: 480px) {
- .collectionViewBaseChrome-collapse {
- display: none;
- }
- }
-
- .collectionViewBaseChrome-template,
- .collectionViewBaseChrome-viewModes {
- align-items: center;
- height: 100%;
- display: flex;
- background: transparent;
- color: $medium-gray;
- justify-content: center;
- }
-
- .collectionViewBaseChrome-viewSpecs {
- margin-left: 5px;
- display: grid;
- border: none;
- border-right: solid $medium-gray 1px;
-
- .collectionViewBaseChrome-filterIcon {
- position: relative;
- display: flex;
- margin: auto;
- background: $dark-gray;
- color: $white;
- width: 30px;
- height: 30px;
- align-items: center;
- justify-content: center;
- border: none;
- border-right: solid $medium-gray 1px;
- }
-
- .collectionViewBaseChrome-viewSpecsInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: $medium-gray;
- text-align: center;
- letter-spacing: 2px;
- outline-color: $black;
- font-size: $small-text;
- background: $white;
- height: 100%;
- width: 75px;
- }
-
- .collectionViewBaseChrome-viewSpecsMenu {
- overflow: hidden;
- transition: height .5s, display .5s;
- position: absolute;
- top: 60px;
- z-index: 100;
- display: flex;
- flex-direction: column;
- background: $white;
- box-shadow: $medium-gray 2px 2px 4px;
-
- .qs-datepicker {
- left: unset;
- right: 0;
- }
-
- .collectionViewBaseChrome-viewSpecsMenu-row {
- display: grid;
- grid-template-columns: 150px 200px 150px;
- margin-top: 10px;
- margin-right: 10px;
-
- .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
- .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
- .collectionViewBaseChrome-viewSpecsMenu-rowRight {
- font-size: $small-text;
- letter-spacing: 2px;
- color: $medium-gray;
- margin-left: 10px;
- padding: 5px;
- border: none;
- outline-color: $black;
- }
- }
-
- .collectionViewBaseChrome-viewSpecsMenu-lastRow {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- grid-gap: 10px;
- margin: 10px;
- }
- }
- }
- }
-
- .collectionStackingViewChrome-cont,
- .collectionTreeViewChrome-cont,
- .collection3DCarouselViewChrome-cont {
- display: flex;
- justify-content: space-between;
- }
-
- .collectionGridViewChrome-cont {
- display: flex;
- margin-left: 10;
-
- .collectionGridViewChrome-viewPicker {
- font-size: $small-text;
- //text-transform: uppercase;
- //letter-spacing: 2px;
- background: $dark-gray;
- color: $white;
- outline-color: $black;
- color: $white;
- border: none;
- border-right: solid $medium-gray 1px;
- }
-
- .collectionGridViewChrome-viewPicker:active {
- outline-color: $black;
- }
-
- .grid-control {
- align-self: center;
- display: flex;
- flex-direction: row;
- margin-right: 5px;
-
- .grid-icon {
- margin-right: 5px;
- align-self: center;
- }
-
- .flexLabel {
- margin-bottom: 0;
- }
-
- .collectionGridViewChrome-entryBox {
- width: 50%;
- color: $black;
- }
-
- .collectionGridViewChrome-columnButton {
- color: $black;
- }
- }
- }
-
- .collectionStackingViewChrome-sort,
- .collectionTreeViewChrome-sort {
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- .collectionStackingViewChrome-sortIcon,
- .collectionTreeViewChrome-sortIcon {
- transition: transform .5s;
- margin-left: 10px;
- }
- }
-
- button:hover {
- transform: scale(1);
- }
-
-
- .collectionStackingViewChrome-pivotField-cont,
- .collectionTreeViewChrome-pivotField-cont,
- .collection3DCarouselViewChrome-scrollSpeed-cont {
- justify-self: right;
- align-items: center;
- display: flex;
- grid-auto-columns: auto;
- font-size: $small-text;
- letter-spacing: 2px;
-
- .collectionStackingViewChrome-pivotField-label,
- .collectionTreeViewChrome-pivotField-label,
- .collection3DCarouselViewChrome-scrollSpeed-label {
- grid-column: 1;
- margin-right: 7px;
- user-select: none;
- font-family: $sans-serif;
- letter-spacing: normal;
- }
-
- .collectionStackingViewChrome-sortIcon {
- transition: transform .5s;
- grid-column: 3;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- width: 25px;
- height: 25px;
- border-radius: 100%;
- }
-
- .collectionStackingViewChrome-sortIcon:hover {
- background-color: $drop-shadow;
- }
-
- .collectionStackingViewChrome-pivotField,
- .collectionTreeViewChrome-pivotField,
- .collection3DCarouselViewChrome-scrollSpeed {
- color: $white;
- grid-column: 2;
- grid-row: 1;
- width: 90%;
- min-width: 100px;
- display: flex;
- height: 80%;
- border-radius: 7px;
- align-items: center;
- background: $white;
-
- .editable-view-input,
- input,
- .editableView-container-editing-oneLine,
- .editableView-container-editing {
- margin: auto;
- border: 0px;
- color: $light-gray !important;
- text-align: center;
- letter-spacing: 2px;
- outline-color: $black;
- height: 100%;
- }
-
- .react-autosuggest__container {
- margin: 0;
- color: $medium-gray;
- padding: 0px;
- }
- }
- }
-
- .collectionStackingViewChrome-pivotField:hover,
- .collectionTreeViewChrome-pivotField:hover,
- .collection3DCarouselViewChrome-scrollSpeed:hover {
- cursor: text;
- }
-
- }
-}
-
-.collectionMenu-webUrlButtons {
- margin-left: 44;
- background: lightGray;
+.collectionMenu-container {
display: flex;
-}
-
-.webBox-urlEditor {
- position: relative;
- opacity: 0.9;
- z-index: 901;
- transition: top .5s;
-
- .urlEditor {
- display: grid;
- grid-template-columns: 1fr auto;
- padding-bottom: 10px;
- overflow: hidden;
- margin-top: 5px;
- height: 35px;
-
- .editorBase {
- display: flex;
-
- .editor-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 40px;
- transform-origin: top left;
- }
-
- .switchToText {
- color: $medium-gray;
- }
-
- .switchToText:hover {
- color: $dark-gray;
- }
- }
-
- button:hover {
- transform: scale(1);
- }
- }
-}
-
-.collectionMenu-urlInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: $black;
- font-size: $small-text;
- letter-spacing: 2px;
- outline-color: $black;
- background: $white;
- width: 100%;
- min-width: 350px;
- margin-right: 10px;
- height: 100%;
-}
-
-.collectionFreeFormMenu-cont {
- display: inline-flex;
position: relative;
+ align-content: center;
+ justify-content: space-between;
+ background-color: $dark-gray;
+ height: 35px;
+ border-bottom: $standard-border;
+ padding-right: 5px;
align-items: center;
- height: 100%;
-
- .color-previewI {
- width: 60%;
- top: 80%;
- position: absolute;
- height: 4px;
- }
-
- .color-previewII {
- width: 80%;
- height: 80%;
- margin-left: 10%;
- position: absolute;
- bottom: 5;
- }
-
- .btn-group {
- display: grid;
- grid-template-columns: auto auto auto auto;
- margin: auto;
- /* Make the buttons appear below each other */
- }
- .btn-draw {
- display: inline-flex;
- margin: auto;
- /* Make the buttons appear below each other */
- }
-
- .fwdKeyframe,
- .numKeyframe,
- .backKeyframe {
+ .collectionMenu-hardCodedButton {
cursor: pointer;
- position: relative;
- width: 20;
- height: 30;
- bottom: 0;
- background: $dark-gray;
- display: inline-flex;
- align-items: center;
color: $white;
- }
-
- .backKeyframe {
- svg {
- display: block;
- margin: auto;
- }
- }
-
-
- .numKeyframe {
- flex-direction: column;
- padding-top: 5px;
- }
-
- .fwdKeyframe {
- svg {
- display: block;
- margin: auto;
- }
-
- border-right: solid $medium-gray 1px;
- }
-}
-
-.collectionSchemaViewChrome-cont {
- display: flex;
- font-size: $small-text;
-
- .collectionSchemaViewChrome-toggle {
- display: flex;
- margin-left: 10px;
- }
-
- .collectionSchemaViewChrome-label {
- text-transform: uppercase;
- letter-spacing: 2px;
- margin-right: 5px;
+ width: 25px;
+ height: 25px;
+ padding: 5;
+ text-align: center;
display: flex;
- flex-direction: column;
justify-content: center;
- }
-
- .collectionSchemaViewChrome-toggler {
- width: 100px;
- height: 35px;
- background-color: $black;
+ align-items: center;
position: relative;
- }
-
- .collectionSchemaViewChrome-togglerButton {
- width: 47px;
- height: 30px;
- background-color: $light-gray;
- // position: absolute;
- transition: all 0.5s ease;
- // top: 3px;
- margin-top: 3px;
- color: $medium-gray;
- letter-spacing: 2px;
- text-transform: uppercase;
- display: flex;
- flex-direction: column;
- justify-content: center;
- text-align: center;
+ transition: 0.2s;
+ border-radius: 3px;
- &.on {
- margin-left: 3px;
- }
-
- &.off {
- margin-left: 50px;
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.2);
}
}
}
-
-.commandEntry-outerDiv {
- display: flex;
- flex-direction: column;
- height: 40px;
-}
-
-.commandEntry-inputArea {
- display: flex;
- flex-direction: row;
- width: 150px;
- margin: auto auto auto auto;
-}
-
-.react-autosuggest__container {
- position: relative;
- width: 100%;
- margin-left: 5px;
- margin-right: 5px;
-}
-
-.react-autosuggest__input {
- border: 1px solid $light-gray;
- border-radius: 4px;
- width: 100%;
-}
-
-.react-autosuggest__input--focused {
- outline: none;
-}
-
-.react-autosuggest__input--open {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-.react-autosuggest__suggestions-container {
- display: none;
-}
-
-.react-autosuggest__suggestions-container--open {
- display: block;
- position: fixed;
- overflow-y: auto;
- max-height: 400px;
- width: 180px;
- border: 1px solid $light-gray;
- background-color: $white;
- font-family: $sans-serif;
- font-weight: 300;
- font-size: $large-header;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- z-index: 2;
-}
-
-.react-autosuggest__suggestions-list {
- margin: 0;
- padding: 0;
- list-style-type: none;
-}
-
-.react-autosuggest__suggestion {
- cursor: pointer;
- padding: 10px 20px;
-}
-
-.react-autosuggest__suggestion--highlighted {
- background-color: $light-gray;
-} \ No newline at end of file
+// .collectionMenu-cont {
+// position: relative;
+// display: inline-flex;
+// width: 100%;
+// opacity: 0.9;
+// z-index: 901;
+// transition: top .5s;
+// background: $dark-gray;
+// color: $white;
+// transform-origin: top left;
+// top: 0;
+// width: 100%;
+
+// .recordButtonOutline {
+// border-radius: 100%;
+// width: 18px;
+// height: 18px;
+// border: solid 1px $white;
+// display: flex;
+// align-items: center;
+// justify-content: center;
+// }
+
+// .recordButtonInner {
+// border-radius: 100%;
+// width: 70%;
+// height: 70%;
+// background: $white;
+// }
+
+// .collectionMenu {
+// display: flex;
+// height: 100%;
+// overflow: visible;
+// z-index: 901;
+// border: unset;
+
+// .collectionMenu-divider {
+// height: 100%;
+// margin-left: 3px;
+// margin-right: 3px;
+// width: 2px;
+// background-color: $medium-gray;
+// }
+
+// .collectionViewBaseChrome {
+// display: flex;
+// align-items: center;
+
+// .collectionViewBaseChrome-viewPicker {
+// font-size: $small-text;
+// outline-color: $black;
+// color: $white;
+// border: none;
+// background: $dark-gray;
+// }
+
+// .collectionViewBaseChrome-viewPicker:focus {
+// outline: none;
+// border: none;
+// }
+
+// .collectionViewBaseChrome-viewPicker:active {
+// outline-color: $black;
+// }
+
+// .collectionViewBaseChrome-button {
+// font-size: $small-text;
+// text-transform: uppercase;
+// letter-spacing: 2px;
+// background: $white;
+// color: $pink;
+// outline-color: $black;
+// border: none;
+// padding: 12px 10px 11px 10px;
+// margin-left: 10px;
+// }
+
+// .collectionViewBaseChrome-cmdPicker {
+// margin-left: 3px;
+// margin-right: 0px;
+// font-size: $small-text;
+// text-transform: capitalize;
+// color: $white;
+// border: none;
+// background: $dark-gray;
+// }
+
+// .collectionViewBaseChrome-cmdPicker:focus {
+// border: none;
+// outline: none;
+// }
+
+// .commandEntry-outerDiv {
+// pointer-events: all;
+// background-color: transparent;
+// display: flex;
+// flex-direction: row;
+// align-items: center;
+// justify-content: center;
+// height: 100%;
+// overflow: hidden;
+
+// .commandEntry-drop {
+// color: $white;
+// width: 30px;
+// margin-top: auto;
+// margin-bottom: auto;
+// }
+// }
+
+// .commandEntry-outerDiv:hover{
+// background-color: $drop-shadow;
+
+// .collectionViewBaseChrome-viewPicker,
+// .collectionViewBaseChrome-cmdPicker{
+// background: $dark-gray;
+// }
+// }
+
+// .collectionViewBaseChrome-collapse {
+// transition: all .5s, opacity 0.3s;
+// position: absolute;
+// width: 30px;
+// transform-origin: top left;
+// pointer-events: all;
+// // margin-top: 10px;
+// }
+
+// @media only screen and (max-device-width: 480px) {
+// .collectionViewBaseChrome-collapse {
+// display: none;
+// }
+// }
+
+// .collectionViewBaseChrome-template,
+// .collectionViewBaseChrome-viewModes {
+// align-items: center;
+// height: 100%;
+// display: flex;
+// background: transparent;
+// color: $medium-gray;
+// justify-content: center;
+// }
+
+// .collectionViewBaseChrome-viewSpecs {
+// margin-left: 5px;
+// display: grid;
+// border: none;
+// border-right: solid $medium-gray 1px;
+
+// .collectionViewBaseChrome-filterIcon {
+// position: relative;
+// display: flex;
+// margin: auto;
+// background: $dark-gray;
+// color: $white;
+// width: 30px;
+// height: 30px;
+// align-items: center;
+// justify-content: center;
+// border: none;
+// border-right: solid $medium-gray 1px;
+// }
+
+// .collectionViewBaseChrome-viewSpecsInput {
+// padding: 12px 10px 11px 10px;
+// border: 0px;
+// color: $medium-gray;
+// text-align: center;
+// letter-spacing: 2px;
+// outline-color: $black;
+// font-size: $small-text;
+// background: $white;
+// height: 100%;
+// width: 75px;
+// }
+
+// .collectionViewBaseChrome-viewSpecsMenu {
+// overflow: hidden;
+// transition: height .5s, display .5s;
+// position: absolute;
+// top: 60px;
+// z-index: 100;
+// display: flex;
+// flex-direction: column;
+// background: $white;
+// box-shadow: $medium-gray 2px 2px 4px;
+
+// .qs-datepicker {
+// left: unset;
+// right: 0;
+// }
+
+// .collectionViewBaseChrome-viewSpecsMenu-row {
+// display: grid;
+// grid-template-columns: 150px 200px 150px;
+// margin-top: 10px;
+// margin-right: 10px;
+
+// .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+// .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+// .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+// font-size: $small-text;
+// letter-spacing: 2px;
+// color: $medium-gray;
+// margin-left: 10px;
+// padding: 5px;
+// border: none;
+// outline-color: $black;
+// }
+// }
+
+// .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+// display: grid;
+// grid-template-columns: 1fr 1fr 1fr;
+// grid-gap: 10px;
+// margin: 10px;
+// }
+// }
+// }
+// }
+
+// .collectionStackingViewChrome-cont,
+// .collectionTreeViewChrome-cont,
+// .collection3DCarouselViewChrome-cont {
+// display: flex;
+// justify-content: space-between;
+// }
+
+// .collectionGridViewChrome-cont {
+// display: flex;
+// margin-left: 10;
+
+// .collectionGridViewChrome-viewPicker {
+// font-size: $small-text;
+// //text-transform: uppercase;
+// //letter-spacing: 2px;
+// background: $dark-gray;
+// color: $white;
+// outline-color: $black;
+// color: $white;
+// border: none;
+// border-right: solid $medium-gray 1px;
+// }
+
+// .collectionGridViewChrome-viewPicker:active {
+// outline-color: $black;
+// }
+
+// .grid-control {
+// align-self: center;
+// display: flex;
+// flex-direction: row;
+// margin-right: 5px;
+
+// .grid-icon {
+// margin-right: 5px;
+// align-self: center;
+// }
+
+// .flexLabel {
+// margin-bottom: 0;
+// }
+
+// .collectionGridViewChrome-entryBox {
+// width: 50%;
+// color: $black;
+// }
+
+// .collectionGridViewChrome-columnButton {
+// color: $black;
+// }
+// }
+// }
+
+// .collectionStackingViewChrome-sort,
+// .collectionTreeViewChrome-sort {
+// display: flex;
+// align-items: center;
+// justify-content: space-between;
+
+// .collectionStackingViewChrome-sortIcon,
+// .collectionTreeViewChrome-sortIcon {
+// transition: transform .5s;
+// margin-left: 10px;
+// }
+// }
+
+// button:hover {
+// transform: scale(1);
+// }
+
+
+// .collectionStackingViewChrome-pivotField-cont,
+// .collectionTreeViewChrome-pivotField-cont,
+// .collection3DCarouselViewChrome-scrollSpeed-cont {
+// justify-self: right;
+// align-items: center;
+// display: flex;
+// grid-auto-columns: auto;
+// font-size: $small-text;
+// letter-spacing: 2px;
+
+// .collectionStackingViewChrome-pivotField-label,
+// .collectionTreeViewChrome-pivotField-label,
+// .collection3DCarouselViewChrome-scrollSpeed-label {
+// grid-column: 1;
+// margin-right: 7px;
+// user-select: none;
+// font-family: $sans-serif;
+// letter-spacing: normal;
+// }
+
+// .collectionStackingViewChrome-sortIcon {
+// transition: transform .5s;
+// grid-column: 3;
+// text-align: center;
+// display: flex;
+// justify-content: center;
+// align-items: center;
+// cursor: pointer;
+// width: 25px;
+// height: 25px;
+// border-radius: 100%;
+// }
+
+// .collectionStackingViewChrome-sortIcon:hover {
+// background-color: $drop-shadow;
+// }
+
+// .collectionStackingViewChrome-pivotField,
+// .collectionTreeViewChrome-pivotField,
+// .collection3DCarouselViewChrome-scrollSpeed {
+// color: $white;
+// grid-column: 2;
+// grid-row: 1;
+// width: 90%;
+// min-width: 100px;
+// display: flex;
+// height: 80%;
+// border-radius: 7px;
+// align-items: center;
+// background: $white;
+
+// .editable-view-input,
+// input,
+// .editableView-container-editing-oneLine,
+// .editableView-container-editing {
+// margin: auto;
+// border: 0px;
+// color: $light-gray !important;
+// text-align: center;
+// letter-spacing: 2px;
+// outline-color: $black;
+// height: 100%;
+// }
+
+// .react-autosuggest__container {
+// margin: 0;
+// color: $medium-gray;
+// padding: 0px;
+// }
+// }
+// }
+
+// .collectionStackingViewChrome-pivotField:hover,
+// .collectionTreeViewChrome-pivotField:hover,
+// .collection3DCarouselViewChrome-scrollSpeed:hover {
+// cursor: text;
+// }
+
+// }
+// }
+
+// .collectionMenu-webUrlButtons {
+// margin-left: 44;
+// background: lightGray;
+// display: flex;
+// }
+
+// .webBox-urlEditor {
+// position: relative;
+// opacity: 0.9;
+// z-index: 901;
+// transition: top .5s;
+
+// .urlEditor {
+// display: grid;
+// grid-template-columns: 1fr auto;
+// padding-bottom: 10px;
+// overflow: hidden;
+// margin-top: 5px;
+// height: 35px;
+
+// .editorBase {
+// display: flex;
+
+// .editor-collapse {
+// transition: all .5s, opacity 0.3s;
+// position: absolute;
+// width: 40px;
+// transform-origin: top left;
+// }
+
+// .switchToText {
+// color: $medium-gray;
+// }
+
+// .switchToText:hover {
+// color: $dark-gray;
+// }
+// }
+
+// button:hover {
+// transform: scale(1);
+// }
+// }
+// }
+
+// .collectionMenu-urlInput {
+// padding: 12px 10px 11px 10px;
+// border: 0px;
+// color: $black;
+// font-size: $small-text;
+// letter-spacing: 2px;
+// outline-color: $black;
+// background: $white;
+// width: 100%;
+// min-width: 350px;
+// margin-right: 10px;
+// height: 100%;
+// }
+
+// .collectionFreeFormMenu-cont {
+// display: inline-flex;
+// position: relative;
+// align-items: center;
+// height: 100%;
+
+// .color-previewI {
+// width: 60%;
+// top: 80%;
+// position: absolute;
+// height: 4px;
+// }
+
+// .color-previewII {
+// width: 80%;
+// height: 80%;
+// margin-left: 10%;
+// position: absolute;
+// bottom: 5;
+// }
+
+// .btn-group {
+// display: grid;
+// grid-template-columns: auto auto auto auto;
+// margin: auto;
+// /* Make the buttons appear below each other */
+// }
+
+// .btn-draw {
+// display: inline-flex;
+// margin: auto;
+// /* Make the buttons appear below each other */
+// }
+
+// .fwdKeyframe,
+// .numKeyframe,
+// .backKeyframe {
+// cursor: pointer;
+// position: relative;
+// width: 20;
+// height: 30;
+// bottom: 0;
+// background: $dark-gray;
+// display: inline-flex;
+// align-items: center;
+// color: $white;
+// }
+
+// .backKeyframe {
+// svg {
+// display: block;
+// margin: auto;
+// }
+// }
+
+
+// .numKeyframe {
+// flex-direction: column;
+// padding-top: 5px;
+// }
+
+// .fwdKeyframe {
+// svg {
+// display: block;
+// margin: auto;
+// }
+
+// border-right: solid $medium-gray 1px;
+// }
+// }
+
+// .collectionSchemaViewChrome-cont {
+// display: flex;
+// font-size: $small-text;
+
+// .collectionSchemaViewChrome-toggle {
+// display: flex;
+// margin-left: 10px;
+// }
+
+// .collectionSchemaViewChrome-label {
+// text-transform: uppercase;
+// letter-spacing: 2px;
+// margin-right: 5px;
+// display: flex;
+// flex-direction: column;
+// justify-content: center;
+// }
+
+// .collectionSchemaViewChrome-toggler {
+// width: 100px;
+// height: 35px;
+// background-color: $black;
+// position: relative;
+// }
+
+// .collectionSchemaViewChrome-togglerButton {
+// width: 47px;
+// height: 30px;
+// background-color: $light-gray;
+// // position: absolute;
+// transition: all 0.5s ease;
+// // top: 3px;
+// margin-top: 3px;
+// color: $medium-gray;
+// letter-spacing: 2px;
+// text-transform: uppercase;
+// display: flex;
+// flex-direction: column;
+// justify-content: center;
+// text-align: center;
+
+// &.on {
+// margin-left: 3px;
+// }
+
+// &.off {
+// margin-left: 50px;
+// }
+// }
+// }
+
+
+// .commandEntry-outerDiv {
+// display: flex;
+// flex-direction: column;
+// height: 40px;
+// }
+
+// .commandEntry-inputArea {
+// display: flex;
+// flex-direction: row;
+// width: 150px;
+// margin: auto auto auto auto;
+// }
+
+// .react-autosuggest__container {
+// position: relative;
+// width: 100%;
+// margin-left: 5px;
+// margin-right: 5px;
+// }
+
+// .react-autosuggest__input {
+// border: 1px solid $light-gray;
+// border-radius: 4px;
+// width: 100%;
+// }
+
+// .react-autosuggest__input--focused {
+// outline: none;
+// }
+
+// .react-autosuggest__input--open {
+// border-bottom-left-radius: 0;
+// border-bottom-right-radius: 0;
+// }
+
+// .react-autosuggest__suggestions-container {
+// display: none;
+// }
+
+// .react-autosuggest__suggestions-container--open {
+// display: block;
+// position: fixed;
+// overflow-y: auto;
+// max-height: 400px;
+// width: 180px;
+// border: 1px solid $light-gray;
+// background-color: $white;
+// font-family: $sans-serif;
+// font-weight: 300;
+// font-size: $large-header;
+// border-bottom-left-radius: 4px;
+// border-bottom-right-radius: 4px;
+// z-index: 2;
+// }
+
+// .react-autosuggest__suggestions-list {
+// margin: 0;
+// padding: 0;
+// list-style-type: none;
+// }
+
+// .react-autosuggest__suggestion {
+// cursor: pointer;
+// padding: 10px 20px;
+// }
+
+// .react-autosuggest__suggestion--highlighted {
+// background-color: $light-gray;
+// } \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8f4df4a92..aefa1ec3d 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
-import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import { ColorState } from "react-color";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
@@ -15,29 +15,32 @@ import { RichTextField } from "../../../fields/RichTextField";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { DragManager } from "../../util/DragManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { EditableView } from "../EditableView";
import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke";
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
+import { LightboxView } from "../LightboxView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
+import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
import { PresBox } from "../nodes/trails/PresBox";
+import { DefaultStyleProvider } from "../StyleProvider";
+import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionLinearView } from "./collectionLinear";
import "./CollectionMenu.scss";
import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { TabDocView } from "./TabDocView";
-import { LightboxView } from "../LightboxView";
-import { Docs } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
+import { Colors } from "../global/globalEnums";
@observer
export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -46,6 +49,8 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable SelectedCollection: DocumentView | undefined;
@observable FieldKey: string;
+ private _docBtnRef = React.createRef<HTMLDivElement>();
+
constructor(props: any) {
super(props);
this.FieldKey = "";
@@ -57,7 +62,7 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
reaction(() => SelectionManager.Views().length && SelectionManager.Views()[0],
- (doc) => doc && this.SetSelection(doc));
+ view => view && this.SetSelection(view));
}
@action
@@ -82,30 +87,92 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
+ buttonBarXf = () => {
+ if (!this._docBtnRef.current) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ return new Transform(-translateX, -translateY, 1 / scale);
+ }
+
+ panelWidth100 = () => 100;
+ panelHeight35 = () => 35;
+
+ @computed get contMenuButtons() {
+ trace();
+ const selDoc = Doc.UserDoc().contextMenuBtns;
+ return !(selDoc instanceof Doc) ? (null) : <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: "35px" }} >
+ <CollectionLinearView
+ Document={selDoc}
+ DataDoc={undefined}
+ fieldKey={"data"}
+ dropAction={"alias"}
+ setHeight={returnFalse}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ rootSelected={returnTrue}
+ bringToFront={emptyFunction}
+ select={emptyFunction}
+ isContentActive={returnTrue}
+ isAnyChildContentActive={returnFalse}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ moveDocument={returnFalse}
+ CollectionView={undefined}
+ addDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={this.buttonBarXf}
+ PanelWidth={this.panelWidth100}
+ PanelHeight={this.panelHeight35}
+ renderDepth={0}
+ focus={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ </div>;
+ }
+
render() {
- const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
- <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- </button>
- </Tooltip>;
const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left";
const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel";
const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom">
- <button className="antimodeMenu-button" key="properties" style={{ backgroundColor: "#424242" }}
+ <div className="collectionMenu-hardCodedButton"
+ style={{ backgroundColor: CurrentUserUtils.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }}
+ key="properties"
onPointerDown={this.toggleProperties}>
<FontAwesomeIcon icon={propIcon} size="lg" />
- </button>
+ </div>
</Tooltip>;
- return this.getElement(!this.SelectedCollection ? [/*button*/] :
- [<CollectionViewBaseChrome key="chrome"
- docView={this.SelectedCollection}
- fieldKey={this.SelectedCollection.LayoutFieldKey}
- type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
- prop,
- /*button*/]);
+ // NEW BUTTONS
+ //dash col linear view buttons
+ const contMenuButtons =
+ <div className="collectionMenu-container">
+ {this.contMenuButtons}
+ {prop}
+ </div>;
+
+ return contMenuButtons;
+
+ // const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
+ // <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
+ // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+ // </button>
+ // </Tooltip>;
+
+ // OLD BUTTONS
+ // return this.getElement(!this.SelectedCollection ? [/*button*/] :
+ // [<CollectionViewBaseChrome key="chrome"
+ // docView={this.SelectedCollection}
+ // fieldKey={this.SelectedCollection.LayoutFieldKey}
+ // type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
+ // prop,
+ // /*button*/]);
}
}
@@ -374,10 +441,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
</div>);
}
- @computed get selectedDocumentView() {
- return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
- }
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
+ @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
@computed get notACollection() {
if (this.selectedDoc) {
const layoutField = Doc.LayoutField(this.selectedDoc);
@@ -485,8 +550,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
@undoBatch
onAliasButtonMoved = (e: PointerEvent) => {
const contentDiv = this.selectedDocumentView?.ContentDiv;
- if (contentDiv) {
- const dragData = new DragManager.DocumentDragData([this.selectedDocumentView!.props.Document]);
+ if (contentDiv && this.selectedDoc) {
+ const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
dragData.defaultDropAction = "alias";
dragData.canEmbed = true;
@@ -581,11 +646,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")];
}
@computed get childDocs() { return DocListCast(this.dataField); }
- @computed get selectedDocumentView() { return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; }
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
- @computed get isText() {
- return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
- }
+ @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
+ @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
+ @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; }
@undoBatch
@action
@@ -720,7 +783,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}>
•
- </button>
+ </button>
</Tooltip>)}
</div>;
}
@@ -759,7 +822,6 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>;
}
- @observable viewType = this.selectedDoc?._viewType;
render() {
return !this.props.docView.layoutDoc ? (null) :
@@ -991,7 +1053,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionMenuProp
<button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
<div className="collectionTreeViewChrome-sortLabel">
Sort
- </div>
+ </div>
<div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? "90" : this.ascending ? "180" : "0"}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
@@ -1225,3 +1287,4 @@ Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) {
CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
});
+
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 6bdeaf722..6a22acae8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -338,7 +338,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
{this.renderColors(this._col)}
<div className="collectionSchema-headerMenu-group">
<button onClick={() => { this.deleteColumn(this._col.heading); }}
- >Delete Column</button>
+ >Hide Column</button>
</div>
</div>;
}
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index e456c0664..59c21210a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,70 +1,94 @@
+@import "../global/globalCssVariables.scss";
+
.collectionStackedTimeline {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 1000;
+ overflow: hidden;
+ top: 0px;
+
+ .collectionStackedTimeline-trim-shade {
position: absolute;
- width: 100%;
height: 100%;
- border: gray solid 1px;
- border-radius: 3px;
- z-index: 1000;
- overflow: hidden;
- top: 0px;
+ background-color: $dark-gray;
+ opacity: 0.3;
+ }
- .collectionStackedTimeline-selector {
- position: absolute;
- width: 10px;
- top: 2.5%;
- height: 95%;
- background: lightblue;
- border-radius: 5px;
- opacity: 0.3;
- z-index: 500;
- border-style: solid;
- border-color: darkblue;
- border-width: 1px;
- }
+ .collectionStackedTimeline-trim-controls {
+ height: 100%;
+ position: absolute;
+ box-sizing: border-box;
+ border: 2px solid $medium-blue;
+ display: flex;
+ justify-content: space-between;
+ max-width: 100%;
- .collectionStackedTimeline-current {
- width: 1px;
- height: 100%;
- background-color: red;
- position: absolute;
- top: 0px;
- pointer-events: none;
+ .collectionStackedTimeline-trim-handle {
+ background-color: $medium-blue;
+ height: 100%;
+ width: 5px;
+ cursor: ew-resize;
}
+ }
- .collectionStackedTimeline-marker-timeline {
- position: absolute;
- top: 2.5%;
- height: 95%;
- border-radius: 4px;
- &:hover {
- opacity: 1;
- }
+ .collectionStackedTimeline-selector {
+ position: absolute;
+ width: 10px;
+ top: 2.5%;
+ height: 95%;
+ background: $light-blue;
+ border-radius: 3px;
+ opacity: 0.3;
+ z-index: 500;
+ border-style: solid;
+ border-color: $medium-blue;
+ border-width: 1px;
+ }
- .collectionStackedTimeline-left-resizer,
- .collectionStackedTimeline-resizer {
- background: dimgrey;
- position: absolute;
- top: 0;
- height: 100%;
- width: 10px;
- pointer-events: all;
- cursor: ew-resize;
- z-index: 100;
- }
- .collectionStackedTimeline-resizer {
- right: 0;
- }
- .collectionStackedTimeline-left-resizer {
- left: 0;
- }
+ .collectionStackedTimeline-current {
+ width: 1px;
+ height: 100%;
+ background-color: $pink;
+ position: absolute;
+ top: 0px;
+ pointer-events: none;
+ }
+
+ .collectionStackedTimeline-marker-timeline {
+ position: absolute;
+ top: 2.5%;
+ height: 95%;
+ border-radius: 4px;
+ &:hover {
+ opacity: 1;
}
- .collectionStackedTimeline-waveform {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- pointer-events: none;
+ .collectionStackedTimeline-left-resizer,
+ .collectionStackedTimeline-resizer {
+ background: $medium-gray;
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 10px;
+ pointer-events: all;
+ cursor: ew-resize;
+ z-index: 100;
+ }
+ .collectionStackedTimeline-resizer {
+ right: 0;
}
-} \ No newline at end of file
+ .collectionStackedTimeline-left-resizer {
+ left: 0;
+ }
+ }
+
+ .collectionStackedTimeline-waveform {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ }
+}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index a2c95df6e..d98d966d8 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,5 +1,12 @@
import React = require("react");
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import {
+ action,
+ computed,
+ IReactionDisposer,
+ observable,
+ reaction,
+ runInAction,
+} from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { Doc, DocListCast } from "../../../fields/Doc";
@@ -8,7 +15,16 @@ import { List } from "../../../fields/List";
import { listSpec, makeInterface } from "../../../fields/Schema";
import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
-import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent, returnTrue } from "../../../Utils";
+import {
+ emptyFunction,
+ formatTime,
+ OmitKeys,
+ returnFalse,
+ returnOne,
+ setupMoveUpEvents,
+ StopEvent,
+ returnTrue,
+} from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { LinkManager } from "../../util/LinkManager";
import { Scripting } from "../../util/Scripting";
@@ -18,9 +34,15 @@ import { undoBatch } from "../../util/UndoManager";
import { AudioWaveform } from "../AudioWaveform";
import { CollectionSubView } from "../collections/CollectionSubView";
import { LightboxView } from "../LightboxView";
-import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
+import {
+ DocAfterFocusFunc,
+ DocFocusFunc,
+ DocumentView,
+ DocumentViewProps,
+} from "../nodes/DocumentView";
import { LabelBox } from "../nodes/LabelBox";
import "./CollectionStackedTimeline.scss";
+import { Colors } from "../global/globalEnums";
type PanZoomDocument = makeInterface<[]>;
const PanZoomDocument = makeInterface();
@@ -36,11 +58,21 @@ export type CollectionStackedTimelineProps = {
endTag: string;
mediaPath: string;
dictationKey: string;
+ trimming: boolean;
+ trimStart: number;
+ trimEnd: number;
+ trimDuration: number;
+ setStartTrim: (newStart: number) => void;
+ setEndTrim: (newEnd: number) => void;
};
@observer
-export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument, CollectionStackedTimelineProps>(PanZoomDocument) {
- @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
+export class CollectionStackedTimeline extends CollectionSubView<
+ PanZoomDocument,
+ CollectionStackedTimelineProps
+>(PanZoomDocument) {
+ @observable static SelectingRegion: CollectionStackedTimeline | undefined =
+ undefined;
static RangeScript: ScriptField;
static LabelScript: ScriptField;
static RangePlayScript: ScriptField;
@@ -50,48 +82,111 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
private _markerStart: number = 0;
@observable _markerEnd: number = 0;
- get duration() { return this.props.duration; }
- @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
+ get minLength() {
+ const rect = this._timeline?.getBoundingClientRect();
+ if (rect) {
+ return 0.05 * this.duration;
+ }
+ return 0;
+ }
+
+ get trimStart() {
+ return this.props.trimStart;
+ }
+
+ get trimEnd() {
+ return this.props.trimEnd;
+ }
+
+ get duration() {
+ return this.props.duration;
+ }
+
+ @computed get currentTime() {
+ return NumCast(this.layoutDoc._currentTimecode);
+ }
@computed get selectionContainer() {
- return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="collectionStackedTimeline-selector" style={{
- left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`,
- width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%`
- }} />;
+ return CollectionStackedTimeline.SelectingRegion !== this ? null : (
+ <div
+ className="collectionStackedTimeline-selector"
+ style={{
+ left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.props.trimDuration) * 100}%`,
+ width: `${(Math.abs(this._markerStart - this._markerEnd) / this.props.trimDuration) * 100}%`,
+ }}
+ />
+ );
}
constructor(props: any) {
super(props);
// onClick play scripts
- CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
- CollectionStackedTimeline.RangePlayScript = CollectionStackedTimeline.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
+ CollectionStackedTimeline.RangeScript =
+ CollectionStackedTimeline.RangeScript ||
+ ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
+ self: Doc.name,
+ scriptContext: "any",
+ clientX: "number",
+ })!;
+ CollectionStackedTimeline.RangePlayScript =
+ CollectionStackedTimeline.RangePlayScript ||
+ ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
+ self: Doc.name,
+ scriptContext: "any",
+ clientX: "number",
+ })!;
}
- componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); }
+ componentDidMount() {
+ document.addEventListener("keydown", this.keyEvents, true);
+ }
componentWillUnmount() {
document.removeEventListener("keydown", this.keyEvents, true);
- if (CollectionStackedTimeline.SelectingRegion === this) runInAction(() => CollectionStackedTimeline.SelectingRegion = undefined);
+ if (CollectionStackedTimeline.SelectingRegion === this) {
+ runInAction(
+ () => (CollectionStackedTimeline.SelectingRegion = undefined)
+ );
+ }
}
- anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
+ anchorStart = (anchor: Doc) =>
+ NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]))
anchorEnd = (anchor: Doc, val: any = null) => {
const endVal = NumCast(anchor[this.props.endTag], val);
- return NumCast(anchor._timecodeToHide, endVal === undefined ? null : endVal);
+ return NumCast(
+ anchor._timecodeToHide,
+ endVal === undefined ? null : endVal
+ );
}
- toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration));
+ toTimeline = (screen_delta: number, width: number) => {
+ return Math.max(
+ this.trimStart,
+ Math.min(this.trimEnd, (screen_delta / width) * this.props.trimDuration + this.trimStart));
+ }
+
rangeClickScript = () => CollectionStackedTimeline.RangeScript;
rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
// for creating key anchors with key events
@action
keyEvents = (e: KeyboardEvent) => {
- if (!(e.target instanceof HTMLInputElement) && this.props.isSelected(true)) {
+ if (
+ !(e.target instanceof HTMLInputElement) &&
+ this.props.isSelected(true)
+ ) {
switch (e.key) {
case " ":
if (!CollectionStackedTimeline.SelectingRegion) {
this._markerStart = this._markerEnd = this.currentTime;
CollectionStackedTimeline.SelectingRegion = this;
} else {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
+ CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.props.fieldKey,
+ this.props.startTag,
+ this.props.endTag,
+ this.currentTime
+ );
CollectionStackedTimeline.SelectingRegion = undefined;
}
}
@@ -101,7 +196,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
+ const linkTime = NumCast(
+ la2[this.props.startTag],
+ NumCast(la1[this.props.startTag])
+ );
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
@@ -118,10 +216,18 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
const wasPlaying = this.props.playing();
if (wasPlaying) this.props.Pause();
const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
- setupMoveUpEvents(this, e,
- action(e => {
- if (!wasSelecting && CollectionStackedTimeline.SelectingRegion !== this) {
- this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e) => {
+ if (
+ !wasSelecting &&
+ CollectionStackedTimeline.SelectingRegion !== this
+ ) {
+ this._markerStart = this._markerEnd = this.toTimeline(
+ clientX - rect.x,
+ rect.width
+ );
CollectionStackedTimeline.SelectingRegion = this;
}
this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
@@ -134,32 +240,129 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
this._markerStart = this._markerEnd;
this._markerEnd = tmp;
}
- if (!isClick && CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15)) {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag,
- this._markerStart, this._markerEnd);
+ if (
+ !isClick &&
+ CollectionStackedTimeline.SelectingRegion === this &&
+ Math.abs(movement[0]) > 15 &&
+ !this.props.trimming
+ ) {
+ CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.props.fieldKey,
+ this.props.startTag,
+ this.props.endTag,
+ this._markerStart,
+ this._markerEnd
+ );
}
- (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined);
+ (!isClick || !wasSelecting) &&
+ (CollectionStackedTimeline.SelectingRegion = undefined);
}),
(e, doubleTap) => {
this.props.select(false);
- e.shiftKey && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
+ e.shiftKey &&
+ CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.props.fieldKey,
+ this.props.startTag,
+ this.props.endTag,
+ this.currentTime
+ );
!wasPlaying && doubleTap && this.props.Play();
},
- this.props.isSelected(true) || this.props.isContentActive(), undefined,
- () => !wasPlaying && this.props.setTime((clientX - rect.x) / rect.width * this.duration));
+ this.props.isSelected(true) || this.props.isContentActive(),
+ undefined,
+ () => {
+ !wasPlaying &&
+ (this.props.trimming && this.duration ?
+ this.props.setTime(((clientX - rect.x) / rect.width) * this.duration)
+ :
+ this.props.setTime(((clientX - rect.x) / rect.width) * this.props.trimDuration + this.trimStart)
+ );
+ }
+ );
}
+
+ }
+
+ @action
+ trimLeft = (e: React.PointerEvent): void => {
+ const rect = this._timeline?.getBoundingClientRect();
+ const clientX = e.movementX;
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e, [], []) => {
+ if (rect && this.props.isContentActive()) {
+ this.props.setStartTrim(Math.min(
+ Math.max(
+ this.trimStart + (e.movementX / rect.width) * this.duration,
+ 0
+ ),
+ this.trimEnd - this.minLength
+ ));
+ }
+ return false;
+ }),
+ emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ this.props.setStartTrim(0);
+ }
+ })
+ );
+ }
+
+ @action
+ trimRight = (e: React.PointerEvent): void => {
+ const rect = this._timeline?.getBoundingClientRect();
+ const clientX = e.movementX;
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e, [], []) => {
+ if (rect && this.props.isContentActive()) {
+ this.props.setEndTrim(Math.max(
+ Math.min(
+ this.trimEnd + (e.movementX / rect.width) * this.duration,
+ this.duration
+ ),
+ this.trimStart + this.minLength
+ ));
+ }
+ return false;
+ }),
+ emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ this.props.setEndTrim(this.duration);
+ }
+ })
+ );
}
@undoBatch
@action
- static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number) {
+ static createAnchor(
+ rootDoc: Doc,
+ dataDoc: Doc,
+ fieldKey: string,
+ startTag: string,
+ endTag: string,
+ anchorStartTime?: number,
+ anchorEndTime?: number
+ ) {
if (anchorStartTime === undefined) return rootDoc;
const anchor = Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`) as any,
+ title: ComputedField.MakeFunction(
+ `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
+ ) as any,
useLinkSmallAnchor: true,
hideLinkButton: true,
annotationOn: rootDoc,
- _timelineLabel: true
+ _timelineLabel: true,
});
Doc.GetProto(anchor)[startTag] = anchorStartTime;
Doc.GetProto(anchor)[endTag] = anchorEndTime;
@@ -179,7 +382,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
if (this.props.playing()) this.props.Pause();
else this.props.playFrom(seekTimeInSeconds, endTime);
} else {
- if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
+ if (
+ seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) &&
+ endTime > NumCast(this.layoutDoc._currentTimecode)
+ ) {
if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
this.props.Pause();
} else {
@@ -194,39 +400,60 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
@action
clickAnchor = (anchorDoc: Doc, clientX: number) => {
- if (anchorDoc.isLinkButton) LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+ if (anchorDoc.isLinkButton) {
+ LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+ }
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
const endTime = this.anchorEnd(anchorDoc);
- if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
+ if (
+ seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 &&
+ endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4
+ ) {
if (this.props.playing()) this.props.Pause();
else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
const rect = this._timeline?.getBoundingClientRect();
- rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ rect &&
+ this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
} else {
- if (this.layoutDoc.autoPlayAnchors) this.props.playFrom(seekTimeInSeconds, endTime);
- else this.props.setTime(seekTimeInSeconds);
+ if (this.layoutDoc.autoPlayAnchors) {
+ this.props.playFrom(seekTimeInSeconds, endTime);
+ }
+ else {
+ this.props.setTime(seekTimeInSeconds);
+ }
}
return { select: true };
}
-
// makes sure no anchors overlaps each other by setting the correct position and width
- getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => {
+ getLevel = (
+ m: Doc,
+ placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]
+ ) => {
const timelineContentWidth = this.props.PanelWidth();
const x1 = this.anchorStart(m);
- const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration);
+ const x2 = this.anchorEnd(
+ m,
+ x1 + (10 / timelineContentWidth) * this.duration
+ );
let max = 0;
- const overlappedLevels = new Set(placed.map(p => {
- const y1 = p.anchorStartTime;
- const y2 = p.anchorEndTime;
- if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) ||
- (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
- max = Math.max(max, p.level);
- return p.level;
- }
- }));
+ const overlappedLevels = new Set(
+ placed.map((p) => {
+ const y1 = p.anchorStartTime;
+ const y2 = p.anchorEndTime;
+ if (
+ (x1 >= y1 && x1 <= y2) ||
+ (x2 >= y1 && x2 <= y2) ||
+ (y1 >= x1 && y1 <= x2) ||
+ (y2 >= x1 && y2 <= x2)
+ ) {
+ max = Math.max(max, p.level);
+ return p.level;
+ }
+ })
+ );
let level = max + 1;
for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
@@ -235,82 +462,185 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
}
dictationHeightPercent = 50;
- dictationHeight = () => this.props.PanelHeight() * (100 - this.dictationHeightPercent) / 100;
- timelineContentHeight = () => this.props.PanelHeight() * this.dictationHeightPercent / 100;
- dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight());
+ dictationHeight = () =>
+ (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100
+ timelineContentHeight = () =>
+ (this.props.PanelHeight() * this.dictationHeightPercent) / 100
+ dictationScreenToLocalTransform = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(0, -this.timelineContentHeight())
@computed get renderDictation() {
const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
- return !dictation ? (null) : <div style={{ position: "absolute", height: "100%", top: this.timelineContentHeight(), background: "tan" }}>
- <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- Document={dictation}
- PanelHeight={this.dictationHeight}
- isAnnotationOverlay={true}
- isDocumentActive={returnFalse}
- select={emptyFunction}
- scaling={returnOne}
- xMargin={25}
- yMargin={10}
- ScreenToLocalTransform={this.dictationScreenToLocalTransform}
- whenChildContentsActiveChanged={emptyFunction}
- removeDocument={returnFalse}
- moveDocument={returnFalse}
- addDocument={returnFalse}
- CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}>
- </DocumentView>
- </div>;
+ return !dictation ? null : (
+ <div
+ style={{
+ position: "absolute",
+ height: "100%",
+ top: this.timelineContentHeight(),
+ background: Colors.LIGHT_BLUE,
+ }}
+ >
+ <DocumentView
+ {...OmitKeys(this.props, [
+ "NativeWidth",
+ "NativeHeight",
+ "setContentView",
+ ]).omit}
+ Document={dictation}
+ PanelHeight={this.dictationHeight}
+ isAnnotationOverlay={true}
+ isDocumentActive={returnFalse}
+ select={emptyFunction}
+ scaling={returnOne}
+ xMargin={25}
+ yMargin={10}
+ ScreenToLocalTransform={this.dictationScreenToLocalTransform}
+ whenChildContentsActiveChanged={emptyFunction}
+ removeDocument={returnFalse}
+ moveDocument={returnFalse}
+ addDocument={returnFalse}
+ CollectionView={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ></DocumentView>
+ </div>
+ );
}
@computed get renderAudioWaveform() {
- return !this.props.mediaPath ? (null) :
- <div className="collectionStackedTimeline-waveform" >
+ return !this.props.mediaPath ? null : (
+ <div className="collectionStackedTimeline-waveform">
<AudioWaveform
duration={this.duration}
mediaPath={this.props.mediaPath}
- dataDoc={this.dataDoc}
- PanelHeight={this.timelineContentHeight} />
- </div>;
+ layoutDoc={this.layoutDoc}
+ PanelHeight={this.timelineContentHeight}
+ trimming={this.props.trimming}
+ />
+ </div>
+ );
}
+
currentTimecode = () => this.currentTime;
render() {
const timelineContentWidth = this.props.PanelWidth();
- const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
- const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
+ const overlaps: {
+ anchorStartTime: number;
+ anchorEndTime: number;
+ level: number;
+ }[] = [];
+ const drawAnchors = this.childDocs.map((anchor) => ({
+ level: this.getLevel(anchor, overlaps),
+ anchor,
+ }));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
- const isActive = this.props.isContentActive() || this.props.isSelected(false);
- return <div className="collectionStackedTimeline" ref={(timeline: HTMLDivElement | null) => this._timeline = timeline}
- onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}>
- {drawAnchors.map(d => {
- const start = this.anchorStart(d.anchor);
- const end = this.anchorEnd(d.anchor, start + 10 / timelineContentWidth * this.duration);
- const left = start / this.duration * timelineContentWidth;
- const top = d.level / maxLevel * this.timelineContentHeight();
- const timespan = end - start;
- return this.props.Document.hideAnchors ? (null) :
- <div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]}
- style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${this.timelineContentHeight() / maxLevel}px` }}
- onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} >
- <StackedTimelineAnchor {...this.props}
- mark={d.anchor}
- rangeClickScript={this.rangeClickScript}
- rangePlayScript={this.rangePlayScript}
- left={left}
- top={top}
- width={timelineContentWidth * timespan / this.duration}
- height={this.timelineContentHeight() / maxLevel}
- toTimeline={this.toTimeline}
- layoutDoc={this.layoutDoc}
- currentTimecode={this.currentTimecode}
- _timeline={this._timeline}
- stackedTimeline={this}
- />
- </div>;
- })}
- {this.selectionContainer}
- {this.renderAudioWaveform}
- {this.renderDictation}
-
- <div className="collectionStackedTimeline-current" style={{ left: `${this.currentTime / this.duration * 100}%` }} />
- </div>;
+ const isActive =
+ this.props.isContentActive() || this.props.isSelected(false);
+ return (
+ <div
+ className="collectionStackedTimeline"
+ ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+ onClick={(e) => isActive && StopEvent(e)}
+ onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)}
+ >
+ {drawAnchors.map((d) => {
+
+ const start = this.anchorStart(d.anchor);
+ const end = this.anchorEnd(
+ d.anchor,
+ start + (10 / timelineContentWidth) * this.duration
+ );
+ const left = this.props.trimming ?
+ (start / this.duration) * timelineContentWidth
+ : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
+ const top = (d.level / maxLevel) * this.timelineContentHeight();
+ const timespan = end - start;
+ const width = (timespan / this.props.trimDuration) * timelineContentWidth;
+ const height = this.timelineContentHeight() / maxLevel;
+ return this.props.Document.hideAnchors ? null : (
+ <div
+ className={"collectionStackedTimeline-marker-timeline"}
+ key={d.anchor[Id]}
+ style={{
+ left,
+ top,
+ width: `${width}px`,
+ height: `${height}px`,
+ }}
+ onClick={(e) => {
+ this.props.playFrom(start, this.anchorEnd(d.anchor));
+ e.stopPropagation();
+ }}
+ >
+ <StackedTimelineAnchor
+ {...this.props}
+ mark={d.anchor}
+ rangeClickScript={this.rangeClickScript}
+ rangePlayScript={this.rangePlayScript}
+ left={left}
+ top={top}
+ width={width}
+ height={height}
+ toTimeline={this.toTimeline}
+ layoutDoc={this.layoutDoc}
+ currentTimecode={this.currentTimecode}
+ _timeline={this._timeline}
+ stackedTimeline={this}
+ trimStart={this.trimStart}
+ trimEnd={this.trimEnd}
+ />
+ </div>
+ );
+ })}
+ {!this.props.trimming && this.selectionContainer}
+ {this.renderAudioWaveform}
+ {this.renderDictation}
+
+ <div
+ className="collectionStackedTimeline-current"
+ style={{
+ left: this.props.trimming
+ ? `${(this.currentTime / this.duration) * 100}%`
+ : `${(this.currentTime - this.trimStart) / (this.trimEnd - this.trimStart) * 100}%`,
+ }}
+ />
+
+ {this.props.trimming && (
+ <>
+ <div
+ className="collectionStackedTimeline-trim-shade"
+ style={{ width: `${(this.trimStart / this.duration) * 100}%` }}
+ ></div>
+
+ <div
+ className="collectionStackedTimeline-trim-controls"
+ style={{
+ left: `${(this.trimStart / this.duration) * 100}%`,
+ width: `${((this.trimEnd - this.trimStart) / this.duration) * 100
+ }%`,
+ }}
+ >
+ <div
+ className="collectionStackedTimeline-trim-handle"
+ onPointerDown={this.trimLeft}
+ ></div>
+ <div
+ className="collectionStackedTimeline-trim-handle"
+ onPointerDown={this.trimRight}
+ ></div>
+ </div>
+
+ <div
+ className="collectionStackedTimeline-trim-shade"
+ style={{
+ left: `${(this.trimEnd / this.duration) * 100}%`,
+ width: `${((this.duration - this.trimEnd) / this.duration) * 100
+ }%`,
+ }}
+ ></div>
+ </>
+ )}
+ </div>
+ );
}
}
@@ -335,6 +665,8 @@ interface StackedTimelineAnchorProps {
currentTimecode: () => number;
isSelected: (outsideReaction?: boolean) => boolean;
stackedTimeline: CollectionStackedTimeline;
+ trimStart: number;
+ trimEnd: number;
}
@observer
class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
@@ -345,22 +677,41 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
this._lastTimecode = this.props.currentTimecode();
}
componentDidMount() {
- this._disposer = reaction(() => this.props.currentTimecode(),
+ this._disposer = reaction(
+ () => this.props.currentTimecode(),
(time) => {
- const dictationDoc = Cast(this.props.layoutDoc["data-dictation"], Doc, null);
- const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
- if (!LightboxView.LightboxDoc
+ const dictationDoc = Cast(
+ this.props.layoutDoc["data-dictation"],
+ Doc,
+ null
+ );
+ const isDictation =
+ dictationDoc &&
+ DocListCast(this.props.mark.links).some(
+ (link) =>
+ Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc
+ );
+ if (
+ !LightboxView.LightboxDoc &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
/*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
- && DocListCast(this.props.mark.links).length &&
+ DocListCast(this.props.mark.links).length &&
time > NumCast(this.props.mark[this.props.startTag]) &&
time < NumCast(this.props.mark[this.props.endTag]) &&
- this._lastTimecode < NumCast(this.props.mark[this.props.startTag])) {
- LinkManager.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true);
+ this._lastTimecode < NumCast(this.props.mark[this.props.startTag])
+ ) {
+ LinkManager.FollowLink(
+ undefined,
+ this.props.mark,
+ this.props as any as DocumentViewProps,
+ false,
+ true
+ );
}
this._lastTimecode = time;
- });
+ }
+ );
}
componentWillUnmount() {
this._disposer?.();
@@ -373,57 +724,136 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
return this.props.toTimeline(e.clientX - rect.x, rect.width);
};
const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
- const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
- if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
- else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
+ const timelineOnly =
+ Cast(anchor[this.props.startTag], "number", null) !== undefined;
+ if (timelineOnly) {
+ Doc.SetInPlace(
+ anchor,
+ left ? this.props.startTag : this.props.endTag,
+ time,
+ true
+ );
+ }
+ else {
+ left
+ ? (anchor._timecodeToShow = time)
+ : (anchor._timecodeToHide = time);
+ }
return false;
};
- setupMoveUpEvents(this, e,
+ setupMoveUpEvents(
+ this,
+ e,
(e) => changeAnchor(anchor, left, newTime(e)),
(e) => {
this.props.setTime(newTime(e));
this.props._timeline?.releasePointerCapture(e.pointerId);
},
- emptyFunction);
+ emptyFunction
+ );
}
- renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
+
+ @action
+ computeTitle = () => {
+ const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
+ const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+ return `#${formatTime(start)}-${formatTime(end)}`;
+ }
+
+ renderInner = computedFn(function (
+ this: StackedTimelineAnchor,
+ mark: Doc,
+ script: undefined | (() => ScriptField),
+ doublescript: undefined | (() => ScriptField),
+ x: number,
+ y: number,
+ width: number,
+ height: number
+ ) {
const anchor = observable({ view: undefined as any });
- const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+ const focusFunc = (
+ doc: Doc,
+ willZoom?: boolean,
+ scale?: number,
+ afterFocus?: DocAfterFocusFunc,
+ docTransform?: Transform
+ ) => {
this.props.playLink(mark);
this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
};
return {
- anchor, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- ref={action((r: DocumentView | null) => anchor.view = r)}
- Document={mark}
- DataDoc={undefined}
- renderDepth={this.props.renderDepth + 1}
- LayoutTemplate={undefined}
- LayoutTemplateString={LabelBox.LayoutString("data")}
- isDocumentActive={returnFalse}
- PanelWidth={() => width}
- PanelHeight={() => height}
- ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)}
- focus={focusFunc}
- rootSelected={returnFalse}
- onClick={script}
- onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
- ignoreAutoHeight={false}
- hideResizeHandles={true}
- bringToFront={emptyFunction}
- scriptContext={this.props.stackedTimeline} />
+ anchor,
+ view: (
+ <DocumentView
+ key="view"
+ {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ ref={action((r: DocumentView | null) => (anchor.view = r))}
+ Document={mark}
+ DataDoc={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())}
+ isDocumentActive={returnFalse}
+ PanelWidth={() => width}
+ PanelHeight={() => height}
+ ScreenToLocalTransform={() =>
+ this.props.ScreenToLocalTransform().translate(-x, -y)
+ }
+ focus={focusFunc}
+ rootSelected={returnFalse}
+ onClick={script}
+ onDoubleClick={
+ this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript
+ }
+ ignoreAutoHeight={false}
+ hideResizeHandles={true}
+ bringToFront={emptyFunction}
+ scriptContext={this.props.stackedTimeline}
+ />
+ ),
};
});
+
render() {
- const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.props.left, this.props.top, this.props.width, this.props.height);
- return <>
- {inner.view}
- {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) :
- <>
- <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
- <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
- </>}
- </>;
+ const inner = this.renderInner(
+ this.props.mark,
+ this.props.rangeClickScript,
+ this.props.rangePlayScript,
+ this.props.left,
+ this.props.top,
+ this.props.width,
+ this.props.height
+ );
+ return (
+ <>
+ {inner.view}
+ {!inner.anchor.view ||
+ !SelectionManager.IsSelected(inner.anchor.view) ? null : (
+ <>
+ <div
+ key="left"
+ className="collectionStackedTimeline-left-resizer"
+ onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
+ />
+ <div
+ key="right"
+ className="collectionStackedTimeline-resizer"
+ onPointerDown={(e) =>
+ this.onAnchorDown(e, this.props.mark, false)
+ }
+ />
+ </>
+ )}
+ </>
+ );
}
}
-Scripting.addGlobal(function formatToTime(time: number): any { return formatTime(time); }); \ No newline at end of file
+Scripting.addGlobal(function formatToTime(time: number): any {
+ return formatTime(time);
+});
+Scripting.addGlobal(function min(num1: number, num2: number): number {
+ return Math.min(num1, num2);
+});
+Scripting.addGlobal(function max(num1: number, num2: number): number {
+ return Math.max(num1, num2);
+}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 4b123c8b6..2f002736d 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -14,6 +14,30 @@
width: 100%;
}
+// TODO:glr Turn this into a seperate class
+.documentButtonMenu {
+ position: relative;
+ height: fit-content;
+ border-bottom: $standard-border;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ align-content: center;
+ padding: 5px 0 5px 0;
+
+ .documentExplanation {
+ width: 90%;
+ margin: 5px;
+ font-size: 11px;
+ background-color: $light-blue;
+ color: $medium-blue;
+ padding: 10px;
+ border-radius: 10px;
+ border: solid 2px $medium-blue;
+ }
+}
+
.collectionStackingView,
.collectionMasonryView {
height: 100%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4fedda1bd..540bfd1ef 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { DataSym, Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
import { collectionSchema, documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
@@ -11,7 +11,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils, returnTrue, returnEmptyDoclist, returnEmptyFilter } from "../../../Utils";
import { DocUtils, Docs } from "../../documents/Documents";
import { DragManager, dropActionType } from "../../util/DragManager";
import { SnappingManager } from "../../util/SnappingManager";
@@ -23,12 +23,14 @@ import { EditableView } from "../EditableView";
import { LightboxView } from "../LightboxView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView";
-import { StyleProp } from "../StyleProvider";
+import { StyleProp, DefaultStyleProvider } from "../StyleProvider";
import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
import "./CollectionStackingView.scss";
import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
+import { FontIconBox } from "../nodes/button/FontIconBox";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
const _global = (window /* browser */ || global /* node */) as any;
type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -234,6 +236,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
rootSelected={this.rootSelected}
+ showTitle={this.props.childShowTitle}
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -514,6 +517,47 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0));
}
+ @computed get buttonMenu() {
+ const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+ // TODO:glr Allow support for multiple buttons
+ if (menuDoc) {
+ const width: number = NumCast(menuDoc._width, 30);
+ const height: number = NumCast(menuDoc._height, 30);
+ console.log(menuDoc.title, width, height);
+ return (<div className="buttonMenu-docBtn"
+ style={{ width: width, height: height }}>
+ <DocumentView
+ Document={menuDoc}
+ DataDoc={menuDoc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={() => 35}
+ PanelHeight={() => 35}
+ renderDepth={this.props.renderDepth}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ );
+ }
+ }
+
@computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); }
@computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); }
@@ -529,34 +573,50 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
SetValue: this.addGroup,
contents: "+ ADD A GROUP"
};
+ const buttonMenu = this.rootDoc.buttonMenu;
+ const noviceExplainer = this.rootDoc.explainer;
+ console.log(noviceExplainer);
return (
- <div className="collectionStackingMasonry-cont" >
- <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
- ref={this.createRef}
- style={{
- overflowY: this.props.isContentActive() ? "auto" : "hidden",
- background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: this.backgroundEvents ? "all" : undefined
- }}
- onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
- onDrop={this.onExternalDrop.bind(this)}
- onContextMenu={this.onContextMenu}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
- {this.renderedSections}
- {!this.showAddAGroup ? (null) :
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
- style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
- <EditableView {...editableViewProps} />
- </div>}
- {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
- <Switch
- onChange={this.onToggle}
- onClick={this.onToggle}
- defaultChecked={true}
- checkedChildren="edit"
- unCheckedChildren="view"
- />} */}
- </div> </div>
+ <>
+ {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
+ {buttonMenu ? this.buttonMenu : null}
+ {Doc.UserDoc().noviceMode && noviceExplainer ?
+ <div className="documentExplanation">
+ {noviceExplainer}
+ </div>
+ : null
+ }
+ </div> : null}
+ <div className="collectionStackingMasonry-cont" >
+ <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
+ ref={this.createRef}
+ style={{
+ overflowY: this.props.isContentActive() ? "auto" : "hidden",
+ background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+ pointerEvents: this.backgroundEvents ? "all" : undefined
+ }}
+ onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
+ onDrop={this.onExternalDrop.bind(this)}
+ onContextMenu={this.onContextMenu}
+ onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
+ {this.renderedSections}
+ {!this.showAddAGroup ? (null) :
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
+ style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+ <EditableView {...editableViewProps} />
+ </div>}
+ {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
+ <Switch
+ onChange={this.onToggle}
+ onClick={this.onToggle}
+ defaultChecked={true}
+ checkedChildren="edit"
+ unCheckedChildren="view"
+ />} */}
+ </div>
+ </div>
+ </>
+
);
}
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 47733994b..58289a161 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -111,7 +111,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4");
@action pointerLeave = () => this._background = "inherit";
- textCallback = (char: string) => this.addNewTextDoc("", false, true);
+ textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b70df93da..06d20f015 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -321,12 +321,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
});
} else {
- let srcUrl: string | undefined;
- let srcWeb: Doc | undefined;
- if (SelectionManager.Views().length) {
- srcWeb = SelectionManager.Views()[0].props.Document;
- srcUrl = (srcWeb.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
- }
+ const srcWeb = SelectionManager.Docs().lastElement();
+ const srcUrl = (srcWeb?.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 89d42439e..4d62a1af4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { Document, listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyFunction } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DocumentManager } from '../../util/DocumentManager';
@@ -26,6 +26,7 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import { TreeView } from "./TreeView";
import React = require("react");
+import { Transform } from '../../util/Transform';
const _global = (window /* browser */ || global /* node */) as any;
export type collectionTreeViewProps = {
@@ -221,8 +222,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
childContextMenuItems = () => {
const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
- const filterScripts = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: filterScripts[i], label }));
+ const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
+ const icons = StrListCast(this.doc.childContextMenuIcons);
+ return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
}
@computed get treeViewElements() {
TraceMobx();
@@ -265,13 +267,48 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
return hideTitle ? (null) : (this.outlineMode ? this.documentTitle : this.editableTitle)(this.treeChildren);
}
- @computed get renderClearButton() {
- return !this.doc.treeViewShowClearButton ? (null) : <div key="toolbar">
- <button className="toolbar-button round-button" title="Empty" onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}>
- <FontAwesomeIcon icon={"trash"} size="sm" />
- </button>
- </div >;
+ @computed get buttonMenu() {
+ const menuDoc:Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+ // To create a multibutton menu add a CollectionLinearView
+ if (menuDoc){
+
+ const width: number = NumCast(menuDoc._width, 30);
+ const height: number = NumCast(menuDoc._height, 30);
+ console.log(menuDoc.title, width, height);
+ return (<div className="buttonMenu-docBtn"
+ style = {{width: width, height: height}}>
+ <DocumentView
+ Document={menuDoc}
+ DataDoc={menuDoc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={() => 35}
+ PanelHeight={() => 35}
+ renderDepth={this.props.renderDepth + 1}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>);
+ }
}
+
+
@computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); }
@computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); }
@@ -295,22 +332,34 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
TraceMobx();
const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined;
+ const buttonMenu = this.rootDoc.buttonMenu;
+ const noviceExplainer = this.rootDoc.explainer;
return !(this.doc instanceof Doc) || !this.treeChildren ? (null) :
- <div className="collectionTreeView-container"
- style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
- onContextMenu={this.onContextMenu}>
- <div className="collectionTreeView-dropTarget"
- style={{ background: background(), paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
- onWheel={e => e.stopPropagation()}
- onDrop={this.onTreeDrop}
- ref={this.createTreeDropTarget}>
+ <>
{this.titleBar}
- {this.renderClearButton}
- <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
- {this.treeViewElements}
- </ul>
- </div >
- </div>;
+ <div className="collectionTreeView-container"
+ style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
+ onContextMenu={this.onContextMenu}>
+ {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
+ {buttonMenu ? this.buttonMenu : null}
+ {Doc.UserDoc().noviceMode && noviceExplainer ?
+ <div className="documentExplanation">
+ {noviceExplainer}
+ </div>
+ : null
+ }
+ </div> : null}
+ <div className="collectionTreeView-dropTarget"
+ style={{ background: background(), paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
+ onWheel={e => e.stopPropagation()}
+ onDrop={this.onTreeDrop}
+ ref={this.createTreeDropTarget}>
+ <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
+ {this.treeViewElements}
+ </ul>
+ </div >
+ </div>
+ </>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 229e93b9e..bc02c44f0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -24,7 +24,7 @@ import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionDockingView } from "./CollectionDockingView";
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
-import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionLinearView } from './collectionLinear';
import CollectionMapView from './CollectionMapView';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
@@ -71,6 +71,7 @@ export interface CollectionViewProps extends FieldViewProps {
childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
childFitWidth?: () => boolean;
+ childShowTitle?: () => string;
childOpacity?: () => number;
childContextMenuItems?: () => { script: ScriptField, label: string }[];
childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index a963f1cb9..7f62ecaa0 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,3 +1,6 @@
+@import "../global/globalCssVariables.scss";
+
+
input.lm_title:focus,
input.lm_title {
max-width: unset !important;
@@ -57,12 +60,11 @@ input.lm_title {
}
}
-.miniMap-hidden,
.miniMap {
position: absolute;
overflow: hidden;
- right: 10;
- bottom: 10;
+ right: 15;
+ bottom: 15;
border: solid 1px;
box-shadow: black 0.4vw 0.4vw 0.8vw;
width: 100%;
@@ -82,17 +84,21 @@ input.lm_title {
}
.miniMap-hidden {
+ cursor: pointer;
position: absolute;
- bottom: 0;
- right: 0;
- width: 45px;
- height: 45px;
- transform: translate(20px, 20px) rotate(45deg);
- border-radius: 30px;
+ bottom: 5;
+ display: flex;
+ right: 5;
+ width: 25px;
+ height: 25px;
+ border-radius: 3px;
padding: 2px;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+ background-color: $light-gray;
- >svg {
- margin-top: 3px;
- transform: translate(0px, 7px);
+ &:hover {
+ box-shadow: none;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 117dba4de..34cb6ec55 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -33,6 +33,7 @@ import { CollectionView, CollectionViewType } from './CollectionView';
import "./TabDocView.scss";
import React = require("react");
import Color = require('color');
+import { Colors, Shadows } from '../global/globalEnums';
const _global = (window /* browser */ || global /* node */) as any;
interface TabDocViewProps {
@@ -123,6 +124,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
tab.element[0].prepend(iconWrap);
tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
({ layer, color }) => {
+ // console.log("TabDocView: " + this.tabColor);
+ // console.log("lightOrDark: " + lightOrDark(this.tabColor));
const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
titleEle.style.color = textColor;
titleEle.style.backgroundColor = "transparent";
@@ -131,12 +134,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
moreInfoDrag.style.backgroundColor = textColor;
tab.element[0].style.background = !layer ? color : "dimgrey";
}, { fireImmediately: true });
- // TODO:glr fix
- // tab.element[0].style.borderTopRightRadius = "8px";
- // tab.element[0].children[1].appendChild(toggle);
- // tab._disposers.layerDisposer = reaction(() =>
- // ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
- // ({ layer, color }) => toggle.style.background = !layer ? color : "dimgrey", { fireImmediately: true });
}
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: MouseEvent) => {
@@ -390,8 +387,16 @@ export class TabDocView extends React.Component<TabDocViewProps> {
background={this.miniMapColor}
document={this._document}
tabView={this.tabView} />
- <Tooltip style={{ display: this.disableMinimap() ? "none" : undefined }} key="ttip" title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
- <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
+ <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? "Open minimap" : "Close minimap"}</div>}>
+ <div className="miniMap-hidden"
+ style={{
+ display: this.disableMinimap() || this._document._viewType !== "freeform" ? "none" : undefined,
+ color: this._document.hideMinimap ? Colors.BLACK : Colors.WHITE,
+ backgroundColor: this._document.hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
+ boxShadow: this._document.hideMinimap ? Shadows.STANDARD_SHADOW : undefined
+ }}
+ onPointerDown={e => e.stopPropagation()}
+ onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
<FontAwesomeIcon icon={"globe-asia"} size="lg" />
</div>
</Tooltip>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 86fd21677..8cf34ddcc 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -154,8 +154,8 @@ export class TreeView extends React.Component<TreeViewProps> {
} else {
// choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
// this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
- // choose an appropriate alias or make one -- -- choose the first alias that (1) the user owns, (2) has no context field - if I own it and someone else does not have it open,, otherwise create an alias
- this.props.addDocTab(this.props.document, "add:right");
+ const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right");
}
}
@@ -525,7 +525,8 @@ export class TreeView extends React.Component<TreeViewProps> {
childContextMenuItems = () => {
const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], label }));
+ const icons = StrListCast(this.doc.childContextMenuIcons);
+ return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
}
onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
@@ -835,7 +836,7 @@ export class TreeView extends React.Component<TreeViewProps> {
dontRegisterView: boolean | undefined,
observerHeight: (ref: any) => void,
unobserveHeight: (ref: any) => void,
- contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string }[])
+ contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[])
) {
const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 16258404d..3b3e069d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -5,7 +5,6 @@ import { Id } from "../../../../fields/FieldSymbols";
import { List } from "../../../../fields/List";
import { NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { DocumentType } from "../../../documents/DocumentTypes";
import { LinkManager } from "../../../util/LinkManager";
import { SnappingManager } from "../../../util/SnappingManager";
import { DocumentView } from "../../nodes/DocumentView";
@@ -43,8 +42,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
const adiv = acont.length ? acont[0] : A.ContentDiv;
const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
const a = adiv.getBoundingClientRect();
@@ -69,8 +68,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
const m = targetAhyperlink.getBoundingClientRect();
const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- linkDoc.anchor1_x = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
- linkDoc.anchor1_y = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
+ const mpx = mp[0] / A.props.PanelWidth();
+ const mpy = mp[1] / A.props.PanelHeight();
+ if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
}
if (!targetBhyperlink) {
if (linkDoc.linkAutoMove) {
@@ -80,8 +81,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
const m = targetBhyperlink.getBoundingClientRect();
const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- linkDoc.anchor2_x = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
- linkDoc.anchor2_y = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
+ const mpx = mp[0] / B.props.PanelWidth();
+ const mpy = mp[1] / B.props.PanelHeight();
+ if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
}
}
@@ -140,8 +143,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const adiv = (acont.length ? acont[0] : A.ContentDiv);
- const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
+ const adiv = acont.length ? acont[0] : A.ContentDiv;
+ const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return;
const a = adiv.getBoundingClientRect();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index e812064b7..dacbb3508 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,30 +1,17 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { Utils } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
-import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
@observer
export class CollectionFreeFormLinksView extends React.Component {
@computed get uniqueConnections() {
- const connections = DocumentManager.Instance.LinkedDocumentViews
- .filter(c => c.a.props.Document.type === DocumentType.LINK || c.b.props.Document.type === DocumentType.LINK)
- .reduce((drawnPairs, connection) => {
- const matchingPairs = drawnPairs.filter(pair => connection.a === pair.a && connection.b === pair.b);
- matchingPairs.forEach(drawnPair => drawnPair.l.add(connection.l));
- if (!matchingPairs.length) drawnPairs.push({ a: connection.a, b: connection.b, l: new Set<Doc>([connection.l]) });
- return drawnPairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Set<Doc> }[]);
- const set = new Map<Doc, { a: DocumentView, b: DocumentView, l: Doc[] }>();
- connections.map(c => !set.has(Array.from(c.l)[0]) && set.set(Array.from(c.l)[0], { a: c.a, b: c.b, l: Array.from(c.l) }));
- return Array.from(set.values()).map(c => <CollectionFreeFormLinkView key={c.l[0][Id]} A={c.a} B={c.b} LinkDocs={c.l} />);
+ return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c =>
+ <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />
+ );
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fb949a36d..c8561d901 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -510,7 +510,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
- { title: "ink stroke", x: B.x - Number(ActiveInkWidth()) / 2, y: B.y - Number(ActiveInkWidth()) / 2, _width: B.width + Number(ActiveInkWidth()), _height: B.height + Number(ActiveInkWidth()) });
+ { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -1052,7 +1052,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
freezeDimensions={this.props.childFreezeDimensions}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
- dontRegisterView={this.props.dontRegisterView}
+ showTitle={this.props.childShowTitle}
+ dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
(this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
@@ -1457,7 +1458,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
<div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}>
- {this.layoutDoc["_backgroundGrid-show"] ? this.backgroundGrid : (null)}
+ {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
<CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.contentTransform}
diff --git a/src/client/views/collections/collectionFreeForm/index.ts b/src/client/views/collections/collectionFreeForm/index.ts
new file mode 100644
index 000000000..702dc8d42
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/index.ts
@@ -0,0 +1,7 @@
+export * from "./CollectionFreeFormLayoutEngines";
+export * from "./CollectionFreeFormLinkView";
+export * from "./CollectionFreeFormLinksView";
+export * from "./CollectionFreeFormRemoteCursors";
+export * from "./CollectionFreeFormView";
+export * from "./MarqueeOptionsMenu";
+export * from "./MarqueeView"; \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/index.ts b/src/client/views/collections/collectionGrid/index.ts
new file mode 100644
index 000000000..be5d5667a
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/index.ts
@@ -0,0 +1,2 @@
+export * from "./Grid";
+export * from "./CollectionGridView"; \ No newline at end of file
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 46e40489b..8fe804466 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,15 +1,31 @@
-@import "../global/globalCssVariables";
-@import "../_nodeModuleOverrides";
+@import "../../global/globalCssVariables";
+@import "../../_nodeModuleOverrides";
.collectionLinearView-outer {
overflow: visible;
height: 100%;
pointer-events: none;
+ &.true {
+ padding-left: 5px;
+ padding-right: 5px;
+ border-left: $standard-border;
+ background-color: $medium-blue-alt;
+ }
+
+ >input:not(:checked)~&.true {
+ background-color: transparent;
+ }
+
.collectionLinearView {
display: flex;
height: 100%;
align-items: center;
+ gap: 5px;
+
+ .collectionView {
+ overflow: visible !important;
+ }
>span {
background: $dark-gray;
@@ -41,7 +57,7 @@
}
.bottomPopup-descriptions {
- cursor:pointer;
+ cursor: pointer;
display: inline;
white-space: nowrap;
padding-left: 8px;
@@ -54,7 +70,7 @@
}
.bottomPopup-exit {
- cursor:pointer;
+ cursor: pointer;
display: inline;
white-space: nowrap;
margin-right: 10px;
@@ -67,29 +83,26 @@
}
>label {
- margin-top: "auto";
- margin-bottom: "auto";
- background: $dark-gray;
- color: $white;
- display: inline-block;
- border-radius: 18px;
- font-size: 12.5px;
- width: 18px;
- height: 18px;
- margin-top: auto;
- margin-bottom: auto;
- margin-right: 3px;
+ pointer-events: all;
cursor: pointer;
+ background-color: $medium-blue;
+ padding: 5;
+ border-radius: 2px;
+ height: 25;
+ min-width: 25;
+ margin: 0;
+ color: $white;
+ display: flex;
+ font-weight: 100;
+ width: fit-content;
transition: transform 0.2s;
- }
-
- label p {
- padding-left: 5px;
- }
+ align-items: center;
+ justify-content: center;
+ transition: 0.2s;
- label:hover {
- background: $medium-gray;
- transform: scale(1.15);
+ &:hover{
+ filter: brightness(0.85);
+ }
}
>input {
@@ -110,13 +123,11 @@
display: flex;
opacity: 1;
position: relative;
- margin-top: auto;
.collectionLinearView-docBtn,
.collectionLinearView-docBtn-scalable {
position: relative;
margin: auto;
- margin-left: 3px;
transform-origin: center 80%;
}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
new file mode 100644
index 000000000..7fe95fef0
--- /dev/null
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -0,0 +1,226 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from '../../../../fields/FieldSymbols';
+import { makeInterface } from '../../../../fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
+import { DragManager } from '../../../util/DragManager';
+import { Transform } from '../../../util/Transform';
+import { Colors, Shadows } from '../../global/globalEnums';
+import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { DocumentView } from '../../nodes/DocumentView';
+import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
+import { StyleProp } from '../../StyleProvider';
+import { CollectionSubView } from '../CollectionSubView';
+import { CollectionViewType } from '../CollectionView';
+import "./CollectionLinearView.scss";
+
+
+type LinearDocument = makeInterface<[typeof documentSchema,]>;
+const LinearDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionLinearView extends CollectionSubView(LinearDocument) {
+ @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ @observable private _selectedIndex = -1;
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _widthDisposer?: IReactionDisposer;
+ private _selectedDisposer?: IReactionDisposer;
+
+ componentWillUnmount() {
+ this._dropDisposer?.();
+ this._widthDisposer?.();
+ this._selectedDisposer?.();
+ this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
+ }
+
+ componentDidMount() {
+ this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10),
+ width => this.childDocs.length && (this.layoutDoc._width = width),
+ { fireImmediately: true }
+ );
+
+ this._selectedDisposer = reaction(
+ () => NumCast(this.layoutDoc.selectedIndex),
+ (i) => runInAction(() => {
+ this._selectedIndex = i;
+ let selected: any = undefined;
+ this.childLayoutPairs.map(async (pair, ind) => {
+ const isSelected = this._selectedIndex === ind;
+ if (isSelected) {
+ selected = pair;
+ }
+ else {
+ ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
+ }
+ });
+ if (selected && selected.layout) {
+ ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
+ }
+ }),
+ { fireImmediately: true }
+ );
+ }
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this._dropDisposer && this._dropDisposer();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+ }
+ }
+
+ dimension = () => NumCast(this.rootDoc._height); // 2 * the padding
+ getTransform = (ele: Opt<HTMLDivElement>) => {
+ if (!ele) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
+ return new Transform(-translateX, -translateY, 1);
+ }
+
+ @action
+ exitLongLinks = () => {
+ if (DocumentLinksButton.StartLink) {
+ if (DocumentLinksButton.StartLink.Document) {
+ action((e: React.PointerEvent<HTMLDivElement>) => {
+ Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+ });
+ }
+ }
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ }
+
+ @action
+ changeDescriptionSetting = () => {
+ if (LinkDescriptionPopup.showDescriptions) {
+ if (LinkDescriptionPopup.showDescriptions === "ON") {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ } else {
+ LinkDescriptionPopup.showDescriptions = "ON";
+ }
+ } else {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+ }
+
+ myContextMenu = (e: React.MouseEvent) => {
+ console.log("STOPPING");
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+
+ getDisplayDoc = (doc: Doc) => {
+ const nested = doc._viewType === CollectionViewType.Linear;
+ const hidden = doc.hidden === true;
+
+ let dref: Opt<HTMLDivElement>;
+ const docXf = () => this.getTransform(dref);
+ // const scalable = pair.layout.onClick || pair.layout.onDragStart;
+ return hidden ? (null) : <div className={`collectionLinearView-docBtn`} key={doc[Id]} ref={r => dref = r || undefined}
+ style={{
+ pointerEvents: "all",
+ width: nested ? undefined : NumCast(doc._width),
+ height: nested ? undefined : NumCast(doc._height),
+ marginLeft: !nested ? 2.5 : 0,
+ marginRight: !nested ? 2.5 : 0,
+ // width: NumCast(pair.layout._width),
+ // height: NumCast(pair.layout._height),
+ }} >
+ <DocumentView
+ Document={doc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={docXf}
+ PanelWidth={nested ? doc[WidthSym] : this.dimension}
+ PanelHeight={nested ? doc[HeightSym] : this.dimension}
+ renderDepth={this.props.renderDepth + 1}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ </div>;
+ }
+
+ render() {
+ const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label
+ const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content
+ const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content
+ const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not
+ const floating: boolean = BoolCast(this.props.Document.linearViewFloating); // Specify whether it is expandable or not
+
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle
+ const menuOpener = <label htmlFor={`${guid}`}
+ style={{
+ boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined,
+ color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK,
+ backgroundColor: backgroundColor === color ? "black" : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <div className="collectionLinearView-menuOpener">
+ {BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={"minus"} /> : icon ? icon : <FontAwesomeIcon icon={"plus"} />}
+ </div>
+ </label>;
+
+ return <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : "transparent" }}>
+ <div className="collectionLinearView" ref={this.createDashEventsTarget}
+ onContextMenu={this.myContextMenu} >
+ {!expandable ? (null) : <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close" : "Open"}</div></>} placement="top">
+ {menuOpener}
+ </Tooltip>}
+ <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
+ onChange={action(() => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
+
+ <div className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir,
+ gap: flexGap
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ {DocumentLinksButton.StartLink && StrCast(this.layoutDoc.title) === "docked buttons" ? <span className="bottomPopup-background" style={{
+ pointerEvents: "all"
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <span className="bottomPopup-text" >
+ Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
+ </span>
+
+ <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+ Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ </span>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+ Stop
+ </span>
+ </Tooltip>
+
+ </span> : null}
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts
new file mode 100644
index 000000000..ff73e14ae
--- /dev/null
+++ b/src/client/views/collections/collectionLinear/index.ts
@@ -0,0 +1 @@
+export * from "./CollectionLinearView"; \ No newline at end of file
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index aaa50ba67..a25f962df 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -191,7 +191,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
{this.renderSorting()}
{this.renderColors()}
<div className="collectionSchema-headerMenu-group">
- <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Delete Column</button>
+ <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Hide Column</button>
</div>
</>
}
@@ -413,7 +413,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
bool = fields ? fields[1] === "check" : false;
}
return <div key={key} className="key-option" style={{
- border: "1px solid lightgray", paddingLeft: 5, textAlign: "left",
+ paddingLeft: 5, textAlign: "left",
width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white",
}}
>
@@ -489,8 +489,10 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
}
render() {
return (
- <div style={{ display: "flex" }} ref={this.setNode}>
- <FontAwesomeIcon onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} />
+ <div style={{ display: "flex", width: '100%', alignContent: 'center', alignItems: 'center' }} ref={this.setNode}>
+ <div className="schema-icon" onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }}>
+ <FontAwesomeIcon icon={this.props.icon} size="lg" style={{ display: "inline" }} />
+ </div>
{/* <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => {
runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen })
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
index f48906ba5..0e19ef3d9 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
@@ -134,9 +134,9 @@ export class MovableRow extends React.Component<MovableRowProps> {
<div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
<ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} >
<div className="row-dragger">
- <div className="row-option" style={{ left: 5 }} onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
- <div className="row-option" style={{ cursor: "grab", left: 25 }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
- <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+ <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
+ <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
+ <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
</div>
{children}
</ReactTableDefaults.TrComponent>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 40cdcd14b..3074ce66e 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -108,9 +108,7 @@
}
.rt-th {
padding: 0;
- border: solid lightgray;
- border-width: 0 1px;
- border-bottom: 2px solid lightgray;
+ border-left: solid 1px $light-gray;
}
}
.rt-th {
@@ -213,6 +211,8 @@
}
}
+
+
.collectionSchemaView-header {
height: 100%;
color: gray;
@@ -227,6 +227,15 @@ button.add-column {
width: 28px;
}
+.collectionSchemaView-menuOptions-wrapper {
+ background: rgb(241, 239, 235);
+ display: flex;
+ cursor: default;
+ height: 100%;
+ align-content: center;
+ align-items: center;
+}
+
.collectionSchema-header-menuOptions {
color: black;
width: 180px;
@@ -272,6 +281,9 @@ button.add-column {
width: 10px;
}
}
+
+
+
.keys-dropdown {
position: relative;
//width: 100%;
@@ -287,26 +299,7 @@ button.add-column {
font-weight: normal;
}
}
- .keys-options-wrapper {
- width: 100%;
- max-height: 150px;
- overflow-y: scroll;
- position: absolute;
- top: 28px;
- box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
- background-color: white;
- .key-option {
- background-color: white;
- border: 1px solid lightgray;
- padding: 2px 3px;
- &:not(:first-child) {
- border-top: 0;
- }
- &:hover {
- background-color: $light-gray;
- }
- }
- }
+
}
.columnMenu-colors {
display: flex;
@@ -325,11 +318,53 @@ button.add-column {
}
}
+.schema-icon {
+ cursor: pointer;
+ width: 25px;
+ height: 25px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-content: center;
+ background-color: $medium-blue;
+ color: white;
+ margin-right: 5px;
+ font-size: 10px;
+ border-radius: 3px;
+
+}
+
+.keys-options-wrapper {
+ position: absolute;
+ text-align: left;
+ height: fit-content;
+ top: 100%;
+ z-index: 21;
+ background-color: #ffffff;
+ box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+ padding: 1px;
+ .key-option {
+ cursor: pointer;
+ color: #000000;
+ width: 100%;
+ height: 25px;
+ font-weight: 400;
+ display: flex;
+ justify-content: left;
+ align-items: center;
+ padding-left: 5px;
+ &:hover {
+ background-color: $light-gray;
+ }
+ }
+}
+
.collectionSchema-row {
height: 100%;
background-color: white;
&.row-focused .rt-td {
- background-color: #bfffc0; //$light-gray;
+ background-color: $light-blue; //$light-gray;
+ overflow: visible;
}
&.row-wrapped {
.rt-td {
@@ -338,39 +373,40 @@ button.add-column {
}
.row-dragger {
display: flex;
- justify-content: space-around;
- //flex: 50 0 auto;
- width: 0;
- max-width: 50px;
- //height: 100%;
+ justify-content: space-evenly;
+ width: 58px;
+ position: absolute;
+ /* max-width: 50px; */
min-height: 30px;
align-items: center;
color: lightgray;
background-color: white;
transition: color 0.1s ease;
.row-option {
- // padding: 5px;
+ color: black;
cursor: pointer;
- position: absolute;
+ position: relative;
transition: color 0.1s ease;
display: flex;
flex-direction: column;
justify-content: center;
z-index: 2;
+ border-radius: 3px;
+ padding: 3px;
&:hover {
- color: gray;
+ background-color: $light-gray;
}
}
}
.collectionSchema-row-wrapper {
&.row-above {
- border-top: 1px solid red;
+ border-top: 1px solid $medium-blue;
}
&.row-below {
- border-bottom: 1px solid red;
+ border-bottom: 1px solid $medium-blue;
}
&.row-inside {
- border: 1px solid red;
+ border: 2px dashed $medium-blue;
}
.row-dragging {
background-color: blue;
@@ -383,24 +419,32 @@ button.add-column {
height: unset;
}
+.collectionSchemaView-cellContents {
+ width: 100%;
+}
+
.collectionSchemaView-cellWrapper {
+ display: flex;
height: 100%;
- padding: 4px;
text-align: left;
padding-left: 19px;
position: relative;
+ align-items: center;
+ align-content: center;
&:focus {
outline: none;
}
&.editing {
padding: 0;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ transform: scale(1.1);
+ z-index: 40;
input {
outline: 0;
border: none;
- background-color: rgb(255, 217, 217);
+ background-color: $white;
width: 100%;
height: 100%;
- padding: 2px 3px;
min-height: 26px;
}
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index fed64b620..dfe99ffc8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -338,7 +338,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
{this.renderColors(this._col)}
<div className="collectionSchema-headerMenu-group">
<button onClick={() => { this.deleteColumn(this._col.heading); }}
- >Delete Column</button>
+ >Hide Column</button>
</div>
</div>;
}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index 631f623c6..3833f968b 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -194,10 +194,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
const header = <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}>
{keysDropdown}
- <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}>
+ <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "pointer" }}>
<FontAwesomeIcon icon={sortIcon} size="lg" />
</div>
- {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>}
+ {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
</div>;
return {
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 7556f8b8a..caa9f4fe5 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -1,6 +1,7 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
// colors
$white: #ffffff;
+$off-white: #fdfdfd;
$light-gray: #dfdfdf;
$medium-gray: #9f9f9f;
$dark-gray: #323232;
@@ -8,6 +9,7 @@ $black: #000000;
$light-blue: #bdddf5;
$medium-blue: #4476f7;
+$medium-blue-alt: #4476f73d;
$pink: #e0217d;
$yellow: #f5d747;
@@ -15,6 +17,7 @@ $close-red: #e48282;
$drop-shadow: "#32323215";
+
//padding
$minimum-padding: 4px;
$medium-padding: 16px;
@@ -24,7 +27,8 @@ $large-padding: 32px;
$icon-size: 28px;
// fonts
-$sans-serif: "Roboto", sans-serif;
+$sans-serif: "Roboto",
+sans-serif;
$large-header: 16px;
$body-text: 12px;
$small-text: 9px;
@@ -43,6 +47,13 @@ $radialMenu-zindex: 100000; // context menu shows up over everything
// borders
$standard-border: solid 1px #9f9f9f;
+// border radius
+$standard-border-radius: 3px;
+
+// shadow
+$standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+
+
$searchpanel-height: 32px;
$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
$docDecorations-zindex: 998; // then doc decorations appear over everything else
@@ -67,4 +78,4 @@ $TREE_BULLET_WIDTH: 20px;
DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
MENU_PANEL_WIDTH: $MENU_PANEL_WIDTH;
TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
-}
+} \ No newline at end of file
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 2aeb8e338..56779c37c 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -5,6 +5,7 @@ export enum Colors {
LIGHT_GRAY = "#DFDFDF",
WHITE = "#FFFFFF",
MEDIUM_BLUE = "#4476F7",
+ MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
LIGHT_BLUE = "#BDDDF5",
PINK = "#E0217D",
YELLOW = "#F5D747",
@@ -35,4 +36,8 @@ export enum IconSizes {
export enum Borders {
STANDARD = "solid 1px #9F9F9F"
+}
+
+export enum Shadows {
+ STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)"
} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 3fcb024df..ac2b19fd6 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,188 +1,204 @@
+@import "../global/globalCssVariables.scss";
+
+
.audiobox-container,
.audiobox-container-interactive {
+ width: 100%;
+ height: 100%;
+ position: inherit;
+ display: flex;
+ position: relative;
+ cursor: default;
+
+ .audiobox-buttons {
+ display: flex;
width: 100%;
+ align-items: center;
height: 100%;
- position: inherit;
- display: flex;
- position: relative;
- cursor: default;
- .audiobox-buttons {
- display: flex;
- width: 100%;
- align-items: center;
- height: 100%;
-
- .audiobox-dictation {
- position: relative;
- width: 30px;
- height: 100%;
- align-items: center;
- display: inherit;
- background: dimgray;
- left: 0px;
- &:hover {
- color: white;
- cursor: pointer;
- }
- }
+ .audiobox-dictation {
+ position: relative;
+ width: 30px;
+ height: 100%;
+ align-items: center;
+ display: inherit;
+ background: $medium-gray;
+ left: 0px;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ cursor: pointer;
+ }
}
+ }
- .audiobox-control,
- .audiobox-control-interactive {
- top: 0;
- max-height: 32px;
- width: 100%;
- display: inline-block;
- pointer-events: none;
- }
+ .audiobox-control,
+ .audiobox-control-interactive {
+ top: 0;
+ max-height: 32px;
+ width: 100%;
+ display: inline-block;
+ pointer-events: none;
+ }
+
+ .audiobox-control-interactive {
+ pointer-events: all;
+ }
- .audiobox-control-interactive {
- pointer-events: all;
+ .audiobox-record-interactive,
+ .audiobox-record {
+ pointer-events: all;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ }
+
+ .audiobox-record {
+ pointer-events: none;
+ }
+
+ .recording {
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ padding-right: 5px;
+ display: flex;
+ background-color: $medium-blue;
+
+ .time {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ font-size: $large-header;
+ text-align: center;
+ top: 5;
}
- .audiobox-record-interactive,
- .audiobox-record {
- pointer-events: all;
- width: 100%;
- height: 100%;
- position: relative;
+ .buttons {
+ position: relative;
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 25px;
+ width: 25px;
+ padding: 5px;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ }
}
+ }
- .audiobox-record {
- pointer-events: none;
+ .audiobox-controls {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ background: $dark-gray;
+
+ .audiobox-dictation {
+ position: absolute;
+ width: 40px;
+ height: 100%;
+ align-items: center;
+ display: inherit;
+ background: $medium-gray;
+ left: 0px;
}
- .recording {
+ .audiobox-player {
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 100%;
+ position: relative;
+ padding-right: 5px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ .audiobox-buttons {
+ position: relative;
margin-top: auto;
margin-bottom: auto;
- width: 100%;
- height: 100%;
- position: relative;
- padding-right: 5px;
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background-color: $dark-gray;
+ color: $white;
display: flex;
- background-color: red;
-
- .time {
- position: relative;
- height: 100%;
- width: 100%;
- font-size: 20;
- text-align: center;
- top: 5;
+ align-items: center;
+ justify-content: center;
+ left: 5px;
+ &:hover {
+ background-color: $black;
}
- .buttons {
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- width: 25px;
- padding: 5px;
- &:hover{
- background-color: crimson;
- }
+ svg {
+ width: 100%;
+ position: absolute;
+ border-width: "thin";
+ border-color: "white";
}
- }
+ }
- .audiobox-controls {
- width: 100%;
- height: 100%;
+ .audiobox-dictation {
position: relative;
- display: flex;
- padding-left: 2px;
- background: black;
-
- .audiobox-dictation {
- position: absolute;
- width: 30px;
- height: 100%;
- align-items: center;
- display: inherit;
- background: dimgray;
- left: 0px;
- }
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 25px;
+ align-items: center;
+ display: inherit;
+ background: $medium-gray;
+ }
- .audiobox-player {
- margin-top: auto;
- margin-bottom: auto;
- width: 100%;
- position: relative;
- padding-right: 5px;
- display: flex;
-
- .audiobox-playhead {
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- margin-right: 2px;
- height: 25px;
- padding: 2px;
- border-radius: 50%;
- background-color: black;
- color: white;
- &:hover {
- background-color: grey;
- color: lightgrey;
- }
- }
-
- .audiobox-dictation {
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- width: 25px;
- padding: 2px;
- align-items: center;
- display: inherit;
- background: dimgray;
- }
-
- .audiobox-timeline {
- position: absolute;
- width: 100%;
- border: gray solid 1px;
- border-radius: 3px;
- z-index: 1000;
- overflow: hidden;
- }
-
- .audioBox-total-time,
- .audioBox-current-time {
- position: absolute;
- font-size: 8;
- top: 100%;
- color: white;
- }
- .audioBox-current-time {
- left: 30px;
- }
-
- .audioBox-total-time {
- right: 2px;
- }
- }
+ .audiobox-timeline {
+ position: absolute;
+ width: 100%;
+ z-index: 1000;
+ overflow: hidden;
+ border-right: 5px solid black;
+ }
+
+ .audioBox-total-time,
+ .audioBox-current-time {
+ position: absolute;
+ font-size: $small-text;
+ top: 100%;
+ color: $white;
+ }
+ .audioBox-current-time {
+ left: 42px;
+ }
+
+ .audioBox-total-time {
+ right: 2px;
+ }
}
+ }
}
-
@media only screen and (max-device-width: 480px) {
- .audiobox-dictation {
- font-size: 5em;
- display: flex;
- width: 100;
- justify-content: center;
- flex-direction: column;
- align-items: center;
- }
-
- .audiobox-container .audiobox-record,
- .audiobox-container-interactive .audiobox-record {
- font-size: 3em;
- }
-
- .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead,
- .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
- .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead {
- width: 70px;
- }
-} \ No newline at end of file
+ .audiobox-dictation {
+ font-size: 5em;
+ display: flex;
+ width: 100;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .audiobox-container .audiobox-record,
+ .audiobox-container-interactive .audiobox-record {
+ font-size: 3em;
+ }
+
+ .audiobox-container .audiobox-controls .audiobox-player .audiobox-buttons,
+ .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
+ .audiobox-container-interactive
+ .audiobox-controls
+ .audiobox-player
+ .audiobox-buttons {
+ width: 70px;
+ }
+}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index faaa887c4..8962d29f0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,6 +1,13 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import {
+ action,
+ computed,
+ IReactionDisposer,
+ observable,
+ reaction,
+ runInAction,
+} from "mobx";
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
@@ -9,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";
@@ -17,23 +24,34 @@ import { SnappingManager } from "../../util/SnappingManager";
import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
+import {
+ ViewBoxAnnotatableComponent,
+ ViewBoxAnnotatableProps,
+} from "../DocComponent";
import "./AudioBox.scss";
-import { FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps } from "./FieldView";
import { LinkDocPreview } from "./LinkDocPreview";
+import { faLessThan } from "@fortawesome/free-solid-svg-icons";
+import { Colors } from "../global/globalEnums";
+
declare class MediaRecorder {
- constructor(e: any); // whatever MediaRecorder has
+ constructor(e: any); // whatever MediaRecorder has
}
type AudioDocument = makeInterface<[typeof documentSchema]>;
const AudioDocument = makeInterface(documentSchema);
@observer
-export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, AudioDocument>(AudioDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
+export class AudioBox extends ViewBoxAnnotatableComponent<
+ ViewBoxAnnotatableProps & FieldViewProps,
+ AudioDocument
+>(AudioDocument) {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(AudioBox, fieldKey);
+ }
public static Enabled = false;
- static playheadWidth = 30; // width of playhead
- static heightPercent = 80; // height of timeline in percent of height of audioBox.
+ static playheadWidth = 40; // width of playhead
+ static heightPercent = 75; // height of timeline in percent of height of audioBox.
static Instance: AudioBox;
_disposers: { [name: string]: IReactionDisposer } = {};
@@ -47,35 +65,82 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
_stream: MediaStream | undefined;
_start: number = 0;
_play: any = null;
+ _ended: boolean = false;
@observable static _scrubTime = 0;
@observable _markerEnd: number = 0;
@observable _position: number = 0;
@observable _waveHeight: Opt<number> = this.layoutDoc._height;
@observable _paused: boolean = false;
- @computed get mediaState(): undefined | "pendingRecording" | "recording" | "paused" | "playing" { return this.dataDoc.mediaState as (undefined | "pendingRecording" | "recording" | "paused" | "playing"); }
- set mediaState(value) { this.dataDoc.mediaState = value; }
- public static SetScrubTime = action((timeInMillisFrom1970: number) => { AudioBox._scrubTime = 0; AudioBox._scrubTime = timeInMillisFrom1970; });
- @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
- @computed get duration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); }
- @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey]); }
- @computed get links() { return DocListCast(this.dataDoc.links); }
- @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct time
- @computed get heightPercent() { return AudioBox.heightPercent; }
+ @observable _trimming: boolean = false;
+ @observable _trimStart: number = NumCast(this.layoutDoc.clipStart) ? NumCast(this.layoutDoc.clipStart) : 0;
+ @observable _trimEnd: number = NumCast(this.layoutDoc.clipEnd) ? NumCast(this.layoutDoc.clipEnd)
+ : this.duration;
+
+ @computed get mediaState():
+ | undefined
+ | "pendingRecording"
+ | "recording"
+ | "paused"
+ | "playing" {
+ return this.dataDoc.mediaState as
+ | undefined
+ | "pendingRecording"
+ | "recording"
+ | "paused"
+ | "playing";
+ }
+ set mediaState(value) {
+ this.dataDoc.mediaState = value;
+ }
+ public static SetScrubTime = action((timeInMillisFrom1970: number) => {
+ AudioBox._scrubTime = 0;
+ AudioBox._scrubTime = timeInMillisFrom1970;
+ });
+ @computed get recordingStart() {
+ return Cast(
+ this.dataDoc[this.props.fieldKey + "-recordingStart"],
+ DateField
+ )?.date.getTime();
+ }
+ @computed get duration() {
+ return NumCast(this.dataDoc[`${this.fieldKey}-duration`]);
+ }
+ @computed get trimDuration() {
+ return this._trimming && this._trimEnd ? this.duration : this._trimEnd - this._trimStart;
+ }
+ @computed get anchorDocs() {
+ return DocListCast(this.dataDoc[this.annotationKey]);
+ }
+ @computed get links() {
+ return DocListCast(this.dataDoc.links);
+ }
+ @computed get pauseTime() {
+ return this._pauseEnd - this._pauseStart;
+ } // total time paused to update the correct time
+ @computed get heightPercent() {
+ return AudioBox.heightPercent;
+ }
constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) {
super(props);
AudioBox.Instance = this;
if (this.duration === undefined) {
- runInAction(() => this.Document[this.fieldKey + "-duration"] = this.Document.duration);
+ runInAction(
+ () =>
+ (this.Document[this.fieldKey + "-duration"] = this.Document.duration)
+ );
}
}
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1) || 0;
+ const linkTime =
+ this._stackedTimeline.current?.anchorStart(la2) ||
+ this._stackedTimeline.current?.anchorStart(la1) ||
+ 0;
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
@@ -84,16 +149,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
getAnchor = () => {
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey,
- "_timecodeToShow" /* audioStart */, "_timecodeToHide" /* audioEnd */, this._ele?.currentTime ||
- Cast(this.props.Document._currentTimecode, "number", null) || (this.mediaState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined))
- || this.rootDoc;
+ return (
+ CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.annotationKey,
+ "_timecodeToShow" /* audioStart */,
+ "_timecodeToHide" /* audioEnd */,
+ this._ele?.currentTime ||
+ Cast(this.props.Document._currentTimecode, "number", null) ||
+ (this.mediaState === "recording"
+ ? (Date.now() - (this.recordingStart || 0)) / 1000
+ : undefined)
+ ) || this.rootDoc
+ );
}
componentWillUnmount() {
- Object.values(this._disposers).forEach(disposer => disposer?.());
+ Object.values(this._disposers).forEach((disposer) => disposer?.());
const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
+ ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
}
@action
@@ -102,39 +177,68 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.mediaState = this.path ? "paused" : undefined;
+ this.layoutDoc.clipStart = this.layoutDoc.clipStart ? this.layoutDoc.clipStart : 0;
+ this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? this.layoutDoc.clipEnd : this.duration ? this.duration : undefined;
+
+ this.path && this.setAnchorTime(NumCast(this.layoutDoc.clipStart));
+ this.path && this.timecodeChanged();
+
this._disposers.triggerAudio = reaction(
- () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
- start => start !== undefined && setTimeout(() => {
- this.playFrom(start);
+ () =>
+ !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1
+ ? NumCast(this.Document._triggerAudio, null)
+ : undefined,
+ (start) =>
+ start !== undefined &&
setTimeout(() => {
- this.Document._currentTimecode = start;
- this.Document._triggerAudio = undefined;
- }, 10);
- }), // wait for mainCont and try again to play
+ this.playFrom(start);
+ setTimeout(() => {
+ this.Document._currentTimecode = start;
+ this.Document._triggerAudio = undefined;
+ }, 10);
+ }), // wait for mainCont and try again to play
{ fireImmediately: true }
);
this._disposers.audioStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? Cast(this.Document._audioStop, "number", null) : undefined,
- audioStop => audioStop !== undefined && setTimeout(() => {
- this.Pause();
- setTimeout(() => this.Document._audioStop = undefined, 10);
- }), // wait for mainCont and try again to play
+ () =>
+ this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo
+ ? Cast(this.Document._audioStop, "number", null)
+ : undefined,
+ (audioStop) =>
+ audioStop !== undefined &&
+ setTimeout(() => {
+ this.Pause();
+ setTimeout(() => (this.Document._audioStop = undefined), 10);
+ }), // wait for mainCont and try again to play
{ fireImmediately: true }
);
}
// for updating the timecode
+ @action
timecodeChanged = () => {
const htmlEle = this._ele;
if (this.mediaState !== "recording" && htmlEle) {
- htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration);
- this.links.map(l => this.getLinkData(l)).forEach(({ la1, la2, linkTime }) => {
- if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) {
- Doc.linkFollowHighlight(la1);
- }
- });
+ htmlEle.duration &&
+ htmlEle.duration !== Infinity &&
+ runInAction(
+ () => (this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration)
+ );
+ this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? Math.min(this.duration, NumCast(this.layoutDoc.clipEnd)) : this.duration;
+ this._trimEnd = this._trimEnd ? Math.min(this.duration, this._trimEnd) : this.duration;
+ this.links
+ .map((l) => this.getLinkData(l))
+ .forEach(({ la1, la2, linkTime }) => {
+ if (
+ linkTime > NumCast(this.layoutDoc._currentTimecode) &&
+ linkTime < htmlEle.currentTime
+ ) {
+ Doc.linkFollowHighlight(la1);
+ }
+ });
this.layoutDoc._currentTimecode = htmlEle.currentTime;
+
}
}
@@ -146,12 +250,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// play audio for documents created during recording
playFromTime = (absoluteTime: number) => {
- this.recordingStart && this.playFrom((absoluteTime - this.recordingStart) / 1000);
+ this.recordingStart &&
+ this.playFrom((absoluteTime - this.recordingStart) / 1000);
}
// play back the audio from time
@action
- playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => {
+ playFrom = (seekTimeInSeconds: number, endTime: number = this._trimEnd, fullPlay: boolean = false) => {
clearTimeout(this._play);
if (Number.isNaN(this._ele?.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
@@ -162,12 +267,20 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
} else {
this.Pause();
}
- } else if (seekTimeInSeconds <= this._ele.duration) {
- this._ele.currentTime = seekTimeInSeconds;
+ } else if (this._trimStart <= endTime && seekTimeInSeconds <= this._trimEnd) {
+ const start = Math.max(this._trimStart, seekTimeInSeconds);
+ const end = Math.min(this._trimEnd, endTime);
+ this._ele.currentTime = start;
this._ele.play();
- runInAction(() => this.mediaState = "playing");
+ runInAction(() => (this.mediaState = "playing"));
if (endTime !== this.duration) {
- this._play = setTimeout(() => this.Pause(), (endTime - seekTimeInSeconds) * 1000); // use setTimeout to play a specific duration
+ this._play = setTimeout(
+ () => {
+ this._ended = fullPlay ? true : this._ended;
+ this.Pause();
+ },
+ (end - start) * 1000
+ ); // use setTimeout to play a specific duration
}
} else {
this.Pause();
@@ -182,7 +295,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (this._paused) {
this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
} else {
- this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+ this.layoutDoc._currentTimecode =
+ (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
}
}
}
@@ -191,7 +305,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
recordAudioAnnotation = async () => {
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
- this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date());
+ this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(
+ new Date()
+ );
DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
@@ -200,7 +316,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
};
this._recordStart = new Date().getTime();
- runInAction(() => this.mediaState = "recording");
+ runInAction(() => (this.mediaState = "recording"));
setTimeout(this.updateRecordTime, 0);
this._recorder.start();
setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour
@@ -209,21 +325,49 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors", event: () => this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors, icon: "expand-arrows-alt" });
- funcs.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + " play when link is selected", event: () => this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks, icon: "expand-arrows-alt" });
- funcs.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors, icon: "expand-arrows-alt" });
- ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ funcs.push({
+ description:
+ (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors",
+ event: () => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
+ icon: "expand-arrows-alt",
+ });
+ funcs.push({
+ description:
+ (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") +
+ " play when link is selected",
+ event: () =>
+ (this.layoutDoc.dontAutoPlayFollowedLinks =
+ !this.layoutDoc.dontAutoPlayFollowedLinks),
+ icon: "expand-arrows-alt",
+ });
+ funcs.push({
+ description:
+ (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") +
+ " anchors onClick",
+ event: () =>
+ (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
+ icon: "expand-arrows-alt",
+ });
+ ContextMenu.Instance?.addItem({
+ description: "Options...",
+ subitems: funcs,
+ icon: "asterisk",
+ });
}
// stops the recording
stopRecording = action(() => {
this._recorder.stop();
this._recorder = undefined;
- this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+ this.dataDoc[this.fieldKey + "-duration"] =
+ (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
this.mediaState = "paused";
+ this._trimEnd = this.duration;
+ this.layoutDoc.clipStart = 0;
+ this.layoutDoc.clipEnd = this.duration;
this._stream?.getAudioTracks()[0].stop();
const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
+ ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
});
// button for starting and stopping the recording
@@ -236,17 +380,37 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// for play button
Play = (e?: any) => {
- this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1);
+ let start;
+ if (this._ended || this._ele!.currentTime === this.duration) {
+ start = this._trimStart;
+ this._ended = false;
+ }
+ else {
+ start = this._ele!.currentTime;
+ }
+
+ this.playFrom(start, this._trimEnd, true);
e?.stopPropagation?.();
}
// creates a text document for dictation
onFile = (e: any) => {
- const newDoc = CurrentUserUtils.GetNewTextDoc("", NumCast(this.props.Document.x), NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
- NumCast(this.props.Document._width), 2 * NumCast(this.props.Document._height));
+ const newDoc = CurrentUserUtils.GetNewTextDoc(
+ "",
+ NumCast(this.props.Document.x),
+ NumCast(this.props.Document.y) +
+ NumCast(this.props.Document._height) +
+ 10,
+ NumCast(this.props.Document._width),
+ 2 * NumCast(this.props.Document._height)
+ );
Doc.GetProto(newDoc).recordingSource = this.dataDoc;
- Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
- Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
+ Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(
+ `self.recordingSource["${this.props.fieldKey}-recordingStart"]`
+ );
+ Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(
+ "self.recordingSource.mediaState"
+ );
this.props.addDocument?.(newDoc);
e.stopPropagation();
}
@@ -261,7 +425,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// returns the path of the audio file
@computed get path() {
const field = Cast(this.props.Document[this.props.fieldKey], AudioField);
- const path = (field instanceof AudioField) ? field.url.href : "";
+ const path = field instanceof AudioField ? field.url.href : "";
return path === nullAudio ? "" : path;
}
@@ -295,98 +459,256 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
playLink = (link: Doc) => {
const stack = this._stackedTimeline.current;
if (link.annotationOn === this.rootDoc) {
- if (!this.layoutDoc.dontAutoPlayFollowedLinks) this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
- else this._ele!.currentTime = this.layoutDoc._currentTimecode = (stack?.anchorStart(link) || 0);
+ if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
+ this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
+ } else {
+ this._ele!.currentTime = this.layoutDoc._currentTimecode =
+ stack?.anchorStart(link) || 0;
+ }
+ } else {
+ this.links
+ .filter((l) => l.anchor1 === link || l.anchor2 === link)
+ .forEach((l) => {
+ const { la1, la2 } = this.getLinkData(l);
+ const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
+ const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
+ if (startTime !== undefined) {
+ if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
+ endTime
+ ? this.playFrom(startTime, endTime)
+ : this.playFrom(startTime);
+ } else {
+ this._ele!.currentTime = this.layoutDoc._currentTimecode =
+ startTime;
+ }
+ }
+ });
}
- else {
- this.links.filter(l => l.anchor1 === link || l.anchor2 === link).forEach(l => {
- const { la1, la2 } = this.getLinkData(l);
- const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
- const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
- if (startTime !== undefined) {
- if (!this.layoutDoc.dontAutoPlayFollowedLinks) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
- else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime;
- }
- });
+ }
+
+ // shows trim controls
+ @action
+ startTrim = () => {
+ if (!this.duration) {
+ this.timecodeChanged();
}
+ if (this.mediaState === "playing") {
+ this.Pause();
+ }
+ this._trimming = true;
+ }
+
+ // hides trim controls and displays new clip
+ @action
+ finishTrim = () => {
+ if (this.mediaState === "playing") {
+ this.Pause();
+ }
+ this.layoutDoc.clipStart = this._trimStart;
+ 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) => {
+ this._trimStart = newStart;
+ }
+
+ @action
+ setEndTrim = (newEnd: number) => {
+ this._trimEnd = newEnd;
}
isActiveChild = () => this._isAnyChildContentActive;
- 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;
- timelineHeight = () => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline)
+ 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);
+ }
+
+ timelineHeight = () =>
+ (((this.props.PanelHeight() * this.heightPercent) / 100) *
+ this.heightPercent) /
+ 100 // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline)
timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth;
@computed get renderTimeline() {
- return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
- fieldKey={this.annotationKey}
- dictationKey={this.fieldKey + "-dictation"}
- mediaPath={this.path}
- renderDepth={this.props.renderDepth + 1}
- startTag={"_timecodeToShow" /* audioStart */}
- endTag={"_timecodeToHide" /* audioEnd */}
- focus={DocUtils.DefaultFocus}
- bringToFront={emptyFunction}
- CollectionView={undefined}
- isAnyChildContentActive={this.isAnyChildContentActive}
- duration={this.duration}
- playFrom={this.playFrom}
- setTime={this.setAnchorTime}
- playing={this.playing}
- whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- ScreenToLocalTransform={this.timelineScreenToLocal}
- Play={this.Play}
- Pause={this.Pause}
- playLink={this.playLink}
- PanelWidth={this.timelineWidth}
- PanelHeight={this.timelineHeight}
- />;
+ return (
+ <CollectionStackedTimeline
+ ref={this._stackedTimeline}
+ {...this.props}
+ fieldKey={this.annotationKey}
+ dictationKey={this.fieldKey + "-dictation"}
+ mediaPath={this.path}
+ renderDepth={this.props.renderDepth + 1}
+ startTag={"_timecodeToShow" /* audioStart */}
+ endTag={"_timecodeToHide" /* audioEnd */}
+ focus={DocUtils.DefaultFocus}
+ bringToFront={emptyFunction}
+ CollectionView={undefined}
+ duration={this.duration}
+ playFrom={this.playFrom}
+ setTime={this.setAnchorTime}
+ playing={this.playing}
+ whenChildContentsActiveChanged={
+ this.timelineWhenChildContentsActiveChanged
+ }
+ removeDocument={this.removeDocument}
+ ScreenToLocalTransform={this.timelineScreenToLocal}
+ Play={this.Play}
+ Pause={this.Pause}
+ isContentActive={this.props.isContentActive}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ playLink={this.playLink}
+ PanelWidth={this.timelineWidth}
+ PanelHeight={this.timelineHeight}
+ trimming={this._trimming}
+ trimStart={this._trimStart}
+ trimEnd={this._trimEnd}
+ trimDuration={this.trimDuration}
+ setStartTrim={this.setStartTrim}
+ setEndTrim={this.setEndTrim}
+ />
+ );
}
render() {
- const interactive = SnappingManager.GetIsDragging() || this.props.isContentActive() ? "-interactive" : "";
- return <div className="audiobox-container"
- onContextMenu={this.specificContextMenu}
- onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined}
- style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined }}>
- {!this.path ?
- <div className="audiobox-buttons">
- <div className="audiobox-dictation" onClick={this.onFile}>
- <FontAwesomeIcon style={{ width: "30px", background: !this.layoutDoc.dontAutoPlayFollowedLinks ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
- </div>
- {this.mediaState === "recording" || this.mediaState === "paused" ?
- <div className="recording" onClick={e => e.stopPropagation()}>
- <div className="buttons" onClick={this.recordClick}>
- <FontAwesomeIcon icon={"stop"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ const interactive =
+ SnappingManager.GetIsDragging() || this.props.isContentActive()
+ ? "-interactive"
+ : "";
+ return (
+ <div
+ className="audiobox-container"
+ onContextMenu={this.specificContextMenu}
+ onClick={
+ !this.path && !this._recorder ? this.recordAudioAnnotation : undefined
+ }
+ style={{
+ pointerEvents:
+ this.props.layerProvider?.(this.layoutDoc) === false
+ ? "none"
+ : undefined,
+ }}
+ >
+ {!this.path ? (
+ <div className="audiobox-buttons">
+ <div className="audiobox-dictation" onClick={this.onFile}>
+ <FontAwesomeIcon
+ style={{
+ width: "30px",
+ background: !this.layoutDoc.dontAutoPlayFollowedLinks
+ ? Colors.LIGHT_BLUE
+ : "rgba(0,0,0,0)",
+ }}
+ icon="file-alt"
+ size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
+ />
+ </div>
+ {this.mediaState === "recording" || this.mediaState === "paused" ? (
+ <div className="recording" onClick={(e) => e.stopPropagation()}>
+ <div className="recording-buttons" onClick={this.recordClick}>
+ <FontAwesomeIcon
+ icon={"stop"}
+ size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
+ />
+ </div>
+ <div
+ className="recording-buttons"
+ onClick={this._paused ? this.recordPlay : this.recordPause}
+ >
+ <FontAwesomeIcon
+ icon={this._paused ? "play" : "pause"}
+ size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
+ />
+ </div>
+ <div className="time">
+ {formatTime(
+ Math.round(NumCast(this.layoutDoc._currentTimecode))
+ )}
+ </div>
</div>
- <div className="buttons" onClick={this._paused ? this.recordPlay : this.recordPause}>
- <FontAwesomeIcon icon={this._paused ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ ) : (
+ <div
+ className={`audiobox-record${interactive}`}
+ style={{ backgroundColor: Colors.DARK_GRAY }}
+ >
+ RECORD
+ </div>
+ )}
+ </div>
+ ) : (
+ <div
+ className="audiobox-controls"
+ style={{
+ pointerEvents:
+ this._isAnyChildContentActive || this.props.isContentActive()
+ ? "all"
+ : "none",
+ }}
+ >
+ <div className="audiobox-dictation" />
+ <div
+ className="audiobox-player"
+ style={{ height: `${AudioBox.heightPercent}%` }}
+ >
+ <div
+ className="audiobox-buttons"
+ title={this.mediaState === "paused" ? "play" : "pause"}
+ onClick={this.mediaState === "paused" ? this.Play : this.Pause}
+ >
+ {" "}
+ <FontAwesomeIcon
+ icon={this.mediaState === "paused" ? "play" : "pause"}
+ size={"1x"}
+ />
+ </div>
+ <div
+ className="audiobox-buttons"
+ title={this._trimming ? "finish" : "trim"}
+ onClick={this._trimming ? this.finishTrim : this.startTrim}
+ >
+ <FontAwesomeIcon
+ icon={this._trimming ? "check" : "cut"}
+ size={"1x"}
+ />
+ </div>
+ <div
+ className="audiobox-timeline"
+ style={{
+ top: 0,
+ height: `100%`,
+ left: AudioBox.playheadWidth,
+ width: `calc(100% - ${AudioBox.playheadWidth}px)`,
+ background: "white",
+ }}
+ >
+ {this.renderTimeline}
+ </div>
+ {this.audio}
+ <div className="audioBox-current-time">
+ {this._trimming ?
+ formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))
+ : formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this._trimStart)))}
+ </div>
+ <div className="audioBox-total-time">
+ {this._trimming || !this._trimEnd ?
+ formatTime(Math.round(NumCast(this.duration)))
+ : formatTime(Math.round(NumCast(this.trimDuration)))}
+ </div>
</div>
- <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}</div>
</div>
- :
- <button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}>
- RECORD
- </button>}
- </div> :
- <div className="audiobox-controls" style={{ pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? "all" : "none" }} >
- <div className="audiobox-dictation" />
- <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
- <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div>
- <div className="audiobox-timeline" style={{ top: 0, height: `100%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}>
- {this.renderTimeline}
- </div>
- {this.audio}
- <div className="audioBox-current-time">
- {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
- </div>
- <div className="audioBox-total-time">
- {formatTime(Math.round(this.duration))}
- </div>
- </div>
- </div>
- }
- </div>;
+ )}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 3d2cdf5a4..fad905d6d 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";
@@ -201,7 +201,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 7648e866e..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;
@@ -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,
}}>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5bd6049d6..8b19fb204 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 {
@@ -91,6 +92,8 @@ export interface DocComponentView {
setFocus?: () => void;
fieldKey?: string;
annotationKey?: string;
+ getTitle?: () => string;
+ getScrollHeight?: () => number;
}
export interface DocumentViewSharedProps {
renderDepth: number;
@@ -111,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;
@@ -135,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;
@@ -205,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); }
@@ -429,7 +433,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
});
// after a timeout, the right _componentView should have been created, so call it to update its view spec values
setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, !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) =>
@@ -462,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);
@@ -603,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) => {
@@ -633,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";
@@ -646,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) {
@@ -677,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");
@@ -701,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) {
@@ -782,6 +787,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
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?.() ||
@@ -843,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}
@@ -933,22 +952,41 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
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)"),
+ color: lightOrDark(background),
+ background,
pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : 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) ?
@@ -960,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"),
@@ -980,8 +1019,9 @@ 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", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
@@ -993,6 +1033,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
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}
@@ -1070,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();
}
@@ -1146,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}
@@ -1176,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/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 7ad03e055..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,7 +79,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
* @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
*/
@computed static get targetDoc() {
- return SelectionManager.Views().length ? SelectionManager.Views()[0].Document : CurrentUserUtils.ActiveDashboard;
+ return SelectionManager.Docs().length ? SelectionManager.Docs()[0] : CurrentUserUtils.ActiveDashboard;
}
@computed static get targetDocChildKey() {
if (SelectionManager.Views().length) {
@@ -88,10 +88,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
return "data";
}
@computed static get targetDocChildren() {
- if (SelectionManager.Views().length) {
- return DocListCast(FilterBox.targetDoc[FilterBox.targetDocChildKey]);
- }
- return DocListCast(CurrentUserUtils.ActiveDashboard.data);
+ return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data);
}
@observable _loaded = false;
@@ -347,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.
}
/**
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/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 6a7793ff0..db1ae0537 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -20,11 +20,26 @@ const LabelSchema = createSchema({});
type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
const LabelDocument = makeInterface(LabelSchema, documentSchema);
+export interface LabelBoxProps {
+ label?: string;
+}
+
@observer
-export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument>(LabelDocument) {
+export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps), LabelDocument>(LabelDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
+ public static LayoutStringWithTitle(fieldType: { name: string }, fieldStr: string, label: string) {
+ return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ }
private dropDisposer?: DragManager.DragDropDisposer;
+ componentDidMount() {
+ this.props.setContentView?.(this);
+ }
+
+ getTitle() {
+ return this.props.label || "";
+ }
+
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
if (ele) {
@@ -65,8 +80,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
render() {
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
- params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
- const label = typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
+ params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
+ const label = this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
return (
<div className="labelBox-outerDiv"
onMouseLeave={action(() => this._mouseOver = false)}
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/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 5e07229c1..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, DocListCast } 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, BoolCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { PdfField } from "../../../fields/URLField";
import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction, returnOne } 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);
@@ -78,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;
@@ -208,11 +209,15 @@ 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.props.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.SidebarShown ? 0 :
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 484dec7e2..90de3227f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -323,8 +323,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{!this.audiopath || this.audiopath === field.url.href ? (null) :
<audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
<source src={this.audiopath} type="audio/mpeg" />
- Not supported.
- </audio>}
+ Not supported.
+ </audio>}
</div>
</div>;
}
@@ -537,6 +537,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
Pause={this.Pause}
playLink={this.playLink}
PanelHeight={this.timelineHeight}
+ trimming={false}
+ trimStart={0}
+ trimEnd={this.duration}
+ trimDuration={this.duration}
+ setStartTrim={() => { }}
+ setEndTrim={() => { }}
/>
</div>;
}
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 e4d4557af..7e46d8433 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -13,9 +13,8 @@ import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils, returnEmptyString, returnEmptyFilter } from "../../../Utils";
+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";
@@ -59,7 +59,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@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; }
@@ -187,8 +187,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
Docs.Create.WebanchorDocument(this._url, {
title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
- y: NumCast(this.layoutDoc._scrollTop)
+ y: NumCast(this.layoutDoc._scrollTop),
+ unrendered: true
});
this.addDocumentWrapper(anchor);
return anchor;
@@ -229,6 +229,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
+ getScrollHeight = () => this._scrollHeight;
+
isFirefox = () => {
return "InstallTrigger" in window; // navigator.userAgent.indexOf("Chrome") !== -1;
}
@@ -417,8 +419,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
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) {
@@ -590,11 +591,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
/>
- <button className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
- style={{ display: !this.props.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..df7c54f67
--- /dev/null
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -0,0 +1,901 @@
+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 } 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';
+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"} />
+ <EditableView GetValue={() => checkResult} SetValue={setValue} contents="...">
+ </EditableView>
+ </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;
+ }
+});
+
+Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean) {
+ if (checkResult) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected && selected.type === DocumentType.INK) {
+ return Number(selected.strokeWidth);
+ } else {
+ const width: number = NumCast(Doc.UserDoc().activeInkWidth);
+ return width;
+ }
+ }
+ Doc.UserDoc().activeInkWidth = width;
+ SelectionManager.Docs().filter(doc => StrCast(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 && selected.type === DocumentType.INK) {
+ return selected.color;
+ } else {
+ const color: string = StrCast(Doc.UserDoc().activeInkColor);
+ return color;
+ }
+ }
+ SetActiveInkColor(StrCast(color));
+ SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.INK).map(doc => doc.color = String(color));
+});
+
+
+/** WEB
+ * webSetURL
+ **/
+Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ console.log("URL: ", url);
+ if (checkResult && selected && selected.type === DocumentType.WEB) {
+ return Cast(selected.data, WebField, null).url;
+ }
+ else if (selected && selected.type === DocumentType.WEB) {
+ selected.data = url;
+ }
+});
+
+
+/** 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 4b1d76d00..78de1fd89 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";
@@ -63,6 +63,8 @@ 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 {
@@ -214,7 +216,7 @@ 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);
+ this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch);
return undefined;
});
AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
@@ -430,6 +432,7 @@ 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();
@@ -571,14 +574,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" });
@@ -710,7 +727,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
- const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) });
+ 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) => {
@@ -861,8 +878,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 });
@@ -902,7 +917,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}, { fireImmediately: true }
);
quickScroll = undefined;
- this.tryUpdateScrollHeight();
+ setTimeout(this.tryUpdateScrollHeight, 10);
}
pushToGoogleDoc = async () => {
@@ -1141,8 +1156,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) })];
}
}
@@ -1357,6 +1372,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._undoTyping = undefined;
return wasUndoing;
}
+
@action
onBlur = (e: any) => {
FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
@@ -1445,18 +1461,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children) {
- var proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
- var scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+ 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();
- setTimeout(() => {
- proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
- scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- scrollHeight && setScrollHeight();
- }, 10);
- } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
+ } 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...
+ }
}
}
}
@@ -1481,11 +1494,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.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
- style={{
- left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
- background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
- }} />;
+ 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) => {
@@ -1547,7 +1567,8 @@ 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.props.isContentActive() && e.stopPropagation()}
style={{
@@ -1556,7 +1577,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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={{
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 82ad2b7db..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() {
@@ -828,7 +566,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
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);
@@ -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 cfc3e75cc..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());
}
@@ -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}
diff --git a/src/client/views/pdf/AnchorMenu.scss b/src/client/views/pdf/AnchorMenu.scss
index b7afb26a5..6990bdcf1 100644
--- a/src/client/views/pdf/AnchorMenu.scss
+++ b/src/client/views/pdf/AnchorMenu.scss
@@ -4,6 +4,35 @@
padding: 5px;
grid-template-columns: 90px 20px 90px;
}
+.anchorMenu-highlighter {
+ padding-right: 5px;
+ .antimodeMenu-button {
+ padding: 0;
+ padding: 0;
+ padding-right: 0px;
+ padding-left: 0px;
+ width: 5px;
+ }
+}
+.anchor-color-preview-button {
+ width: 25px !important;
+ .anchor-color-preview {
+ display: flex;
+ flex-direction: column;
+ padding-right: 3px;
+ width: unset !important;
+ .color-preview {
+ width: 60%;
+ top: 80%;
+ height: 4px;
+ position: relative;
+ top: unset;
+ width: 15px;
+ margin-top: 5px;
+ display: block;
+ }
+ }
+}
.color-wrapper {
display: flex;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 3bf1f0828..3ba427c29 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -41,7 +41,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
@observable private _showLinkPopup: boolean = false;
- @observable public _colorBtn = false;
@observable public Highlighting: boolean = false;
@observable public Status: "marquee" | "annotation" | "" = "";
@@ -97,9 +96,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@computed get highlighter() {
const button =
- <button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
- <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+ <button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
+ <div className="anchor-color-preview" >
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+ <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+ </div>
</button>;
const dropdownContent =
@@ -117,7 +118,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
</div>;
return (
<Tooltip key="highlighter" title={<div className="dash-tooltip">{"Click to Highlight"}</div>}>
- <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
+ <div className="anchorMenu-highlighter">
+ <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
+ </div>
</Tooltip>
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 02010e123..d953c6b6c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,14 +2,13 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
-import { createSchema } from "../../../fields/Schema";
import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
import { PdfField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils, returnFalse, returnEmptyString, returnEmptyFilter } from "../../../Utils";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 260ddfc90..e70b2bc19 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -114,7 +114,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
const linkFrom = this.props.linkFrom();
if (linkFrom) {
console.log(linkFrom.title);
- DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
+ DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link");
}
}
});
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index d04ae8a80..923f1892e 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -22,7 +22,7 @@
.topBar-icon {
cursor: pointer;
- font-size: 12px;
+ font-size: 12.5px;
font-family: 'Roboto';
width: fit-content;
display: flex;
@@ -31,18 +31,20 @@
align-items: center;
justify-self: center;
align-self: center;
- border-radius: 5px;
+ border-radius: $standard-border-radius;
padding: 5px;
- transition: linear 0.1s;
+ transition: linear 0.2s;
color: $black;
background-color: $light-gray;
- }
- .topBar-icon:hover {
- background-color: $light-blue;
+ &:hover {
+ background-color: darken($color: $light-gray, $amount: 20);
+ }
}
-
+
+
+
.topbar-center {
grid-column: 2;
display: inline-flex;
@@ -59,7 +61,7 @@
.topbar-lozenge-dashboard {
display: flex;
-
+
.topbar-dashSelect {
border: none;
@@ -156,9 +158,9 @@
}
&.topbar-input {
- margin:5px;
- border-radius:20px;
- border:$dark-gray;
+ margin: 5px;
+ border-radius: 20px;
+ border: $dark-gray;
display: block;
width: 130px;
-webkit-transition: width 0.4s;
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 05edb975c..d5254e315 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -28,7 +28,7 @@ export class TopBar extends React.Component {
{`${Doc.CurrentUserEmail}`}
</div>
<div className="topbar-icon" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
- {"Sign out"}
+ {"Log out"}
</div>
</div>
<div className="topbar-center" >
@@ -51,7 +51,8 @@ export class TopBar extends React.Component {
</div>
</div>
<div className="topbar-right" >
- <div className="topbar-icon">
+ <div className="topbar-icon" onClick={() => window.open(
+ "https://brown-dash.github.io/Dash-Documentation/", "_blank")}>
{"Help"}<FontAwesomeIcon icon="question-circle"></FontAwesomeIcon>
</div>
<div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}>