aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionMenu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/CollectionMenu.tsx')
-rw-r--r--src/client/views/collections/CollectionMenu.tsx241
1 files changed, 204 insertions, 37 deletions
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 53d2a136e..388eda2b3 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -11,17 +11,18 @@ import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
import { List } from "../../../fields/List";
import { ObjectField } from "../../../fields/ObjectField";
-import { RichTextField } from "../../../fields/RichTextField";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
+import { WebField } from "../../../fields/URLField";
import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
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 { undoBatch } from "../../util/UndoManager";
-import AntimodeMenu from "../AntimodeMenu";
+import AntimodeMenu, { AntimodeMenuProps } from "../AntimodeMenu";
import { EditableView } from "../EditableView";
import GestureOverlay from "../GestureOverlay";
import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
@@ -32,13 +33,13 @@ import "./CollectionMenu.scss";
import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
@observer
-export default class CollectionMenu extends AntimodeMenu {
+export default class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: CollectionMenu;
@observable SelectedCollection: DocumentView | undefined;
@observable FieldKey: string;
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
this.FieldKey = "";
CollectionMenu.Instance = this;
@@ -85,7 +86,8 @@ export default class CollectionMenu extends AntimodeMenu {
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" onPointerDown={this.toggleProperties}>
+ <button className="antimodeMenu-button" key="properties" style={{ backgroundColor: "#424242" }}
+ onPointerDown={this.toggleProperties}>
<FontAwesomeIcon icon={propIcon} size="lg" />
</button>
</Tooltip>;
@@ -160,9 +162,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
};
_viewCommand = {
params: ["target"], title: "bookmark view",
- script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];",
- immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
- initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
+ script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale']; gotoFrame(self.target, self['target-currentFrame']);",
+ immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; this.target.currentFrame = 0; }),
+ initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; button['target-currentFrame'] = this.target.currentFrame; },
};
_clusterCommand = {
params: ["target"], title: "fit content",
@@ -428,7 +430,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
@computed get isText() {
if (this.selectedDoc) {
- return this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField;
+ const layoutField = Doc.LayoutField(this.selectedDoc);
+ return StrCast(layoutField).includes("FormattedText") ||
+ (layoutField instanceof Doc && StrCast(layoutField.layout).includes("FormattedText"));
}
else return false;
}
@@ -580,9 +584,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
{/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
- <div className="color-previewII" style={{ backgroundColor: color }} />
- {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""}
-
+ <div className="color-previewII" style={{ backgroundColor: color }}>
+ {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""}
+ </div>
</button>)}
</div>;
}
@@ -604,6 +608,158 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>;
}
+ @action
+ onUrlDrop = (e: React.DragEvent) => {
+ const { dataTransfer } = e;
+ const html = dataTransfer.getData("text/html");
+ const uri = dataTransfer.getData("text/uri-list");
+ const url = uri || html || this._url;
+ this._url = url.startsWith(window.location.origin) ?
+ url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+ this.submitURL();
+ e.stopPropagation();
+ }
+ onUrlDragover = (e: React.DragEvent) => {
+ e.preventDefault();
+ }
+
+ @computed get _url() {
+ return this.selectedDoc ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : "hello";
+ }
+
+ set _url(value) { this.selectedDoc && (this.selectedDoc.data = value); }
+
+ @action
+ submitURL = () => {
+ if (!this._url.startsWith("http")) this._url = "http://" + this._url;
+ try {
+ const URLy = new URL(this._url);
+ const future = this.selectedDoc ? Cast(this.selectedDoc["data-future"], listSpec("string"), null) : null;
+ const history = this.selectedDoc ? Cast(this.selectedDoc["data-history"], listSpec("string"), null) : [];
+ const annos = this.selectedDoc ? DocListCast(this.selectedDoc["data-annotations"]) : undefined;
+ const url = this.selectedDoc ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : null;
+ if (url) {
+ if (history === undefined) {
+ this.selectedDoc && (this.selectedDoc["data-history"] = new List<string>([url]));
+
+ } else {
+ history.push(url);
+ }
+ future && (future.length = 0);
+ this.selectedDoc && (this.selectedDoc["data-" + this.urlHash(url)] = new List<Doc>(annos));
+ }
+ this.selectedDoc && (this.selectedDoc.data = new WebField(URLy));
+ this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>([]));
+ } catch (e) {
+ console.log("WebBox URL error:" + this._url);
+ }
+ }
+
+ urlHash(s: string) {
+ return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
+ }
+
+ toggleAnnotationMode = () => {
+ this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating;
+ }
+
+ @action
+ onURLChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._url = e.target.value;
+ }
+
+ onValueKeyDown = async (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ this.submitURL();
+ }
+ e.stopPropagation();
+ }
+
+ @action
+ forward = () => {
+ const future = this.selectedDoc && (Cast(this.selectedDoc["data-future"], listSpec("string"), null));
+ const history = this.selectedDoc && Cast(this.selectedDoc["data-history"], listSpec("string"), null);
+ if (future?.length) {
+ history?.push(this._url);
+ this.selectedDoc && (this.selectedDoc["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations"])));
+ this.selectedDoc && (this.selectedDoc.data = new WebField(new URL(this._url = future.pop()!)));
+ this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)])));
+ }
+ }
+
+ @action
+ back = () => {
+ const future = this.selectedDoc && (Cast(this.selectedDoc["data-future"], listSpec("string"), null));
+ const history = this.selectedDoc && Cast(this.selectedDoc["data-history"], listSpec("string"), null);
+ if (history?.length) {
+ if (future === undefined) this.selectedDoc && (this.selectedDoc["data-future"] = new List<string>([this._url]));
+ else future.push(this._url);
+ this.selectedDoc && (this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations"])));
+ this.selectedDoc && (this.selectedDoc.data = new WebField(new URL(this._url = history.pop()!)));
+ this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)])));
+ }
+ }
+
+ private _keyInput = React.createRef<HTMLInputElement>();
+
+ @computed get urlEditor() {
+ return (
+ <div className="webBox-urlEditor"
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover} style={{ top: 0 }}>
+ <div className="urlEditor">
+ <div className="editorBase">
+ <div className="webBox-buttons"
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover} style={{ display: "flex" }}>
+ <div className="webBox-freeze" title={"Annotate"}
+ style={{ background: this.props.docView.layoutDoc.isAnnotating ? "lightBlue" : "gray" }}
+ onClick={this.toggleAnnotationMode} >
+ <FontAwesomeIcon icon="pen" size={"2x"} />
+ </div>
+ <div className="webBox-freeze" title={"Select"}
+ style={{ background: !this.props.docView.layoutDoc.isAnnotating ? "lightBlue" : "gray" }}
+ onClick={this.toggleAnnotationMode} >
+ <FontAwesomeIcon icon={"mouse-pointer"} size={"2x"} />
+ </div>
+ <input className="webpage-urlInput"
+ placeholder="ENTER URL"
+ value={this._url}
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover}
+ onChange={this.onURLChange}
+ onKeyDown={this.onValueKeyDown}
+ onClick={(e) => {
+ this._keyInput.current!.select();
+ e.stopPropagation();
+ }}
+ ref={this._keyInput}
+ />
+ <div style={{
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "space-between",
+ maxWidth: "120px",
+ }}>
+ <button className="submitUrl" onClick={this.submitURL}
+ onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
+ GO
+ </button>
+ <button className="submitUrl" onClick={this.back}>
+ <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
+ </button>
+ <button className="submitUrl" onClick={this.forward}>
+ <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+
@observable viewType = this.selectedDoc?._viewType;
render() {
@@ -622,7 +778,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>
</Tooltip> : null}
{!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
- <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
+ <div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }}
onClick={action(() => this.document.editing = !this.document.editing)} >
{NumCast(this.document.currentFrame)}
</div>
@@ -645,6 +801,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</button>
</Tooltip>
}
+ {/* {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) :
+ this.urlEditor
+ } */}
{!this.isText ?
<>
{this.drawButtons}
@@ -654,7 +813,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</> :
(null)
}
- {this.isText ? <RichTextMenu key="rich" /> : null}
+ {this.isText ? <RichTextMenu /> : null}
</div>;
}
}
@@ -669,7 +828,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@computed get pivotField() { return StrCast(this.document._pivotField); }
getKeySuggestions = async (value: string): Promise<string[]> => {
- value = value.toLowerCase();
+ const val = value.toLowerCase();
const docs = DocListCast(this.document[this.props.fieldKey]);
if (Doc.UserDoc().noviceMode) {
@@ -677,24 +836,24 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
(key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_"));
- return keys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1);
+ return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
} else {
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 ||
key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 ||
- key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] &&
+ key.indexOf("lastModified") >= 0 || (key[0]?.toUpperCase() === key[0] &&
key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_"));
- return noviceKeys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1);
+ return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
}
if (docs instanceof Doc) {
- return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
+ return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
} else {
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
+ return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
}
}
@@ -735,8 +894,10 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@action resetValue = () => { this._currentKey = this.pivotField; };
render() {
+ const doctype = this.props.docView.Document.type;
+ const isPres: boolean = (doctype === DocumentType.PRES);
return (
- <div className="collectionStackingViewChrome-cont">
+ isPres ? (null) : <div className="collectionStackingViewChrome-cont">
<div className="collectionStackingViewChrome-pivotField-cont">
<div className="collectionStackingViewChrome-pivotField-label">
GROUP BY:
@@ -930,16 +1091,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
get numCols() { return NumCast(this.document.gridNumCols, 10); }
/**
- * Sets the value of `numCols` on the grid's Document to the value entered.
- */
- @undoBatch
- onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter" || e.key === "Tab") {
- if (e.currentTarget.valueAsNumber > 0) {
- this.document.gridNumCols = e.currentTarget.valueAsNumber;
- }
-
- }
+ * Sets the value of `numCols` on the grid's Document to the value entered.
+ */
+ onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ if (e.currentTarget.valueAsNumber > 0) undoBatch(() => this.document.gridNumCols = e.currentTarget.valueAsNumber)();
}
/**
@@ -977,9 +1132,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
onDecrementButtonClick = () => {
this.clicked = true;
- if (!this.decrementLimitReached) {
+ if (this.numCols > 1 && !this.decrementLimitReached) {
this.entered && (this.document.gridNumCols as number)++;
undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
+ if (this.numCols === 1) this.decrementLimitReached = true;
}
this.entered = false;
}
@@ -1002,7 +1158,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
decrementValue = () => {
this.entered = true;
if (!this.clicked) {
- if (this.numCols !== 1) {
+ if (this.numCols > 1) {
this.document.gridNumCols = this.numCols - 1;
}
else {
@@ -1035,9 +1191,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
<span className="grid-icon">
<FontAwesomeIcon icon="columns" size="1x" />
</span>
- <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
- <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
- <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
+ <input className="collectionGridViewChrome-entryBox" type="number" value={this.numCols} onChange={this.onNumColsChange} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
+ <input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
</span>
{/* <span className="grid-control">
<span className="grid-icon">
@@ -1078,4 +1234,15 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
</div>
);
}
-} \ No newline at end of file
+}
+Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) {
+ const dataField = doc[Doc.LayoutFieldKey(doc)];
+ const childDocs = DocListCast(dataField);
+ const currentFrame = Cast(doc.currentFrame, "number", null);
+ if (currentFrame === undefined) {
+ doc.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+ doc.currentFrame = Math.max(0, newFrame);
+}); \ No newline at end of file