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 { observer } from "mobx-react"; import { ColorState } from "react-color"; import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; import { Document } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; 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, AntimodeMenuProps } from "../AntimodeMenu"; import { EditableView } from "../EditableView"; import { GestureOverlay } from "../GestureOverlay"; import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentView } from "../nodes/DocumentView"; import { RichTextMenu } from "../nodes/formattedText/RichTextMenu"; import { PresBox } from "../nodes/PresBox"; import "./CollectionMenu.scss"; import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { TabDocView } from "./TabDocView"; import { RichTextField } from "../../../fields/RichTextField"; @observer export class CollectionMenu extends AntimodeMenu { @observable static Instance: CollectionMenu; @observable SelectedCollection: DocumentView | undefined; @observable FieldKey: string; constructor(props: any) { super(props); this.FieldKey = ""; runInAction(() => CollectionMenu.Instance = this); this._canFade = false; // don't let the inking menu fade away runInAction(() => this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true)); this.jumpTo(300, 300); } componentDidMount() { reaction(() => SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0], (doc) => doc && this.SetSelection(doc)); } @action SetSelection(view: DocumentView) { this.SelectedCollection = view; } @action toggleMenuPin = (e: React.MouseEvent) => { Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned; if (!this.Pinned && this._left < 0) { this.jumpTo(300, 300); } } @action toggleProperties = () => { if (CurrentUserUtils.propertiesWidth > 0) { CurrentUserUtils.propertiesWidth = 0; } else { CurrentUserUtils.propertiesWidth = 250; } } render() { const button = Pin Menu} key="pin menu" placement="bottom"> ; const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; const prop = {propTitle}} key="properties" placement="bottom"> ; return this.getElement(!this.SelectedCollection ? [/*button*/] : [, prop, /*button*/]); } } interface CollectionMenuProps { type: CollectionViewType; fieldKey: string; docView: DocumentView; } const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); @observer export class CollectionViewBaseChrome extends React.Component { //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) get document() { return this.props.docView?.props.Document; } get target() { return this.document; } _templateCommand = { params: ["target", "source"], title: "item view", script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])", immediate: undoBatch((source: Doc[]) => { let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text; try { formatStr && JSON.parse(formatStr); } catch (e) { formatStr = ""; } if (source.length === 1 && formatStr) { Doc.SetInPlace(this.target, "childLayoutString", formatStr, false); } else if (source.length) { this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]); } else { Doc.SetInPlace(this.target, "childLayoutString", undefined, true); Doc.SetInPlace(this.target, "childLayoutTemplate", undefined, true); } }), initialize: emptyFunction, }; _narrativeCommand = { params: ["target", "source"], title: "child click view", script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])", immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))), initialize: emptyFunction, }; _contentCommand = { params: ["target", "source"], title: "set content", script: "getProto(self.target).data = copyField(self.source);", immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List(source)), initialize: emptyFunction, }; _onClickCommand = { params: ["target", "proxy"], title: "copy onClick", script: `{ if (self.proxy?.[0]) { getProto(self.proxy[0]).onClick = copyField(self.target.onClick); getProto(self.proxy[0]).target = self.target.target; getProto(self.proxy[0]).source = copyField(self.target.source); }}`, immediate: undoBatch((source: Doc[]) => { }), initialize: emptyFunction, }; _openLinkInCommand = { params: ["target", "container"], title: "link follow target", script: `{ if (self.container?.length) { getProto(self.target).linkContainer = self.container[0]; getProto(self.target).isLinkButton = true; getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])"); }}`, immediate: undoBatch((container: Doc[]) => { if (container.length) { Doc.GetProto(this.target).linkContainer = container[0]; Doc.GetProto(this.target).isLinkButton = true; Doc.GetProto(this.target).onClick = ScriptField.MakeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])"); } }), initialize: emptyFunction, }; _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']; 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", script: "self.target._fitToBox = !self.target._fitToBox;", immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox), initialize: emptyFunction }; _fitContentCommand = { params: ["target"], title: "toggle clusters", script: "self.target._useClusters = !self.target._useClusters;", immediate: undoBatch((source: Doc[]) => this.target._useClusters = !this.target._useClusters), initialize: emptyFunction }; _saveFilterCommand = { params: ["target"], title: "save filter", script: `self.target._docFilters = compareLists(self['target-docFilters'],self.target._docFilters) ? undefined : copyField(self['target-docFilters']); self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`, immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), initialize: (button: Doc) => { button['target-docFilters'] = (Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) instanceof ObjectField ? ObjectField.MakeCopy((Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) as any as ObjectField) : undefined; button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined; }, }; @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } @computed get _tree_commands() { return undefined; } private get _buttonizableCommands() { switch (this.props.type) { default: return this._doc_commands; case CollectionViewType.Freeform: return this._freeform_commands; case CollectionViewType.Tree: return this._tree_commands; case CollectionViewType.Schema: return this._schema_commands; case CollectionViewType.Stacking: return this._stacking_commands; case CollectionViewType.Masonry: return this._stacking_commands; case CollectionViewType.Time: return this._freeform_commands; case CollectionViewType.Carousel: return this._freeform_commands; case CollectionViewType.Carousel3D: return this._freeform_commands; } } private _picker: any; private _commandRef = React.createRef(); private _viewRef = React.createRef(); @observable private _currentKey: string = ""; componentDidMount = action(() => { this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : ""); }); @undoBatch viewChanged = (e: React.ChangeEvent) => { const target = this.document !== Doc.UserDoc().sidebar ? this.document : this.document.proto as Doc; //@ts-ignore target._viewType = e.target.selectedOptions[0].value; } commandChanged = (e: React.ChangeEvent) => { //@ts-ignore runInAction(() => this._currentKey = e.target.selectedOptions[0].value); } @action closeViewSpecs = () => { this.document._facetWidth = 0; } @computed get subChrome() { switch (this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type) { // bcz: ARgh! hack to get menu for tree view outline items default: return this.otherSubChrome; case CollectionViewType.Invalid: case CollectionViewType.Freeform: return (); case CollectionViewType.Stacking: return (); case CollectionViewType.Schema: return (); case CollectionViewType.Tree: return (); case CollectionViewType.Masonry: return (); case CollectionViewType.Carousel3D: return (); case CollectionViewType.Grid: return (); case CollectionViewType.Docking: return (); } } @computed get otherSubChrome() { const docType = this.props.docView.Document.type; switch (docType) { default: return (null); case DocumentType.IMG: return (); case DocumentType.PDF: return (); case DocumentType.INK: return (); case DocumentType.WEB: return (); case DocumentType.VID: return (); case DocumentType.RTF: return (); } } private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer?.(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document); } } @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; if (docDragData?.draggedDocuments.length) { this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || [])); e.stopPropagation(); } return true; } dragViewDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, (e, down, delta) => { const vtype = this.props.type; const c = { params: ["target"], title: vtype, script: `this.target._viewType = '${StrCast(this.props.type)}'`, immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, }; DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY); return true; }, emptyFunction, emptyFunction); } dragCommandDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, (e, down, delta) => { this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY)); return true; }, emptyFunction, () => { this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([])); }); } @computed get templateChrome() { return
drop document to apply or drag to create button
} placement="bottom">
; } @computed get viewModes() { const excludedViewTypes = Doc.UserDoc().noviceMode ? [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.Map, CollectionViewType.Linear, CollectionViewType.Time] : [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.Linear]; return
drop document to apply or drag to create button
} placement="bottom">
; } @computed get selectedDocumentView() { return SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get notACollection() { if (this.selectedDoc) { const layoutField = Doc.LayoutField(this.selectedDoc); return this.props.type === CollectionViewType.Docking || typeof (layoutField) === "string" && !layoutField?.includes("CollectionView"); } else return false; } @computed get pinButton() { const targetDoc = this.selectedDoc; const isPinned = targetDoc && Doc.isDocPinned(targetDoc); return !targetDoc ? (null) : {Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}} placement="top"> ; } @undoBatch @action pinWithView = (targetDoc: Opt) => { if (targetDoc) { TabDocView.PinDoc(targetDoc, false); const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { const scroll = targetDoc._scrollTop; activeDoc.presPinView = true; activeDoc.presPinViewScroll = scroll; } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { const x = targetDoc._panX; const y = targetDoc._panY; const scale = targetDoc._viewScale; activeDoc.presPinView = true; activeDoc.presPinViewX = x; activeDoc.presPinViewY = y; activeDoc.presPinViewScale = scale; } else if (targetDoc.type === DocumentType.COMPARISON) { const width = targetDoc._clipWidth; activeDoc.presPinClipWidth = width; activeDoc.presPinView = true; } } } @computed get pinWithViewButton() { const presPinWithViewIcon = ; const targetDoc = this.selectedDoc; {/* return (!targetDoc || (targetDoc._viewType !== CollectionViewType.Freeform && targetDoc.type !== DocumentType.IMG)) ? (null) :
{"Pin to presentation trail with current view"}
} placement="top"> */ } return (targetDoc && (targetDoc._viewType === CollectionViewType.Freeform || targetDoc._viewType === CollectionViewType.Stacking || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.COMPARISON)) ?
{"Pin to presentation trail with current view"}
} placement="top">
: (null); } @undoBatch onAlias = () => { if (this.selectedDoc && this.selectedDocumentView) { // const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true); // copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width); // copy.y = NumCast(this.selectedDoc.y) + 30; // this.selectedDocumentView.props.addDocument?.(copy); const alias = Doc.MakeAlias(this.selectedDoc); alias.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width); alias.y = NumCast(this.selectedDoc.y) + 30; this.selectedDocumentView.props.addDocument?.(alias); } } onAliasButtonDown = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction); } @undoBatch onAliasButtonMoved = (e: PointerEvent) => { const contentDiv = this.selectedDocumentView?.ContentDiv; if (contentDiv) { const dragData = new DragManager.DocumentDragData([this.selectedDocumentView!.props.Document]); const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y]; dragData.defaultDropAction = "alias"; dragData.canEmbed = true; DragManager.StartDocumentDrag([contentDiv], dragData, e.clientX, e.clientY, { offsetX: offset[0], offsetY: offset[1], hideSource: false }); return true; } return false; } @computed get aliasButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : {"Tap or Drag to create an alias"}} placement="top"> ; } @computed get lightboxButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : {"Show Lightbox of Images"}} placement="top"> ; } render() { return (
{this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} {!this._buttonizableCommands ? (null) : this.templateChrome} {this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) : Toggle Overlay Layer
} placement="bottom"> } {this.notACollection ? (null) : this.lightboxButton} {this.aliasButton} {/* {this.pinButton} */} {this.pinWithViewButton}
{this.subChrome}
); } } @observer export class CollectionDockingChrome extends React.Component { render() { return (null); } } @observer export class CollectionFreeFormViewChrome extends React.Component { public static Instance: CollectionFreeFormViewChrome; constructor(props: any) { super(props); CollectionFreeFormViewChrome.Instance = this; } get document() { return this.props.docView.props.Document; } @computed get dataField() { return this.document[this.props.docView.LayoutFieldKey]; } @computed get childDocs() { return DocListCast(this.dataField); } @computed get selectedDocumentView() { return SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any)?.focused ? true : false; } @undoBatch @action nextKeyframe = (): void => { const currentFrame = Cast(this.document._currentFrame, "number", null); if (currentFrame === undefined) { this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame)); } @undoBatch @action prevKeyframe = (): void => { const currentFrame = Cast(this.document._currentFrame, "number", null); if (currentFrame === undefined) { this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1); } private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""]; private _width = ["1", "5", "10", "100"]; private _dotsize = [10, 20, 30, 40]; private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"]; private _head = ["", "", "", "arrow", "", ""]; private _end = ["", "", "arrow", "arrow", "", ""]; private _shape = ["", "line", "line", "line", "rectangle", "circle"]; private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",]; private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"]; @observable _shapesNum = this._shape.length; @observable _selected = this._shapesNum; @observable _keepMode = false; @observable _colorBtn = false; @observable _widthBtn = false; @observable _fillBtn = false; @action clearKeep() { this._selected = this._shapesNum; } @action changeColor = (color: string, type: string) => { const col: ColorState = { hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" }, rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "", }; if (type === "color") { SetActiveInkColor(Utils.colorString(col)); } else if (type === "fill") { SetActiveFillColor(Utils.colorString(col)); } } @action editProperties = (value: any, field: string) => { SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { const doc = Document(element.rootDoc); if (doc.type === DocumentType.INK) { switch (field) { case "width": doc.strokeWidth = Number(value); break; case "color": doc.color = String(value); break; case "fill": doc.fillColor = String(value); break; case "dash": doc.strokeDash = value; } } })); } @computed get drawButtons() { const func = action((i: number, keep: boolean) => { this._keepMode = keep; if (this._selected !== i) { this._selected = i; Doc.SetSelectedTool(InkTool.Pen); SetActiveArrowStart(this._head[i]); SetActiveArrowEnd(this._end[i]); SetActiveBezierApprox("300"); GestureOverlay.Instance.InkShape = this._shape[i]; } else { this._selected = this._shapesNum; Doc.SetSelectedTool(InkTool.None); SetActiveArrowStart(""); SetActiveArrowEnd(""); GestureOverlay.Instance.InkShape = ""; SetActiveBezierApprox("0"); } }); return
{this._draw.map((icon, i) => {this._title[i]}
} placement="bottom">
)} ; } toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => { return {key}} placement="bottom"> ; } @computed get widthPicker() { const widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null); return !this._widthBtn ? widthPicker :
{widthPicker} {this._width.map((wid, i) => change width
} placement="bottom"> )} ; } @computed get colorPicker() { const colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
); return !this._colorBtn ? colorPicker :
{colorPicker} {this._palette.map(color => )}
; } @computed get fillPicker() { const fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
); return !this._fillBtn ? fillPicker :
{fillPicker} {this._palette.map(color => )}
; } 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; const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url; this.submitURL(newurl); e.stopPropagation(); } onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); } @computed get _url() { return this.selectedDoc?.data instanceof WebField ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : Field.toString(this.selectedDoc?.data as Field); } set _url(value) { if (this.selectedDoc) { Doc.GetProto(this.selectedDoc).data = new WebField(value); Doc.SetInPlace(this.selectedDoc, "title", value, true); const annots = Doc.GetProto(this.selectedDoc)["data-annotations-" + this.urlHash(value)]; Doc.GetProto(this.selectedDoc)["data-annotations"] = annots instanceof ObjectField ? ObjectField.MakeCopy(annots) : new List([]); } } @action submitURL = (url: string) => { if (!url.startsWith("http")) url = "http://" + url; try { const selectedDoc = this.selectedDoc; if (selectedDoc) { const URLy = new URL(url); const future = Cast(selectedDoc["data-future"], listSpec("string"), null); const history = Cast(selectedDoc["data-history"], listSpec("string"), null); const annos = DocListCast(selectedDoc["data-annotations"]); if (Field.toString(selectedDoc.data as Field) === Field.toString(new WebField(URLy))) { Doc.GetProto(selectedDoc).data = new WebField(new URL("http://cs.brown.edu/~avd")); setTimeout(action(() => Doc.GetProto(selectedDoc).data = new WebField(URLy)), 100); } else { if (url) { Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List(annos); if (history === undefined) { selectedDoc["data-history"] = new List([this._url]); } else { history.push(this._url); } future && (future.length = 0); } this._url = url; } } } catch (e) { console.log("WebBox URL error:" + url); } } urlHash(s: string) { return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); } onValueKeyDown = async (e: React.KeyboardEvent) => { e.key === "Enter" && this.submitURL(this._keyInput.current!.value); e.stopPropagation(); } @action forward = () => { const selectedDoc = this.selectedDoc; if (selectedDoc) { const future = Cast(selectedDoc["data-future"], listSpec("string"), null); const history = Cast(selectedDoc["data-history"], listSpec("string"), null); if (future?.length) { history?.push(this._url); Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List(DocListCast(selectedDoc["data-annotations"])); const newurl = future.pop()!; Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl)); Doc.GetProto(selectedDoc)["data-annotations"] = new List(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)])); } } } @action back = () => { const selectedDoc = this.selectedDoc; if (selectedDoc) { const future = Cast(selectedDoc["data-future"], listSpec("string"), null); const history = Cast(selectedDoc["data-history"], listSpec("string"), null); if (history?.length) { if (future === undefined) selectedDoc["data-future"] = new List([this._url]); else future.push(this._url); Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List(DocListCast(selectedDoc["data-annotations"])); const newurl = history.pop()!; Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl)); Doc.GetProto(selectedDoc)["data-annotations"] = new List(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)])); } } } private _keyInput = React.createRef(); @computed get urlEditor() { return (
{ this._keyInput.current!.select(); e.stopPropagation(); }} ref={this._keyInput} />
); } @observable viewType = this.selectedDoc?._viewType; render() { return !this.props.docView.layoutDoc ? (null) :
{!Doc.UserDoc().noviceMode && !this.isText && !this.props.isDoc ? <> Back Frame
} placement="bottom">
Toggle View All
} placement="bottom">
this.document.editing = !this.document.editing)} > {NumCast(this.document._currentFrame)}
Forward Frame
} placement="bottom">
: null} {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) : this.urlEditor } {!this.isText ? <> {this.drawButtons} {this.widthPicker} {this.colorPicker} {this.fillPicker} : (null) } {
} ; } } @observer export class CollectionStackingViewChrome extends React.Component { @observable private _currentKey: string = ""; @observable private suggestions: string[] = []; get document() { return this.props.docView.props.Document; } @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; } @computed get pivotField() { return StrCast(this.document._pivotField); } getKeySuggestions = async (value: string): Promise => { const val = value.toLowerCase(); const docs = DocListCast(this.document[this.props.fieldKey]); if (Doc.UserDoc().noviceMode) { if (docs instanceof Doc) { 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[0] !== "_")); return keys.filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set(); 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[0] !== "_")); return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } } if (docs instanceof Doc) { return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); } } @action onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { this._currentKey = newValue; } getSuggestionValue = (suggestion: string) => suggestion; renderSuggestion = (suggestion: string) => { return

{suggestion}

; } onSuggestionFetch = async ({ value }: { value: string }) => { const sugg = await this.getKeySuggestions(value); runInAction(() => { this.suggestions = sugg; }); } @action onSuggestionClear = () => { this.suggestions = []; } @action setValue = (value: string) => { this.document._pivotField = value; return true; } @action toggleSort = () => { this.document._columnsSort = this.document._columnsSort === "descending" ? "ascending" : this.document._columnsSort === "ascending" ? undefined : "descending"; } @action resetValue = () => { this._currentKey = this.pivotField; }; render() { const doctype = this.props.docView.Document.type; const isPres: boolean = (doctype === DocumentType.PRES); return ( isPres ? (null) :
GROUP BY:
this.pivotField} autosuggestProps={ { resetValue: this.resetValue, value: this._currentKey, onChange: this.onKeyChange, autosuggestProps: { inputProps: { value: this._currentKey, onChange: this.onKeyChange }, getSuggestionValue: this.getSuggestionValue, suggestions: this.suggestions, alwaysRenderSuggestions: true, renderSuggestion: this.renderSuggestion, onSuggestionsFetchRequested: this.onSuggestionFetch, onSuggestionsClearRequested: this.onSuggestionClear } }} oneLine SetValue={this.setValue} contents={this.pivotField ? this.pivotField : "N/A"} />
); } } @observer export class CollectionSchemaViewChrome extends React.Component { // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0; get document() { return this.props.docView.props.Document; } @undoBatch togglePreview = () => { const dividerWidth = 4; const borderWidth = Number(COLLECTION_BORDER_WIDTH); const panelWidth = this.props.docView.props.PanelWidth(); const previewWidth = NumCast(this.document.schemaPreviewWidth); const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth; this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0; } @undoBatch @action toggleTextwrap = async () => { const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []); if (textwrappedRows.length) { this.document.textwrappedSchemaRows = new List([]); } else { const docs = DocListCast(this.document[this.props.fieldKey]); const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); this.document.textwrappedSchemaRows = new List(allRows); } } render() { const previewWidth = NumCast(this.document.schemaPreviewWidth); const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0; return (
Show Preview:
{previewWidth !== 0 ? "on" : "off"}
); } } @observer export class CollectionTreeViewChrome extends React.Component { get document() { return this.props.docView.props.Document; } get sortAscending() { return this.document[this.props.fieldKey + "-sortAscending"]; } set sortAscending(value) { this.document[this.props.fieldKey + "-sortAscending"] = value; } @computed private get ascending() { return Cast(this.sortAscending, "boolean", null); } @action toggleSort = () => { if (this.sortAscending) this.sortAscending = undefined; else if (this.sortAscending === undefined) this.sortAscending = false; else this.sortAscending = true; } render() { return (
); } } // Enter scroll speed for 3D Carousel @observer export class Collection3DCarouselViewChrome extends React.Component { get document() { return this.props.docView.props.Document; } @computed get scrollSpeed() { return this.document._autoScrollSpeed; } @action setValue = (value: string) => { const numValue = Number(StrCast(value)); if (numValue > 0) { this.document._autoScrollSpeed = numValue; return true; } return false; } render() { return (
AUTOSCROLL SPEED:
StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
); } } /** * Chrome for grid view. */ @observer export class CollectionGridViewChrome extends React.Component { private clicked: boolean = false; private entered: boolean = false; private decrementLimitReached: boolean = false; @observable private resize = false; private resizeListenerDisposer: Opt; get document() { return this.props.docView.props.Document; } componentDidMount() { runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700); // listener to reduce text on chrome resize (panel resize) this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => { runInAction(() => this.resize = newValue < 700); }); } componentWillUnmount() { this.resizeListenerDisposer?.(); } get numCols() { return NumCast(this.document.gridNumCols, 10); } /** * Sets the value of `numCols` on the grid's Document to the value entered. */ onNumColsChange = (e: React.ChangeEvent) => { if (e.currentTarget.valueAsNumber > 0) undoBatch(() => this.document.gridNumCols = e.currentTarget.valueAsNumber)(); } /** * Sets the value of `rowHeight` on the grid's Document to the value entered. */ // @undoBatch // onRowHeightEnter = (e: React.KeyboardEvent) => { // if (e.key === "Enter" || e.key === "Tab") { // if (e.currentTarget.valueAsNumber > 0 && this.document.rowHeight as number !== e.currentTarget.valueAsNumber) { // this.document.rowHeight = e.currentTarget.valueAsNumber; // } // } // } /** * Sets whether the grid is flexible or not on the grid's Document. */ @undoBatch toggleFlex = () => { this.document.gridFlex = !BoolCast(this.document.gridFlex, true); } /** * Increments the value of numCols on button click */ onIncrementButtonClick = () => { this.clicked = true; this.entered && (this.document.gridNumCols as number)--; undoBatch(() => this.document.gridNumCols = this.numCols + 1)(); this.entered = false; } /** * Decrements the value of numCols on button click */ onDecrementButtonClick = () => { this.clicked = true; 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; } /** * Increments the value of numCols on button hover */ incrementValue = () => { this.entered = true; if (!this.clicked && !this.decrementLimitReached) { this.document.gridNumCols = this.numCols + 1; } this.decrementLimitReached = false; this.clicked = false; } /** * Decrements the value of numCols on button hover */ decrementValue = () => { this.entered = true; if (!this.clicked) { if (this.numCols > 1) { this.document.gridNumCols = this.numCols - 1; } else { this.decrementLimitReached = true; } } this.clicked = false; } /** * Toggles the value of preventCollision */ toggleCollisions = () => { this.document.gridPreventCollision = !this.document.gridPreventCollision; } /** * Changes the value of the compactType */ changeCompactType = (e: React.ChangeEvent) => { // need to change startCompaction so that this operation will be undoable. this.document.gridStartCompaction = e.target.selectedOptions[0].value; } render() { return (
) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> {/* ) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> */}
); } } 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); });