diff options
Diffstat (limited to 'src/client/views')
51 files changed, 1083 insertions, 975 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index e2c0de8af..8f112de0c 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -124,7 +124,9 @@ } .icon-background { - pointer-events: none; + pointer-events: all; + height:100%; + margin-top: 15px; background-color: transparent; width: 35px; text-align: center; diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 68b97f2b6..5d452e72e 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -248,7 +248,7 @@ export class ContextMenu extends React.Component { e.preventDefault(); } else if (e.key === "Enter" || e.key === "Tab") { const item = this.flatItems[this.selectedIndex]; - item && item.event(); + item && item.event({ x: this.pageX, y: this.pageY }); this.closeMenu(); e.preventDefault(); } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 0366a6a30..330b94afa 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -10,7 +10,7 @@ library.add(faAngleRight); export interface OriginalMenuProps { description: string; - event: () => void; + event: (stuff?: any) => void; undoable?: boolean; icon: IconProp; //maybe should be optional (icon?) closeMenu?: () => void; @@ -44,7 +44,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select if (this.props.undoable !== false) { batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); } - await this.props.event(); + await this.props.event({ x: e.clientX, y: e.clientY }); batch && batch.end(); } } @@ -93,13 +93,13 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)} </div>; return ( - <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onMouseEnter={this.onPointerEnter} onMouseLeave={this.onPointerLeave}> + <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}> {this.props.icon ? ( - <span className="icon-background"> + <span className="icon-background" onMouseEnter={this.onPointerLeave}> <FontAwesomeIcon icon={this.props.icon} size="sm" /> </span> ) : null} - <div className="contextMenu-description"> + <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} > {this.props.description} <FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px" }} /> </div> diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index ac8497bd0..4ab5d733f 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -257,7 +257,7 @@ $linkGap : 3px; padding: 2px 12px; list-style: none; - .templateToggle { + .templateToggle, .chromeToggle { text-align: left; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 94aab8b2f..7829bd7f1 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -3,12 +3,12 @@ import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faChe import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; import { URLField } from '../../new_fields/URLField'; import { emptyFunction, Utils } from "../../Utils"; -import { Docs } from "../documents/Documents"; +import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; @@ -16,7 +16,7 @@ import { undoBatch, UndoManager } from "../util/UndoManager"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { CollectionView } from "./collections/CollectionView"; import './DocumentDecorations.scss'; -import { DocumentView, PositionDocument } from "./nodes/DocumentView"; +import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; @@ -30,6 +30,11 @@ import { MetadataEntryMenu } from './MetadataEntryMenu'; import { ImageBox } from './nodes/ImageBox'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; +import { ObjectField } from '../../new_fields/ObjectField'; +import { DocServer } from '../DocServer'; +import { CompileScript } from '../util/Scripting'; +import { ComputedField } from '../../new_fields/ScriptField'; +import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -141,17 +146,29 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (text[0] === '#') { this._fieldKey = text.slice(1, text.length); this._title = this.selectionTitle; + } else if (text.startsWith("::")) { + let targetID = text.slice(2, text.length); + let promoteDoc = SelectionManager.SelectedDocuments()[0]; + DocUtils.Publish(promoteDoc.props.Document, targetID, promoteDoc.props.addDocument, promoteDoc.props.removeDocument); } else if (text.startsWith(">")) { let fieldTemplateView = SelectionManager.SelectedDocuments()[0]; SelectionManager.DeselectAll(); let fieldTemplate = fieldTemplateView.props.Document; - let docTemplate = fieldTemplateView.props.ContainingCollectionView!.props.Document; - let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); - let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto); - if (text.startsWith(">>")) { - proto.detailedLayout = proto.layout; - proto.miniLayout = ImageBox.LayoutString(metaKey); + let containerView = fieldTemplateView.props.ContainingCollectionView; + if (containerView) { + let docTemplate = containerView.props.Document; + let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); + let proto = Doc.GetProto(docTemplate); + if (metaKey !== containerView.props.fieldKey && containerView.props.DataDoc) { + const fd = fieldTemplate.data; + fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd)); + } + fieldTemplate.title = metaKey; + Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); + if (text.startsWith(">>")) { + proto.detailedLayout = proto.layout; + proto.miniLayout = ImageBox.LayoutString(metaKey); + } } } else { @@ -239,7 +256,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let dragDocView = SelectionManager.SelectedDocuments()[0]; const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top); - let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document), SelectionManager.SelectedDocuments().map(dv => dv.props.DataDoc ? dv.props.DataDoc : dv.props.Document)); + let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); dragData.xOffset = xoff; dragData.yOffset = yoff; dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument; @@ -283,7 +300,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onCloseUp = async (e: PointerEvent) => { e.stopPropagation(); if (e.button === 0) { - const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc); + const recent = Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc; SelectionManager.SelectedDocuments().map(dv => { recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); @@ -344,22 +361,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) { selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc); } - if (!this._removeIcon) { - if (selectedDocs.length === 1) { - this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].toggleMinimized()); - } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD && - Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) { - let docViews = SelectionManager.ViewsSortedVertically(); - let topDocView = docViews[0]; - let ind = topDocView.templates.indexOf(Templates.Bullet.Layout); - if (ind !== -1) { - topDocView.templates.splice(ind, 1); - topDocView.props.Document.subBulletDocs = undefined; - } else { - topDocView.addTemplate(Templates.Bullet); - topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!)); + if (!this._removeIcon && selectedDocs.length === 1) { // if you click on the top-left button when just 1 doc is selected, then collapse it. not sure why we don't do it for multiple selections + this.getIconDoc(selectedDocs[0]).then(async icon => { + let minimizedDoc = await Cast(selectedDocs[0].props.Document.minimizedDoc, Doc); + if (minimizedDoc) { + let scrpt = selectedDocs[0].props.ScreenToLocalTransform().scale(selectedDocs[0].props.ContentScaling()).inverse().transformPoint( + NumCast(minimizedDoc.x) - NumCast(selectedDocs[0].Document.x), NumCast(minimizedDoc.y) - NumCast(selectedDocs[0].Document.y)); + selectedDocs[0].collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); } - } + }); } this._removeIcon = false; } @@ -371,8 +381,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let doc = selected[0].props.Document; let iconDoc = Docs.Create.IconDocument(layoutString); iconDoc.isButton = true; - iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon"; - iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey; + + IconBox.AutomaticTitle(iconDoc); //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined; iconDoc.proto!.isMinimized = false; iconDoc.width = Number(MINIMIZED_ICON_SIZE); @@ -397,8 +407,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } moveIconDoc(iconDoc: Doc) { let selView = SelectionManager.SelectedDocuments()[0]; - let zoom = NumCast(selView.props.Document.zoomBasis, 1); - let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).scale(1 / zoom). + let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()). transformPoint(this._minimizedX - 12, this._minimizedY - 12); iconDoc.x = where[0] + NumCast(selView.props.Document.x); iconDoc.y = where[1] + NumCast(selView.props.Document.y); @@ -417,23 +426,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.addEventListener("pointermove", this.onRadiusMove); document.addEventListener("pointerup", this.onRadiusUp); } - if (!this._isMoving) { - SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)). - map(d => d.borderRounding = "0%"); - } } onRadiusMove = (e: PointerEvent): void => { this._isMoving = true; let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1])); - SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)). - map(d => d.borderRounding = `${Math.min(100, dist)}%`); + dist = dist < 3 ? 0 : dist; + let usingRule = false; SelectionManager.SelectedDocuments().map(dv => { let cv = dv.props.ContainingCollectionView; - let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); + let ruleProvider = cv && cv.props.ruleProvider; let heading = NumCast(dv.props.Document.heading); - cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding)); + ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`); + usingRule = usingRule || (ruleProvider && heading ? true : false); }) + !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)). + map(d => d.borderRounding = `${Math.min(100, dist)}%`); e.stopPropagation(); e.preventDefault(); } @@ -842,24 +850,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let templates: Map<Template, boolean> = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => { - let sorted = SelectionManager.ViewsSortedVertically(); - let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => { - let temps = doc.props.Document.templates; - if (temps instanceof List) { - temps.map(temp => { - if (temp !== Templates.Bullet.Layout || i === 0) { - res.push(temp); - } - }); - } - return res; - }, [] as string[]); let checked = false; - docTemps.forEach(temp => { - if (template.Layout === temp) { - checked = true; - } - }); + SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined)); templates.set(template, checked); }); diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index dd5395802..e9db4b048 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -28,7 +28,8 @@ export interface EditableProps { contents: any; fontStyle?: string; fontSize?: number; - height?: number; + height?: number | "auto"; + maxHeight?: number; display?: string; autosuggestProps?: { resetValue: () => void; @@ -145,7 +146,7 @@ export class EditableView extends React.Component<EditableProps> { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return ( <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} - style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }} + style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }} onClick={this.onClick}> <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span> </div> diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d0464bd5f..ba125d6e5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -30,7 +30,7 @@ export default class KeyManager { } public handle = async (e: KeyboardEvent) => { - let keyname = e.key.toLowerCase(); + let keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); if (modifiers.includes(keyname)) { @@ -166,7 +166,7 @@ export default class KeyManager { break; case "o": let target = SelectionManager.SelectedDocuments()[0]; - target && target.fullScreenClicked(); + target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target) break; case "r": preventDefault = false; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index aa573f16b..57dad5e6b 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -51,8 +51,17 @@ export class InkingControl extends React.Component { let oldColors = selected.map(view => { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let oldColor = StrCast(targetDoc.backgroundColor); - if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) { - let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[]; + let matchedColor = this._selectedColor; + const cv = view.props.ContainingCollectionView; + let ruleProvider: Doc | undefined; + if (cv) { + if (!cv.props.Document.colorPalette) { + let defaultPalette = ["rg14,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; + let colorPalette = Cast(cv.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) cv.props.Document.colorPalette = new List<string>(defaultPalette); + } + let cp = Cast(cv.props.Document.colorPalette, listSpec("string")) as string[]; let closest = 0; let dist = 10000000; let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); @@ -65,21 +74,13 @@ export class InkingControl extends React.Component { } } cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")"; - view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp); - targetDoc.backgroundColor = cp[closest]; - } else - targetDoc.backgroundColor = this._selectedColor; - if (view.props.Document.heading) { - let cv = view.props.ContainingCollectionView; - let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); - let parback = cv && StrCast(cv.props.Document.backgroundColor); - cv && parback && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); - // if (parback && cv && parback.indexOf("rgb") !== -1) { - // let parcol = Utils.fromRGBAstr(parback); - // let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b); - // cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = color.hsl.s - hsl.s); - // } + cv.props.Document.colorPalette = new List(cp); + matchedColor = cp[closest]; + ruleProvider = (view.props.Document.heading && cv && cv.props.ruleProvider) ? cv.props.ruleProvider : undefined; + ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } + !ruleProvider && (targetDoc.backgroundColor = matchedColor); + return { target: targetDoc, previous: oldColor diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 0e687737d..11ec6f0c9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -37,7 +37,6 @@ let swapDocs = async () => { (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled"; - CurrentUserUtils.UserDocument.chromeStatus = "disabled"; await swapDocs(); document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index c3a2cb214..cd8423a12 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -72,7 +72,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> if (this._textTargetDiv) { this._textTargetDiv.style.color = this._textColor; } - this._textAutoHeight = autoHeight; this.TextFieldKey = textFieldKey!; let txf = tx ? tx : () => Transform.Identity(); this._textXf = txf; @@ -103,7 +102,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) { document.removeEventListener("pointermove", this.textBoxMove); document.removeEventListener('pointerup', this.textBoxUp); - let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document], [FormattedTextBox.InputBoxOverlay.props.DataDoc]); + let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]); const [left, top] = this._textXf().inverse().transformPoint(0, 0); dragData.xOffset = e.clientX - left; dragData.yOffset = e.clientY - top; @@ -143,6 +142,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> Document={FormattedTextBox.InputBoxOverlay.props.Document} DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} onClick={undefined} + ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined} ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b64986084..2cec1c052 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -322,6 +322,7 @@ export class MainView extends React.Component { addDocTab={emptyFunction} pinToPres={emptyFunction} onClick={undefined} + ruleProvider={undefined} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} @@ -385,6 +386,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} removeDocument={undefined} + ruleProvider={undefined} onClick={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 45a8556bf..1aed51e64 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -102,7 +102,7 @@ export class PreviewCursor extends React.Component<{}> { (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys !e.key.startsWith("Arrow") && !e.defaultPrevented) { - if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { + if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); PreviewCursor.Visible = false; } diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 7afba5e01..8f06cf770 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -10,12 +10,15 @@ import { emptyFunction } from "../../Utils"; import { ScriptCast } from "../../new_fields/Types"; import { CompileScript } from "../util/Scripting"; import { ScriptField } from "../../new_fields/ScriptField"; +import { DragManager } from "../util/DragManager"; +import { EditableView } from "./EditableView"; export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; onCancel?: () => void; initialText?: string; showDocumentIcons?: boolean; + setParams?: (p: string[]) => void; } @observer @@ -56,17 +59,30 @@ export class ScriptBox extends React.Component<ScriptBoxProps> { onFocus = this.onFocus; onBlur = this.onBlur; } + let params = <EditableView + contents={""} + display={"block"} + maxHeight={72} + height={35} + fontSize={28} + GetValue={() => ""} + SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true} + />; return ( <div className="scriptBox-outerDiv"> + <div style={{ display: "flex", flexDirection: "column", height: "100%" }}> + <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea> + <div style={{ background: "beige" }} >{params}</div> + </div> <div className="scriptBox-toolbar"> <button onClick={e => { this.props.onSave(this._scriptText, this.onError); e.stopPropagation(); }}>Save</button> <button onClick={e => { this.props.onCancel && this.props.onCancel(); e.stopPropagation(); }}>Cancel</button> </div> - <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea> </div> ); } - public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { + //let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);} + public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); let originalText: string | undefined = undefined; @@ -80,7 +96,9 @@ export class ScriptBox extends React.Component<ScriptBoxProps> { } } // tslint:disable-next-line: no-unnecessary-callback-wrapper - let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => { + let params: string[] = []; + let setParams = (p: string[]) => params.splice(0, params.length, ...p); + let scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={() => overlayDisposer()} onSave={(text, onError) => { if (prewrapper) { text = prewrapper + text + (postwrapper ? postwrapper : ""); } @@ -94,9 +112,12 @@ export class ScriptBox extends React.Component<ScriptBoxProps> { onError(script.errors.map(error => error.messageText).join("\n")); return; } + + params.length && DragManager.StartButtonDrag([], text, "a script", {}, params, (button: Doc) => { }, clientX, clientY); + doc[fieldKey] = new ScriptField(script); overlayDisposer(); }} showDocumentIcons />; - overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); + overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: title }); } } diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 393e97a7e..d57a72dad 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,16 +1,15 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import './DocumentDecorations.scss'; -import { DocumentView } from "./nodes/DocumentView"; -import { Template } from "./Templates"; -import React = require("react"); -import { undoBatch } from "../util/UndoManager"; +import { DocumentType } from "../documents/DocumentTypes"; import { DocumentManager } from "../util/DocumentManager"; -import { NumCast } from "../../new_fields/Types"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; +import { undoBatch } from "../util/UndoManager"; +import './DocumentDecorations.scss'; +import { DocumentView } from "./nodes/DocumentView"; +import { Template, Templates } from "./Templates"; +import React = require("react"); +import { Doc } from "../../new_fields/Doc"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -30,6 +29,17 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool } } } +@observer +class ChromeToggle extends React.Component<{ checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> { + render() { + return ( + <li className="chromeToggle"> + <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event)} /> + Chrome + </li> + ); + } +} export interface TemplateMenuProps { docs: DocumentView[]; @@ -41,8 +51,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { @observable private _hidden: boolean = true; dragRef = React.createRef<HTMLUListElement>(); - constructor(props: TemplateMenuProps) { - super(props); + toggleCustom = (e: React.MouseEvent): void => { + this.props.docs.map(dv => dv.toggleCustomView()); } toggleFloat = (e: React.MouseEvent): void => { @@ -57,7 +67,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { setTimeout(() => { let newDocView = DocumentManager.Instance.getDocumentView(topDoc); if (newDocView) { - let de = new DragManager.DocumentDragData([topDoc], [undefined]); + let de = new DragManager.DocumentDragData([topDoc]); de.moveDocument = topDocView.props.moveDocument; let xf = newDocView.ContentDiv!.getBoundingClientRect(); DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, { @@ -80,55 +90,42 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { @action toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => { if (event.target.checked) { - if (template.Name === "Bullet") { - let topDocView = this.props.docs[0]; - topDocView.addTemplate(template); - topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document)); - } else { - this.props.docs.map(d => d.addTemplate(template)); - } - this.props.templates.set(template, true); + this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = template.Name.toLowerCase()); } else { - if (template.Name === "Bullet") { - let topDocView = this.props.docs[0]; - topDocView.removeTemplate(template); - topDocView.props.Document.subBulletDocs = undefined; - } else { - this.props.docs.map(d => d.removeTemplate(template)); - } - this.props.templates.set(template, false); + this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = undefined); } } @undoBatch @action clearTemplates = (event: React.MouseEvent) => { - this.props.docs.map(d => d.clearTemplates()); - Array.from(this.props.templates.keys()).map(t => this.props.templates.set(t, false)); + Templates.TemplateList.map(template => this.props.docs.map(d => d.layoutDoc["show" + template.Name] = false)); } @action - componentWillReceiveProps(nextProps: TemplateMenuProps) { - // this._templates = nextProps.templates; + toggleTemplateActivity = (): void => { + this._hidden = !this._hidden; } + @undoBatch @action - toggleTemplateActivity = (): void => { - this._hidden = !this._hidden; + toggleChrome = (): void => { + this.props.docs.map(dv => dv.layoutDoc.chromeStatus = (dv.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled")); } render() { let templateMenu: Array<JSX.Element> = []; this.props.templates.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />)); - + templateMenu.push(<ChromeToggle key={"chrome"} checked={this.props.docs[0].Document.chromeStatus !== "disabled"} toggle={this.toggleChrome} />); return ( <div className="templating-menu" > <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div> <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}> {templateMenu} + <button onClick={this.toggleCustom}>{this.props.docs[0].Document.nativeLayout ? "Native" : "Custom"}</button> <button onClick={this.toggleFloat}>Float</button> - <button onClick={this.clearTemplates}>Clear</button> + {/* <button onClick={this.clearTemplates}>Clear</button> */} </ul> </div> ); diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 236704fa2..ef78b60d4 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -57,43 +57,7 @@ export namespace Templates { </div> </div>` ); - export const Header = new Template("Header", TemplatePosition.InnerTop, - `<div style = "display:flex; flex-direction:column; height:100%;" > - <div style="width:100%; background-color: rgba(0, 0, 0, .4); color: white; "> - <FormattedTextBox {...props} height={"min-content"} color={"white"} fieldKey={"header"} /> - </div> - <div style="width:100%;height:100%;overflow:auto;">{layout}</div> - </div > ` ); - - export const Bullet = new Template("Bullet", TemplatePosition.InnerTop, - `< div > - <div style="height:100%; width:100%;position:absolute;">{layout}</div> - <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;"> - <img id="isExpander" src="/assets/downarrow.png" width="15px" height="15px" /> - </div> - </div > ` - ); - - export function ImageOverlay(width: number, height: number, field: string = "thumbnail") { - return (`< div > - <div style="height:100%; width:100%; position:absolute;">{layout}</div> - <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;"> - <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} /> - </div> - </div > `); - } - - export function TitleBar(datastring: string) { - return (`<div> - <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100"> - <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">${datastring}</span> - </div> - <div style="height:calc(100% - 25px);"> - <div style="width:100%;overflow:auto">{layout}</div> - </div> - </div>` ); - } - export const TemplateList: Template[] = [Title, Header, Caption, Bullet]; + export const TemplateList: Template[] = [Title, Caption]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index bd8d56851..a54718e9e 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -104,9 +104,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } - if (doc.type === DocumentType.BUTTON) { - doc.collectionContext = this.props.Document; // used by docList() function in Doc.ts so that buttons can iterate over the documents in their collection - } allowDuplicates = true; let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; @@ -130,7 +127,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1); + let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); @@ -144,17 +141,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { return false; } + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. @action.bound moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - let self = this; - let targetDataDoc = this.props.Document; - if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } - if (this.removeDocument(doc)) { - return addDocument(doc); - } - return false; + return this.removeDocument(doc) ? addDocument(doc) : false; } render() { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index fb8b0c41b..a350cfcc5 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -66,15 +66,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } hack: boolean = false; undohack: any = null; - public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) { + public StartOtherDrag(e: any, dragDocs: Doc[]) { let config: any; if (dragDocs.length === 1) { - config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]); + config = CollectionDockingView.makeDocumentConfig(dragDocs[0], undefined); } else { config = { type: 'row', content: dragDocs.map((doc, i) => { - CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]); + CollectionDockingView.makeDocumentConfig(doc, undefined); }) }; } @@ -84,14 +84,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp dragSource.destroy(); }); dragSource._dragListener.onMouseDown(e); - // dragSource.destroy(); - // this.hack = true; - // this.undohack = UndoManager.StartBatch("goldenDrag"); - // dragDocs.map((dragDoc, i) => - // this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener. - // onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } + @undoBatch @action public OpenFullScreen(docView: DocumentView) { let document = Doc.MakeAlias(docView.props.Document); @@ -107,6 +102,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this._maximizedSrc = docView; this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); this.stateChanged(); + SelectionManager.DeselectAll(); } public CloseFullScreen = () => { @@ -175,6 +171,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // + @undoBatch @action public AddRightSplit = (document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) => { let docs = Cast(this.props.Document.data, listSpec(Doc)); @@ -213,6 +210,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp return newContentItem; } + + @undoBatch @action public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined) => { Doc.GetProto(document).lastOpened = new DateField; @@ -412,7 +411,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp e => { e.preventDefault(); e.stopPropagation(); - DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, { + DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, hideSource: false }); @@ -631,6 +630,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { bringToFront={emptyFunction} addDocument={undefined} removeDocument={undefined} + ruleProvider={undefined} ContentScaling={this.contentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 9c26a08f0..3452e8702 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -149,6 +149,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { DataDoc: this.props.rowProps.original, fieldKey: this.props.rowProps.column.id as string, fieldExt: "", + ruleProvider: undefined, ContainingCollectionView: this.props.CollectionView, isSelected: returnFalse, select: emptyFunction, @@ -171,7 +172,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { let onItemDown = (e: React.PointerEvent) => { if (fieldIsDoc) { SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, + this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); } }; let onPointerEnter = (e: React.PointerEvent): void => { @@ -214,7 +216,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { isEditingCallback={this.isEditingCallback} display={"inline"} contents={contents} - height={Number(MAX_ROW_HEIGHT)} + height={"auto"} + maxHeight={Number(MAX_ROW_HEIGHT)} GetValue={() => { let field = props.Document[props.fieldKey]; if (Field.IsField(field)) { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 9d83aa6c1..25d3bd128 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -14,7 +14,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { Gateway } from "../../northstar/manager/Gateway"; -import { SetupDrag, DragManager } from "../../util/DragManager"; +import { DragManager } from "../../util/DragManager"; import { CompileScript, ts, Transformer } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; @@ -32,6 +32,7 @@ import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, Collection import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { DocumentType } from "../../documents/DocumentTypes"; library.add(faCog, faPlus, faSortUp, faSortDown); @@ -161,6 +162,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined} childDocs={this.childDocs} renderDepth={this.props.renderDepth} + ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} width={this.previewWidth} height={this.previewHeight} getTransform={this.getPreviewTransform} @@ -901,6 +903,7 @@ interface CollectionSchemaPreviewProps { fitToBox?: boolean; width: () => number; height: () => number; + ruleProvider: Doc | undefined; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView; onClick?: ScriptField; @@ -995,6 +998,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre Document={this.props.Document} fitToBox={this.props.fitToBox} onClick={this.props.onClick} + ruleProvider={this.props.ruleProvider} showOverlays={this.props.showOverlays} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 654ff2279..14a9dc9d9 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -134,7 +134,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } - @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : ScriptCast(this.Document.onChildClick); } + @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); } getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) { let height = () => this.getDocHeight(layoutDoc); @@ -144,6 +144,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { DataDocument={dataDoc} showOverlays={this.overlays} renderDepth={this.props.renderDepth} + ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} fitToBox={this.props.fitToBox} onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler} width={width} @@ -362,8 +363,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" }); subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" }); subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" }); - subItems.push({ description: "Edit onChildClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onChildClick") }); ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" }); + + let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index bc4fe7dd7..34f2652ff 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -155,6 +155,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let key = StrCast(this.props.parent.props.Document.sectionFilter); let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); + let maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; + newDoc.heading = heading; return this.props.parent.props.addDocument(newDoc); } @@ -180,7 +183,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC if (compiled.compiled) { let scriptField = new ScriptField(compiled); alias.viewSpecScript = scriptField; - let dragData = new DragManager.DocumentDragData([alias], [alias.proto]); + let dragData = new DragManager.DocumentDragData([alias]); DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 99e5ab7b3..d13c69ecf 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -34,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps { export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; + ruleProvider: Doc | undefined; } export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { @@ -52,8 +53,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + get childLayoutPairs() { + return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! })); + } get childDocs() { - let self = this; //TODO tfs: This might not be what we want? //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); @@ -125,9 +128,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { if (de.data.dropAction || de.data.userDropAction) { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (de.data.moveDocument) { - let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; - added = movedDocs.reduce((added: boolean, d) => - de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); + let movedDocs = de.data.draggedDocuments;// de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; + // note that it's possible the drag function might create a drop document that's not the same as the + // original dragged document. So we explicitly call addDocument() with a droppedDocument and + added = movedDocs.reduce((added: boolean, d, i) => + de.data.moveDocument(d, this.props.Document, (doc: Doc) => this.props.addDocument(de.data.droppedDocuments[i])) || added, false); } else { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 50f03005c..40bea2f9d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -37,6 +37,7 @@ export interface TreeViewProps { containingCollection: Doc; renderDepth: number; deleteDoc: (doc: Doc) => boolean; + ruleProvider: Doc | undefined; moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; @@ -52,6 +53,7 @@ export interface TreeViewProps { active: () => boolean; showHeaderFields: () => boolean; preventTreeViewOpen: boolean; + renderedIds: string[]; } library.add(faTrashAlt); @@ -199,6 +201,7 @@ class TreeView extends React.Component<TreeViewProps> { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); } ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" }); ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); e.stopPropagation(); e.preventDefault(); @@ -274,7 +277,9 @@ class TreeView extends React.Component<TreeViewProps> { let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen); + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, + this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen, + [...this.props.renderedIds, doc[Id]]); } else { contentElement = <EditableView key="editableView" @@ -306,7 +311,8 @@ class TreeView extends React.Component<TreeViewProps> { TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, - this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen)} + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen, + [...this.props.renderedIds, this.props.document[Id]])} </ul >; } else if (this.treeViewExpandedView === "fields") { return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}> @@ -320,6 +326,7 @@ class TreeView extends React.Component<TreeViewProps> { DataDocument={this.resolvedDataDoc} renderDepth={this.props.renderDepth} showOverlays={this.noOverlays} + ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider} fitToBox={this.boundsOfCollectionDocument !== undefined} width={this.docWidth} height={this.docHeight} @@ -340,7 +347,7 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { - return <div className="bullet" title="view inline" onClick={action(() => this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> + return <div className="bullet" title="view inline" onClick={action((e: React.MouseEvent) => { this.treeViewOpen = !this.treeViewOpen; e.stopPropagation(); })} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />} </div>; } @@ -373,6 +380,7 @@ class TreeView extends React.Component<TreeViewProps> { style={{ color: this.props.document.isMinimized ? "red" : "black", background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0", + fontWeight: this.props.document.search_string ? "bold" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" }} > @@ -391,7 +399,7 @@ class TreeView extends React.Component<TreeViewProps> { {this.renderTitle} </div> <div className="treeViewItem-border"> - {!this.treeViewOpen ? (null) : this.renderContent} + {!this.treeViewOpen || this.props.renderedIds.indexOf(this.props.document[Id]) !== -1 ? (null) : this.renderContent} </div> </li> </div>; @@ -414,7 +422,8 @@ class TreeView extends React.Component<TreeViewProps> { panelWidth: () => number, renderDepth: number, showHeaderFields: () => boolean, - preventTreeViewOpen: boolean + preventTreeViewOpen: boolean, + renderedIds: string[] ) { let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); @@ -485,6 +494,7 @@ class TreeView extends React.Component<TreeViewProps> { dataDoc={pair.data} containingCollection={containingCollection} treeViewId={treeViewId} + ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc} key={child[Id]} indentDocument={indent} renderDepth={renderDepth} @@ -501,7 +511,8 @@ class TreeView extends React.Component<TreeViewProps> { parentKey={key} active={active} showHeaderFields={showHeaderFields} - preventTreeViewOpen={preventTreeViewOpen} />; + preventTreeViewOpen={preventTreeViewOpen} + renderedIds={renderedIds} />; }); } } @@ -591,13 +602,14 @@ export class CollectionTreeView extends CollectionSubView(Document) { <div id="body" className="collectionTreeView-dropTarget" style={{ overflow: "auto", background: StrCast(this.props.Document.backgroundColor, "lightgray") }} onContextMenu={this.onContextMenu} - onWheel={(e: React.WheelEvent) => (e.target as any).scrollHeight > (e.target as any).clientHeight && e.stopPropagation()} + onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} ref={this.createTreeDropTarget}> <EditableView contents={this.resolvedDataDoc.title} display={"block"} - height={72} + maxHeight={72} + height={"auto"} GetValue={() => StrCast(this.resolvedDataDoc.title)} SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)} OnFillDown={undoBatch((value: string) => { @@ -614,7 +626,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled", - BoolCast(this.props.Document.preventTreeViewOpen)) + BoolCast(this.props.Document.preventTreeViewOpen), []) } </ul> </div > diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 6182e82f4..548f663ec 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -87,7 +87,8 @@ export class CollectionView extends React.Component<FieldViewProps> { onContextMenu = (e: React.MouseEvent): void => { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - let subItems: ContextMenuProps[] = []; + let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); + let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; delete this.props.Document.usePivotLayout; }, icon: "signature" }); if (CollectionBaseView.InSafeMode()) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); @@ -95,6 +96,12 @@ export class CollectionView extends React.Component<FieldViewProps> { subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" }); subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" }); + subItems.push({ + description: "Stacking (AutoHeight)", event: () => { + this.props.Document.viewType = CollectionViewType.Stacking; + this.props.Document.autoHeight = true + }, icon: "ellipsis-v" + }); subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { @@ -103,16 +110,12 @@ export class CollectionView extends React.Component<FieldViewProps> { break; } } - ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); + !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); + let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); - - let makes = ContextMenu.Instance.findByDescription("Make..."); - let makeItems: ContextMenuProps[] = makes && "subitems" in makes ? makes.subitems : []; - makeItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); - !makes && ContextMenu.Instance.addItem({ description: "Make...", subitems: makeItems, icon: "hand-point-right" }); } } diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 17111af58..d8475a467 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -38,8 +38,8 @@ export class SelectorContextMenu extends React.Component<SelectorProps> { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index fc5212edd..cfd18ad35 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,8 +1,9 @@ .collectionfreeformlinkview-linkLine { stroke: black; transform: translate(10000px,10000px); - opacity: 0.5; + opacity: 0.8; pointer-events: all; + stroke-width: 3px; } .collectionfreeformlinkview-linkCircle { stroke: rgb(0,0,0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 6af87b138..df089eb00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -39,10 +39,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo // let l = this.props.LinkDocs; let a = this.props.A; let b = this.props.B; - let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / NumCast(a.zoomBasis, 1) / 2); - let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / NumCast(a.zoomBasis, 1) / 2); - let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2); - let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2); + let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2); + let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2); + let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2); + let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2); let text = ""; // let first = this.props.LinkDocs[0]; // if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); @@ -50,7 +50,6 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo return ( <> <line key="linkLine" className="collectionfreeformlinkview-linkLine" - style={{ strokeWidth: `${2 * 1 / 2}` }} x1={`${x1}`} y1={`${y1}`} x2={`${x2}`} y2={`${y2}`} /> {/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle" diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a593128be..a25627dd1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -31,8 +31,8 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP // let srcTarg = srcDoc; // let x1 = NumCast(srcDoc.x); // let x2 = NumCast(dstDoc.x); - // let x1w = NumCast(srcDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1); - // let x2w = NumCast(dstDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1); + // let x1w = NumCast(srcDoc.width, -1); + // let x2w = NumCast(dstDoc.width, -1); // if (x1w < 0 || x2w < 0 || i === j) { } // else { // let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 21f119d57..03ac012b4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs } from "../../../documents/Documents"; @@ -39,6 +39,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { DocServer } from "../../../DocServer"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -48,6 +49,9 @@ export const panZoomSchema = createSchema({ scale: "number", arrangeScript: ScriptField, arrangeInit: ScriptField, + useClusters: "boolean", + isRuleProvider: "boolean", + fitToBox: "boolean" }); export interface ViewDefBounds { @@ -179,9 +183,9 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema) export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _lastX: number = 0; private _lastY: number = 0; + private _inkKey = "ink"; // the document key used to store ink annotation strokes private get _pwidth() { return this.props.PanelWidth(); } private get _pheight() { return this.props.PanelHeight(); } - private inkKey = "ink"; private _childLayoutDisposer?: IReactionDisposer; private _childDisposer?: IReactionDisposer; @@ -240,7 +244,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return res; } - @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; } + @computed get fitToBox() { return this.props.fitToBox || this.Document.fitToBox; } @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; } public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') @@ -256,29 +260,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed - newBox.heading = 1; - for (let i = 0; i < this.childDocs.length; i++) { - if (this.childDocs[i].heading == 1) { - newBox.heading = 2; - } + let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0; + if (heading === 0) { + let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 : + DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0); + heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } - PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { - if (!ruleProvider) ruleProvider = this.props.Document; - // saturation shift - // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor)); - // let hsl = Utils.RGBToHSL(back.r, back.g, back.b); - // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l }; - // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); - // OR transparency set - let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - col && (Doc.GetProto(newBox).backgroundColor = col); - - let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (Doc.GetProto(newBox).borderRounding = round); - newBox.ruleProvider = ruleProvider; - this.addDocument(newBox, false); - }); + !this.Document.isRuleProvider && (newBox.heading = heading); + this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); @@ -293,10 +283,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } public getActiveDocuments = () => { const curPage = FieldValue(this.Document.curPage, -1); - return this.childDocs.filter(doc => { - var page = NumCast(doc.page, -1); + return this.childLayoutPairs.filter(pair => { + var page = NumCast(pair.layout!.page, -1); return page === curPage || page === -1; - }); + }).map(pair => pair.layout); } @computed get fieldExtensionDoc() { @@ -374,7 +364,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { tryDragCluster(e: PointerEvent) { let probe = this.getTransform().transformPoint(e.clientX, e.clientY); - let cluster = this.childDocs.reduce((cluster, cd) => { + let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { let cx = NumCast(cd.x) - this._clusterDistance; let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(cd.width) + 2 * this._clusterDistance; @@ -385,7 +375,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return cluster; }, -1); if (cluster !== -1) { - let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view let prevSelected = SelectionManager.SelectedDocuments(); @@ -394,7 +384,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); + let de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); @@ -414,9 +404,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action - updateClusters() { + updateClusters(useClusters: boolean) { + this.Document.useClusters = useClusters; this.sets.length = 0; - this.childDocs.map(c => { + this.childLayoutPairs.map(pair => pair.layout).map(c => { let included = []; for (let i = 0; i < this.sets.length; i++) { for (let member of this.sets[i]) { @@ -444,20 +435,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action updateCluster(doc: Doc) { + let childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.props.Document.useClusters) { this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; this.sets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) { doc.cluster = i; } })); - if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) { + if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } this.sets.map((set, i) => { - if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) { + if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { doc.cluster = i; } }); @@ -472,22 +464,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } getClusterColor = (doc: Doc) => { - if (this.props.Document.useClusters) { - let cluster = NumCast(doc.cluster); + let clusterColor = ""; + let cluster = NumCast(doc.cluster); + if (this.Document.useClusters) { if (this.sets.length <= cluster) { - setTimeout(() => this.updateCluster(doc), 0);// this.updateClusters(), 0); - return ""; + setTimeout(() => this.updateCluster(doc), 0); + } else { + // choose a cluster color from a palette + let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; + clusterColor = colors[cluster % colors.length]; + let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor != s.defaultBackgroundColor)) : undefined; + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); + set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); } - let set = this.sets.length > cluster ? this.sets[cluster] : undefined; - let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; - let clusterColor = colors[cluster % colors.length]; - set && set.filter(s => !s.isBackground).map(s => - s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); - set && set.filter(s => s.isBackground).map(s => - s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); - return clusterColor; } - return ""; + return clusterColor; } @action @@ -519,7 +511,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childDocs || []; + let docs = this.childLayoutPairs.map(pair => pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { PDFMenu.Instance.fadeOut(true); @@ -566,31 +558,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (BoolCast(this.props.Document.lockedPosition)) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); - return; } - - let childSelected = this.childDocs.some(doc => { - var dv = DocumentManager.Instance.getDocumentView(doc); - return dv && SelectionManager.IsSelected(dv) ? true : false; - }); - if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) { - return; - } - e.stopPropagation(); - - // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix. - // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) { - // let deltaScale = (1 - (e.deltaY / coefficient)); - // let nw = this.nativeWidth * deltaScale; - // let nh = this.nativeHeight * deltaScale; - // if (nw && nh) { - // this.props.Document.nativeWidth = nw; - // this.props.Document.nativeHeight = nh; - // } - // e.preventDefault(); - // } - // else - { + else if (this.props.active()) { + e.stopPropagation(); let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); @@ -624,41 +594,33 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { super.onDrop(e, { x: pt[0], y: pt[1] }); } - onDragOver = (): void => { - } - bringToFront = (doc: Doc, sendToBack?: boolean) => { if (sendToBack || doc.isBackground) { doc.zIndex = 0; - return; } - const docs = this.childDocs; - docs.slice().sort((doc1, doc2) => { - if (doc1 === doc) return 1; - if (doc2 === doc) return -1; - return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); - }).forEach((doc, index) => doc.zIndex = index + 1); - doc.zIndex = docs.length + 1; + else { + const docs = this.childLayoutPairs.map(pair => pair.layout); + docs.slice().sort((doc1, doc2) => { + if (doc1 === doc) return 1; + if (doc2 === doc) return -1; + return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); + }).forEach((doc, index) => doc.zIndex = index + 1); + doc.zIndex = docs.length + 1; + } } focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => { - const panX = this.Document.panX; - const panY = this.Document.panY; - const id = this.Document[Id]; const state = HistoryUtil.getState(); - state.initializers = state.initializers || {}; // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state - if (state.type === "doc" && panX !== undefined && panY !== undefined) { - const init = state.initializers[id]; + if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) { + const init = state.initializers![this.Document[Id]]; if (!init) { - state.initializers[id] = { - panX, panY - }; + state.initializers![this.Document[Id]] = { panX: this.Document.panX, panY: this.Document.panY }; HistoryUtil.pushState(state); - } else if (init.panX !== panX || init.panY !== panY) { - init.panX = panX; - init.panY = panY; + } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) { + init.panX = this.Document.panX; + init.panY = this.Document.panY; HistoryUtil.pushState(state); } } @@ -666,7 +628,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; const newState = HistoryUtil.getState(); - (newState.initializers || (newState.initializers = {}))[id] = { panX: newPanX, panY: newPanY }; + newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); this.setPan(newPanX, newPanY); @@ -675,7 +637,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (willZoom) { this.setScaleToZoom(doc, scale); } - } setScaleToZoom = (doc: Doc, scale: number = 0.5) => { @@ -707,7 +668,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, - onClick: this.props.onClick, + ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves + onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, PanelWidth: childLayout[WidthSym], @@ -732,6 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, + ruleProvider: this.props.ruleProvider, onClick: this.props.onClick, ScreenToLocalTransform: this.getTransform, renderDepth: this.props.renderDepth, @@ -753,11 +716,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } { const result = script.script.run(params); - if (!result.success) { - return {}; - } - let doc = params.doc; - return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result; + return !result.success ? {} : result.result !== undefined ? result.result : + { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; } viewDefsToJSX = (views: any[]) => { @@ -801,12 +761,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const initScript = this.Document.arrangeInit; const script = this.Document.arrangeScript; let state: any = undefined; - let docs = this.childDocs; - let overlayDocs = DocListCast(this.props.Document.localOverlays); - overlayDocs && docs.push(...overlayDocs); + let pairs = this.childLayoutPairs; let elements: ViewDefResult[] = []; if (initScript) { - const initResult = initScript.script.run({ docs, collection: this.Document }); + const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }); if (initResult.success) { const result = initResult.result; const { state: scriptState, views } = result; @@ -814,18 +772,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { elements = this.viewDefsToJSX(views); } } - let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { - var page = NumCast(doc.page, -1); + let docviews = pairs.reduce((prev, pair) => { + var page = NumCast(pair.layout.page, -1); if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { - let minim = BoolCast(doc.isMinimized); + let minim = BoolCast(pair.layout.isMinimized); if (minim === undefined || !minim) { - const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : - { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; + const pos = script ? this.getCalculatedPositions(script, { doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }) : + { x: Cast(pair.layout.x, "number"), y: Cast(pair.layout.y, "number"), z: Cast(pair.layout.z, "number"), width: Cast(pair.layout.width, "number"), height: Cast(pair.layout.height, "number") }; state = pos.state === undefined ? state : pos.state; - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc); if (pair.layout && !(pair.data instanceof Promise)) { prev.push({ - ele: <CollectionFreeFormDocumentView key={doc[Id]} + ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} + ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} jitterRotation={NumCast(this.props.Document.jitterRotation)} x={script ? pos.x : undefined} y={script ? pos.y : undefined} width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, @@ -842,22 +800,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed.struct get views() { - let source = this.elements; - return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } - @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } - fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox; - arrangeContents = async () => { const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); UndoManager.RunInBatch(() => { @@ -882,89 +836,86 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, "arrange contents"); } + autoFormat = () => { + this.Document.isRuleProvider = !this.Document.isRuleProvider; + // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading. + this.Document.isRuleProvider && this.childLayoutPairs.map(pair => + // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template) + DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => { + let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); + let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout; + if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) { + Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor; + } + }) + ) + } + analyzeStrokes = async () => { - let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); - if (!data) { - return; + let data = Cast(this.fieldExtensionDoc[this._inkKey], InkField); + if (data) { + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData); } - let relevantKeys = ["inkAnalysis", "handwriting"]; - CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData); } onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; + + if (this.childDocs.some(d => BoolCast(d.isTemplate))) { + layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); + } + layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); + layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); + layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" }); + layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); + layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); + layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); layoutItems.push({ - description: "Import document", icon: "upload", event: () => { + description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); input.type = "file"; input.accept = ".zip"; input.onchange = async _e => { - const files = input.files; - if (!files) return; - const file = files[0]; - let formData = new FormData(); - formData.append('file', file); - formData.append('remap', "true"); const upload = Utils.prepend("/uploadDoc"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json === "error") { - return; - } - const doc = await DocServer.GetRefField(json); - if (!doc || !(doc instanceof Doc)) { - return; + let formData = new FormData(); + const file = input.files && input.files[0]; + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc) { + const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument && this.props.addDocument(doc, false); + } + } } - const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY); - doc.x = x, doc.y = y; - this.props.addDocument && - this.props.addDocument(doc, false); }; input.click(); } }); - layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); - layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - layoutItems.push({ - description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, - event: async () => { - Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes - Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; - this.props.Document.useClusters = !this.props.Document.useClusters; - this.updateClusters(); - }, - icon: !this.props.Document.useClusters ? "braille" : "braille" - }); + layoutItems.push({ - description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, - event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, - icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" + description: "Add Note ...", + subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ + description: (i + 1) + ": " + StrCast(note.title), + event: ({ x, y }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(x, y)[0], y: this.getTransform().transformPoint(x, y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), + icon: "eye" + })), + icon: "eye" }); - layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); - layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); - - let noteItems: ContextMenuProps[] = []; - noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); - noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); - noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); - noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); - layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }) ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (noteStyle: string, color: string) => { - let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color })) - } private childViews = () => [ <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />, ...this.views ] - private overlayChildViews = () => { - return [...this.overlayViews]; - } public static AddCustomLayout(doc: Doc, dataKey: string): () => void { return () => { @@ -1003,21 +954,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); return ( <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel} - onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}> + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected} addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> <CollectionFreeFormLinksView {...this.props} key="freeformLinks"> - <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} > + <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={this._inkKey} > {this.childViews} </InkingCanvas> </CollectionFreeFormLinksView> <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> </CollectionFreeFormViewPannableContents> </MarqueeView> - {this.overlayChildViews()} + {this.overlayViews} <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} /> </div> ); @@ -1038,7 +989,6 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & @observer class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { @computed get backgroundView() { - let props = this.props; return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 56d8127e2..8decebe0d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,6 +22,9 @@ import React = require("react"); import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; import { string } from "prop-types"; import { listSpec } from "../../../../new_fields/Schema"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; +import { CompileScript } from "../../../util/Scripting"; +import { ComputedField } from "../../../../new_fields/ScriptField"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -97,6 +100,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } else if (!e.ctrlKey) { this.props.addLiveTextDocument( Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); + } else if (e.keyCode > 48 && e.keyCode <= 57) { + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }); + text.layout = notes[(e.keyCode - 49) % notes.length]; + this.props.addLiveTextDocument(text); } e.stopPropagation(); } @@ -281,15 +289,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps> let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map<string, number>(); [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.backgroundColor); + let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); else usedPaletted.set(bg, 1); } }); + usedPaletted.delete("#f1efeb"); + usedPaletted.delete("white"); + usedPaletted.delete("rgba(255,255,255,1)"); let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); - let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; + let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, @@ -300,13 +311,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps> defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, - title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", + title: "a nested collection", }); let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); - if (e.key === "s") { + if (e.key === "s" || e.key === "S") { selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; @@ -315,39 +326,24 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return d; }); newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - newCollection.proto!.summaryDoc = summary; - selected = [newCollection]; + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + Doc.GetProto(newCollection).summaryDoc = summary; + Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); newCollection.x = bounds.left + bounds.width; - summary.proto!.subBulletDocs = new List<Doc>(selected); - summary.templates = new List<string>([Templates.Bullet.Layout]); - let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); - container.viewType = CollectionViewType.Stacking; - container.autoHeight = true; - this.props.addLiveTextDocument(container); - // }); - } else if (e.key === "S") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; - return d; - }); - newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - newCollection.proto!.summaryDoc = summary; - selected = [newCollection]; - newCollection.x = bounds.left + bounds.width; - //this.props.addDocument(newCollection, false); - summary.proto!.summarizedDocs = new List<Doc>(selected); - summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" - summary.autoHeight = true; - - this.props.addLiveTextDocument(summary); + let computed = CompileScript(`return summaryTitle(this);`, { params: { this: "Doc" }, typecheck: false }); + computed.compiled && (Doc.GetProto(newCollection).title = new ComputedField(computed)); + if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); + container.viewType = CollectionViewType.Stacking; + container.autoHeight = true; + Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "inPlace", or "onRight" + this.props.addLiveTextDocument(container); + } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them + Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" + this.props.addLiveTextDocument(summary); + } } else { - newCollection.ruleProvider = this.props.container.props.Document; this.props.addDocument(newCollection, false); this.props.selectDocuments([newCollection]); } diff --git a/src/client/views/document_templates/image_card/ImageCard.tsx b/src/client/views/document_templates/image_card/ImageCard.tsx index 9931515f3..868afc423 100644 --- a/src/client/views/document_templates/image_card/ImageCard.tsx +++ b/src/client/views/document_templates/image_card/ImageCard.tsx @@ -1,8 +1,5 @@ import * as React from 'react'; -import { DocComponent } from '../../DocComponent'; import { FieldViewProps } from '../../nodes/FieldView'; -import { createSchema, makeInterface } from '../../../../new_fields/Schema'; -import { createInterface } from 'readline'; import { ImageBox } from '../../nodes/ImageBox'; export default class ImageCard extends React.Component<FieldViewProps> { diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 9c65bbcf9..75a4aea26 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -173,7 +173,6 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { if (LinkFollowBox.destinationDoc) { let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc); view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); - SelectionManager.DeselectAll(); } } @@ -181,15 +180,14 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { openColFullScreen = (options: { context: Doc }) => { if (LinkFollowBox.destinationDoc) { if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } let view = DocumentManager.Instance.getDocumentView(options.context); view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); this.highlightDoc(); - SelectionManager.DeselectAll(); } } @@ -210,8 +208,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { if (LinkFollowBox.destinationDoc) { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } @@ -297,8 +295,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { if (LinkFollowBox.destinationDoc) { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index b6a24b0c8..d711ac284 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -57,7 +57,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { let opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); if (opp) return opp; }) as Doc[]; - let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined)); + let dragData = new DragManager.DocumentDragData(draggedDocs); DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { handlers: { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 54848344b..68d3b8ae1 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -13,6 +13,8 @@ import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; import './ButtonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; library.add(faEdit as any); @@ -41,11 +43,24 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); } } + + specificContextMenu = (e: React.MouseEvent): void => { + let funcs: ContextMenuProps[] = []; + funcs.push({ + description: "Clear Script Params", event: () => { + let params = Cast(this.props.Document.buttonParams, listSpec("string")); + params && params.map(p => this.props.Document[p] = undefined) + }, icon: "trash" + }); + + ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" }); + } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - Doc.GetProto(this.dataDoc).source = new List<Doc>(de.data.droppedDocuments); + if (de.data instanceof DragManager.DocumentDragData && e.target) { + this.props.Document[(e.target as any).textContent] = new List<Doc>(de.data.droppedDocuments); e.stopPropagation(); } } @@ -55,7 +70,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt let missingParams = params && params.filter(p => this.props.Document[p] === undefined); params && params.map(async p => await DocListCastAsync(this.props.Document[p])); // bcz: really hacky form of prefetching ... return ( - <div className="buttonBox-outerDiv" ref={this.createDropTarget} > + <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} > <div className="buttonBox-mainButtonCenter"> {(this.Document.text || this.Document.title)} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss new file mode 100644 index 000000000..de0b00a81 --- /dev/null +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss @@ -0,0 +1,5 @@ +.collectionFreeFormDocumentView-container {
+ transform-origin: left top;
+ position: absolute;
+ background-color: transparent;
+}
\ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9692dd8a9..19d4a6784 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,13 +1,14 @@ -import { computed } from "mobx"; +import { computed, action, observable, reaction, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; +import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; +import { FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; -import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; -import "./DocumentView.scss"; +import { percent2frac } from "../../../Utils" +import { DocumentView, DocumentViewProps, documentSchema } from "./DocumentView"; +import "./CollectionFreeFormDocumentView.scss"; import React = require("react"); -import { Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -17,56 +18,80 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { height?: number; jitterRotation: number; } - -const schema = createSchema({ - zoomBasis: "number", +const positionSchema = createSchema({ zIndex: "number", + x: "number", + y: "number", + z: "number", }); -//TODO Types: The import order is wrong, so positionSchema is undefined -type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>; -const FreeformDocument = makeInterface(schema, positionSchema); +export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer -export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) { - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg) scale(${this.zoom}) `; } - @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } - @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.height !== undefined ? this.props.height : this.Document.height || 0; } - @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); } - @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); } - @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); } - @computed get scaleToOverridingWidth() { return this.width / NumCast(this.props.Document.width, this.width); } +export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) { + _disposer: IReactionDisposer | undefined = undefined; + @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } + @computed get X() { return this._animx !== undefined ? this._animx : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } + @computed get Y() { return this._animy !== undefined ? this._animy : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } + @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); } + @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); } + @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } + @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } + @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); } + + @computed get renderScriptDim() { + if (this.Document.renderScript) { + let someView = Cast(this.props.Document.someView, Doc); + let minimap = Cast(this.props.Document.minimap, Doc); + if (someView instanceof Doc && minimap instanceof Doc) { + let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; + let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; + let w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); + let h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); + return { x: x, y: y, width: w, height: h }; + } + } + return undefined; + } + + componentWillUnmount() { + this._disposer && this._disposer(); + } + componentDidMount() { + this._disposer = reaction(() => this.props.Document.iconTarget, + () => { + const icon = this.props.Document.iconTarget ? Array.from(Cast(this.props.Document.iconTarget, listSpec("number"))!) : undefined; + if (icon) { + let target = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); + if (icon[2] === 1) { + this._animx = target[0]; + this._animy = target[1]; + } + setTimeout(action(() => { + this._animx = icon[2] === 1 ? this.Document.x : target[0]; + this._animy = icon[2] === 1 ? this.Document.y : target[1]; + }), 25); + } else { + this._animx = this._animy = undefined; + } + }, { fireImmediately: true }); + } - contentScaling = () => this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? this.width / this.nativeWidth : 1; + contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.zoom / this.scaleToOverridingWidth) - - animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { - this.props.bringToFront(this.props.Document); - let targetPos = [this.Document.x || 0, this.Document.y || 0]; - let iconPos = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); - DocumentView.animateBetweenIconFunc(this.props.Document, - this.Document.width || 0, this.Document.height || 0, stime, maximizing, (progress: number) => { - let pval = maximizing ? - [iconPos[0] + (targetPos[0] - iconPos[0]) * progress, iconPos[1] + (targetPos[1] - iconPos[1]) * progress] : - [targetPos[0] + (iconPos[0] - targetPos[0]) * progress, targetPos[1] + (iconPos[1] - targetPos[1]) * progress]; - this.Document.x = progress === 1 ? targetPos[0] : pval[0]; - this.Document.y = progress === 1 ? targetPos[1] : pval[1]; - }); - } + .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth); borderRounding = () => { - let br = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.borderRounding : this.props.Document.borderRounding); + let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; + let br = StrCast(((this.layoutDoc.layout as Doc) || this.Document).borderRounding); + br = !br && ruleRounding ? ruleRounding : br; if (br.endsWith("%")) { - let percent = Number(br.substr(0, br.length - 1)) / 100; - let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight)); - let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); - return minDim; + let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight)); + return percent2frac(br) * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); } return undefined; } @@ -76,41 +101,31 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF clusterColorFunc = (doc: Doc) => this.clusterColor; + get layoutDoc() { + // if this document's layout field contains a document (ie, a rendering template), then we will use that + // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. + return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + } + + @observable _animx: number | undefined = undefined; + @observable _animy: number | undefined = undefined; + render() { - let txf = this.transform; - let w = this.width; - let h = this.height; - let renderScript = this.Document.renderScript; - if (renderScript) { - let someView = Cast(this.Document.someView, Doc); - let minimap = Cast(this.Document.minimap, Doc); - if (someView instanceof Doc && minimap instanceof Doc) { - let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; - let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; - w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); - h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); - txf = `translate(${x}px,${y}px)`; - } - } const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return ( <div className="collectionFreeFormDocumentView-container" style={{ - transformOrigin: "left top", - position: "absolute", - backgroundColor: "transparent", boxShadow: - this.props.Document.opacity === 0 ? undefined : // if it's not visible, then no shadow - this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow - this.clusterColor ? ( - this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` : // if it's a background & has a cluster color, make the shadow spread really big - `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent - undefined, + this.layoutDoc.opacity === 0 ? undefined : // if it's not visible, then no shadow + this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow + this.clusterColor ? (`${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent + this.layoutDoc.isBackground ? `1px 1px 1px ${this.clusterColor}` : // if it's a background & has a cluster color, make the shadow spread really big + StrCast(this.layoutDoc.boxShadow, ""), borderRadius: this.borderRounding(), - transform: txf, - transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition), - width: w, - height: h, + transform: this.transform, + transition: this.props.Document.isIconAnimating ? "transform .5s" : hasPosition ? "transform 1s" : StrCast(this.layoutDoc.transition), + width: this.width, + height: this.height, zIndex: this.Document.zIndex || 0, }} > <DocumentView {...this.props} @@ -119,7 +134,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF backgroundColor={this.clusterColorFunc} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} - animateBetweenIcon={this.animateBetweenIcon} /> </div> ); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 7c72fb6e6..63011f271 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -4,6 +4,8 @@ position: inherit; top: 0; left:0; + border-radius: inherit; + // background: $light-color; //overflow: hidden; transform-origin: left top; @@ -30,4 +32,44 @@ } .documentView-node-topmost { background: white; +} + +.documentView-styleWrapper { + position: absolute; + display: inline-block; + width:100%; + height:100%; + pointer-events: none; + + .documentView-styleContentWrapper { + width:100%; + display: inline-block; + position: absolute; + } + .documentView-titleWrapper { + overflow:hidden; + color: white; + transform-origin: top left; + top: 0; + height: 25; + background: rgba(0, 0, 0, .4); + padding: 4px; + text-align: center; + text-overflow: ellipsis; + white-space: pre; + } + + .documentView-searchHighlight { + position: absolute; + background: yellow; + bottom: -20px; + border-radius: 5px; + transform-origin: bottom left; + } + + .documentView-captionWrapper { + position: absolute; + bottom: 0; + transform-origin: bottom left; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 633455f63..37c38cc04 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, runInAction, trace, observable } from "mobx"; +import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; @@ -9,12 +9,13 @@ import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { RouteStore } from '../../../server/RouteStore'; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from "../../util/DocumentManager"; @@ -35,12 +36,11 @@ import { MainView } from '../MainView'; import { OverlayView } from '../OverlayView'; import { ScriptBox } from '../ScriptBox'; import { ScriptingRepl } from '../ScriptingRepl'; -import { Template } from "./../Templates"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; +import { CompileScript, Scripting } from '../../util/Scripting'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -89,6 +89,7 @@ export interface DocumentViewProps { renderDepth: number; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; ContentScaling: () => number; + ruleProvider: Doc | undefined; PanelWidth: () => number; PanelHeight: () => number; focus: (doc: Doc, willZoom: boolean, scale?: number) => void; @@ -101,35 +102,41 @@ export interface DocumentViewProps { zoomToScale: (scale: number) => void; backgroundColor: (doc: Doc) => string | undefined; getScale: () => number; - animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; + animateBetweenIcon?: (maximize: boolean, target: number[]) => void; ChromeHeight?: () => number; } -const schema = createSchema({ - layout: "string", +export const documentSchema = createSchema({ + layout: "string", // should also allow Doc but that can't be expressed in the schema + title: "string", nativeWidth: "number", nativeHeight: "number", backgroundColor: "string", opacity: "number", hidden: "boolean", onClick: ScriptField, -}); - -export const positionSchema = createSchema({ - nativeWidth: "number", - nativeHeight: "number", + ignoreAspect: "boolean", + autoHeight: "boolean", + isTemplate: "boolean", + isButton: "boolean", + isBackground: "boolean", + ignoreClick: "boolean", + type: "string", + maximizeLocation: "string", + lockedPosition: "boolean", + excludeFromLibrary: "boolean", width: "number", height: "number", - x: "number", - y: "number", - z: "number", + borderRounding: "string", + fitToBox: "boolean", + searchFields: "string", + heading: "number", + showCaption: "string", + showTitle: "string" }); -export type PositionDocument = makeInterface<[typeof positionSchema]>; -export const PositionDocument = makeInterface(positionSchema); - -type Document = makeInterface<[typeof schema]>; -const Document = makeInterface(schema); +type Document = makeInterface<[typeof documentSchema]>; +const Document = makeInterface(documentSchema); @observer export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) { @@ -137,25 +144,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _downY: number = 0; private _lastTap: number = 0; private _doubleTap = false; - private _hitExpander = false; private _hitTemplateDrag = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; - _animateToIconDisposer?: IReactionDisposer; - _reactionDisposer?: IReactionDisposer; public get ContentDiv() { return this._mainCont.current; } @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost(): boolean { return this.props.renderDepth === 0; } - @computed get templates(): List<string> { - let field = this.props.Document.templates; - if (field && field instanceof List) { - return field; - } - return new List<string>(); - } - set templates(templates: List<string>) { this.props.Document.templates = templates; } - screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); @action componentDidMount() { @@ -164,48 +159,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu handlers: { drop: this.drop.bind(this) } }); } - // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes - this._reactionDisposer = reaction(() => [DocListCast(this.props.Document.maximizedDocs).map(md => md.title), - this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""], - () => { - let maxDoc = DocListCast(this.props.Document.maximizedDocs); - if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) { - this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon"; - } - let sumDoc = Cast(this.props.Document.summaryDoc, Doc); - if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) { - this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded"; - } - }, { fireImmediately: true }); - this._animateToIconDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => - (values instanceof List) && this.animateBetweenIcon(values, values[2], values[3] ? true : false) - , { fireImmediately: true }); DocumentManager.Instance.DocumentViews.push(this); } - animateBetweenIcon = (iconPos: number[], startTime: number, maximizing: boolean) => { - this.props.animateBetweenIcon ? this.props.animateBetweenIcon(iconPos, startTime, maximizing) : - DocumentView.animateBetweenIconFunc(this.props.Document, this.Document[WidthSym](), this.Document[HeightSym](), startTime, maximizing); - } - - public static animateBetweenIconFunc = (doc: Doc, width: number, height: number, stime: number, maximizing: boolean, cb?: (progress: number) => void) => { - setTimeout(() => { - let now = Date.now(); - let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1; - doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; - doc.height = progress === 1 ? height : maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; - cb && cb(progress); - if (now < stime + 200) { - DocumentView.animateBetweenIconFunc(doc, width, height, stime, maximizing, cb); - } - else { - doc.isMinimized = !maximizing; - doc.isIconAnimating = undefined; - } - doc.willMaximize = false; - }, - 2); - } @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); @@ -217,31 +173,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @action componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - this._animateToIconDisposer && this._animateToIconDisposer(); this._dropDisposer && this._dropDisposer(); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } - stopPropagation = (e: React.SyntheticEvent) => { - e.stopPropagation(); - } - get dataDoc() { - if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) { - // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document - // has a template layout document, then we will render the template layout but use - // this document as the data document for the layout. - return this.props.Document; - } + // bcz: don't think we need this, but left it in in case strange behavior pops up. DocumentContentsView has this functionality + // if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) { + // // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document + // // has a template layout document, then we will render the template layout but use + // // this document as the data document for the layout. + // return this.props.Document; + // } return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; } - startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, applyAsTemplate?: boolean) { + startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { if (this._mainCont.current) { - let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; - let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); - let dragData = new DragManager.DocumentDragData(allConnected, alldataConnected); + let dragData = new DragManager.DocumentDragData([this.props.Document]); const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.dropAction = dropAction; dragData.xOffset = xoff; @@ -256,47 +205,54 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); } } - toggleMinimized = async () => { - let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc); - if (minimizedDoc) { - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint( - NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y)); - this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); - } - } - static _undoBatch?: UndoManager.Batch = undefined; @action public collapseTargetsToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { SelectionManager.DeselectAll(); if (expandedDocs) { - if (!DocumentView._undoBatch) { - DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); - } let isMinimized: boolean | undefined; expandedDocs.map(maximizedDoc => { - let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); - if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { - if (isMinimized === undefined) { - isMinimized = BoolCast(maximizedDoc.isMinimized); + if (isMinimized === undefined) { + isMinimized = BoolCast(maximizedDoc.isMinimized); + } + let w = NumCast(maximizedDoc.width); + let h = NumCast(maximizedDoc.height); + let iconAnimating = maximizedDoc.isIconAnimating ? Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!) : undefined; + if (isMinimized || (iconAnimating && iconAnimating.length && iconAnimating[0] === 0)) { + // MAXIMIZE DOC + if (maximizedDoc.isMinimized) { + maximizedDoc.isIconAnimating = new List<number>([0, 0]); + maximizedDoc.isMinimized = false; } - maximizedDoc.willMaximize = isMinimized; - maximizedDoc.isMinimized = false; - maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); + maximizedDoc.iconTarget = new List<number>([...scrpt, 1]); + setTimeout(() => { + maximizedDoc.isIconAnimating = new List<number>([w, h]); + setTimeout(() => { + if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] !== 0) { + maximizedDoc.isIconAnimating = undefined; + } + }, 750); + }, 0); + } else { + maximizedDoc.iconTarget = new List<number>([...scrpt, 0]); + // MINIMIZE DOC + maximizedDoc.isIconAnimating = new List<number>([0, 0]); + setTimeout(() => { + if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] === 0) { + maximizedDoc.isMinimized = true; + maximizedDoc.isIconAnimating = undefined; + } + }, 750); } }); - setTimeout(() => { - DocumentView._undoBatch && DocumentView._undoBatch.end(); - DocumentView._undoBatch = undefined; - }, 500); } } onClick = async (e: React.MouseEvent) => { - if (e.nativeEvent.cancelBubble) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing. + if (e.nativeEvent.cancelBubble) return; // || SelectionManager.IsSelected(this)) -- bcz: needed because EditableView may stopPropagation which won't apparently stop this event from firing. if (this.onClickHandler && this.onClickHandler.script) { e.stopPropagation(); - this.onClickHandler.script.run({ this: this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }); + this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }); e.preventDefault(); return; } @@ -305,41 +261,34 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (this._doubleTap && this.props.renderDepth) { e.stopPropagation(); let fullScreenAlias = Doc.MakeAlias(this.props.Document); - fullScreenAlias.templates = new List<string>(); Doc.UseDetailLayout(fullScreenAlias); - fullScreenAlias.showCaption = true; + fullScreenAlias.showCaption = "caption"; this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } - else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] && + else if (!this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { - if (BoolCast(this.props.Document.ignoreClick)) { - return; - } e.stopPropagation(); SelectionManager.SelectDoc(this, e.ctrlKey); - let isExpander = (e.target as any).id === "isExpander"; - if (BoolCast(this.props.Document.isButton) || this.props.Document.type === DocumentType.BUTTON || isExpander) { - let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs); + if (this.Document.isButton || this.Document.type === DocumentType.BUTTON) { let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); let expandedDocs: Doc[] = []; - expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs; expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; - // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; - if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents + // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; + if (expandedDocs.length) { SelectionManager.DeselectAll(); - let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); + let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); if (altKey || ctrlKey) { - maxLocation = this.props.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace")); + maxLocation = this.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace")); if (!maxLocation || maxLocation === "inPlace") { let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView); - let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized), false); + let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !d.isMinimized, false); expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false); let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView); if (!hasView) { @@ -362,7 +311,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } else if (linkedDocs.length) { SelectionManager.DeselectAll(); - let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document)); + let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); + let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + if (firstUnshown.length) first = [firstUnshown[0]]; let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; // @TODO: shouldn't always follow target context @@ -399,7 +350,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (e.nativeEvent.cancelBubble) return; this._downX = e.clientX; this._downY = e.clientY; - this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0; this._hitTemplateDrag = false; for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) { if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") { @@ -417,11 +367,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu document.removeEventListener("pointermove", this.onPointerMove); } else if (!e.cancelBubble && this.active) { - if (!this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) { - if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) { + if (!this.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) { + if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, this._hitTemplateDrag); + this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); } } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers @@ -439,32 +389,70 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } @undoBatch - fieldsClicked = (): void => { - let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); - this.props.addDocTab(kvp, this.dataDoc, "onRight"); + makeNativeViewClicked = (): void => { + makeNativeView(this.props.Document); + } + @undoBatch + makeCustomViewClicked = (): void => { + this.props.Document.nativeLayout = this.Document.layout; + this.props.Document.nativeType = this.Document.type; + this.props.Document.nonCustomAutoHeight = this.Document.autoHeight; + this.props.Document.nonCustomWidth = this.Document.width; + this.props.Document.nonCustomHeight = this.Document.height; + this.props.Document.nonCustomNativeWidth = this.Document.nativeWidth; + this.props.Document.nonCustomNativeHeight = this.Document.nativeHeight; + this.props.Document.nonCustomIgnoreAspect = this.Document.ignoreAspect; + PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => { + if (custom) { + this.Document.type = DocumentType.TEMPLATE; + this.props.Document.layout = custom; + !custom.nativeWidth && (this.Document.nativeWidth = 0); + !custom.nativeHeight && (this.Document.nativeHeight = 0); + !custom.nativeWidth && (this.Document.ignoreAspect = true); + this.Document.autoHeight = BoolCast(this.Document.customAutoHeight); + this.Document.width = NumCast(this.props.Document.customWidth); + this.Document.height = NumCast(this.props.Document.customHeight); + this.Document.nativeWidth = NumCast(this.props.Document.customNativeWidth); + this.Document.nativeHeight = NumCast(this.props.Document.customNativeHeight); + this.Document.ignoreAspect = BoolCast(this.Document.customIgnoreAspect); + this.props.Document.customAutoHeight = undefined; + this.props.Document.customWidth = undefined; + this.props.Document.customHeight = undefined; + this.props.Document.customNativeWidth = undefined; + this.props.Document.customNativeHeight = undefined; + this.props.Document.customIgnoreAspect = undefined; + } else { + let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; + let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : + this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : + Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + + fieldTemplate.backgroundColor = this.Document.backgroundColor; + fieldTemplate.heading = 1; + fieldTemplate.autoHeight = true; + + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: this.Document.title + "layout", width: (this.Document.width || 0) + 20, height: Math.max(100, (this.Document.height || 0) + 45) }); + let proto = Doc.GetProto(docTemplate); + Doc.MakeMetadataFieldTemplate(fieldTemplate, proto, true); + + Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); + Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.Document.layout; + } + }); } @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); - doc.isButton = !BoolCast(doc.isButton); - if (doc.isButton) { - if (!doc.nativeWidth) { - doc.nativeWidth = this.props.Document[WidthSym](); - doc.nativeHeight = this.props.Document[HeightSym](); - } + if (doc.isButton || doc.onClick) { + doc.isButton = false; + doc.onClick = undefined; } else { - doc.nativeWidth = doc.nativeHeight = undefined; + doc.isButton = true; } } @undoBatch - public fullScreenClicked = (): void => { - CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this); - SelectionManager.DeselectAll(); - } - - @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.AnnotationDragData) { @@ -519,50 +507,57 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } + @undoBatch @action - addTemplate = (template: Template) => { - this.templates.push(template.Layout); - this.templates = this.templates; - } - - @action - removeTemplate = (template: Template) => { - for (let i = 0; i < this.templates.length; i++) { - if (this.templates[i] === template.Layout) { - this.templates.splice(i, 1); - break; - } + freezeNativeDimensions = (): void => { + let proto = this.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document); + proto.autoHeight = this.Document.autoHeight = false; + proto.ignoreAspect = !proto.ignoreAspect; + if (!proto.ignoreAspect && !proto.nativeWidth) { + proto.nativeWidth = this.props.PanelWidth(); + proto.nativeHeight = this.props.PanelHeight(); } - this.templates = this.templates; } + @undoBatch @action - clearTemplates = () => { - this.templates.length = 0; - this.templates = this.templates; + makeIntoPortal = (): void => { + if (!DocListCast(this.props.Document.links).find(doc => { + if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.Document.title + ".portal") return true; + return false; + })) { + let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); + DocServer.GetRefField(portalID).then(existingPortal => { + let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID }); + DocUtils.MakeLink(this.props.Document, portal, undefined, portalID); + Doc.GetProto(this.props.Document).isButton = true; + }) + } } - @undoBatch @action - freezeNativeDimensions = (): void => { - let proto = this.props.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document); - this.props.Document.autoHeight = proto.autoHeight = false; - proto.ignoreAspect = !BoolCast(proto.ignoreAspect); - if (!BoolCast(proto.ignoreAspect) && !proto.nativeWidth) { - proto.nativeWidth = this.props.PanelWidth(); - proto.nativeHeight = this.props.PanelHeight(); + toggleCustomView = (): void => { + if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { + Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc) + } else { + if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { + this.makeCustomViewClicked(); + } else if (this.Document.nativeLayout) { + this.makeNativeViewClicked(); + } } } + @undoBatch @action makeBackground = (): void => { - this.props.Document.isBackground = !this.props.Document.isBackground; - this.props.Document.isBackground && this.props.bringToFront(this.props.Document, true); + this.layoutDoc.isBackground = !this.layoutDoc.isBackground; + this.layoutDoc.isBackground && this.props.bringToFront(this.layoutDoc, true); } @undoBatch @action toggleLockPosition = (): void => { - this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; + this.layoutDoc.lockedPosition = BoolCast(this.layoutDoc.lockedPosition) ? undefined : true; } listen = async () => { @@ -590,45 +585,60 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const cm = ContextMenu.Instance; let subitems: ContextMenuProps[] = []; - subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" }); - subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.dataDoc, "inTab"), icon: "folder" }); - subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "inTab"), icon: "folder" }); - subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.dataDoc, "onRight"), icon: "caret-square-right" }); - subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" }); - subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); + subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this), icon: "desktop" }); + subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.dataDoc, "inTab"), icon: "folder" }); + subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.dataDoc, "onRight"), icon: "caret-square-right" }); + subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "inTab"), icon: "folder" }); + subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" }); + subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); - let existingMake = ContextMenu.Instance.findByDescription("Make..."); - let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; - makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); - makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); - makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); - makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); - makes.push({ - description: "Into Portal", event: () => { - if (!DocListCast(this.props.Document.links).find(doc => { - if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; - return false; - })) { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - Doc.GetProto(this.props.Document).isButton = true; + + let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); + onClicks.push({ + description: "Toggle Detail", event: () => { + let compiled = CompileScript("toggleDetail(this)", { + params: { this: "Doc" }, + typecheck: false, + editable: true, + }); + if (compiled.compiled) { + this.Document.onClick = new ScriptField(compiled); } }, icon: "window-restore" }); - makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" }); - !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); + onClicks.push({ description: this.layoutDoc.ignoreClick ? "Select" : "Do Nothing", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); + onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" }); + onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); + onClicks.push({ + description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => { + this.props.Document.collectionContext = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; + ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n"); + } + }); + !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; - - layoutItems.push({ description: `${this.props.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.props.Document.chromeStatus = (this.props.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.props.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.props.Document.autoHeight = !this.props.Document.autoHeight, icon: "plus" }); - layoutItems.push({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); - layoutItems.push({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" }); + if (this.props.DataDoc) { + layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }) + } + layoutItems.push({ description: `${this.layoutDoc.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.layoutDoc.chromeStatus = (this.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); + layoutItems.push({ description: `${this.layoutDoc.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); + layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); + layoutItems.push({ description: this.layoutDoc.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.layoutDoc.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); - if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) { + if (this.props.Document.detailedLayout && !this.Document.isTemplate) { layoutItems.push({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" }); } + if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { + layoutItems.push({ description: "Use Custom Layout", event: this.makeCustomViewClicked, icon: "concierge-bell" }); + } else if (this.props.Document.nativeLayout) { + layoutItems.push({ description: "Use Native Layout", event: this.makeNativeViewClicked, icon: "concierge-bell" }); + } !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); if (!ClientUtils.RELEASE) { let copies: ContextMenuProps[] = []; @@ -642,17 +652,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); - cm.addItem({ description: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) }); cm.addItem({ - description: "Download document", icon: "download", event: () => { - const a = document.createElement("a"); - const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - a.href = url; - a.download = `DocExport-${this.props.Document[Id]}.zip`; - a.click(); + description: "Download document", icon: "download", event: async () => { + let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { + qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } + })); + console.log(y); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); } }); + cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; @@ -732,7 +746,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu select={this.select} onClick={this.onClickHandler} layoutKey={"layout"} - fitToBox={BoolCast(this.props.Document.fitToBox) ? true : this.props.fitToBox} + fitToBox={this.Document.fitToBox ? true : this.props.fitToBox} DataDoc={this.dataDoc} />); } @@ -748,45 +762,68 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return (showTitle ? 25 : 0) + 1;// bcz: why 8?? } - get layoutDoc() { + get layoutDoc(): Document { // if this document's layout field contains a document (ie, a rendering template), then we will use that // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. - return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document); } - render() { - let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ? + const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; + const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; + const colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor; + const clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground; + const backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ? this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : - StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); - let foregroundColor = StrCast(this.layoutDoc.color); - var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%"; - var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; - let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; - let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - let showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : StrCast(this.layoutDoc.showCaption); - let templates = Cast(this.layoutDoc.templates, listSpec("string")); - if (!showOverlays && templates instanceof List) { - templates.map(str => { - if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title"; - if (!showCaption && str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption"; - }); - } - let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined; - let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); - let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding); - let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); + + const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; + const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; + const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; + const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.layoutDoc.showTitle; + const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.layoutDoc.showCaption; + const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; + const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); + const borderRounding = this.Document.borderRounding || ruleRounding; + const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + const iconAnimating = this.Document.isIconAnimating ? Array.from(Cast(this.Document.isIconAnimating, listSpec("number"))!) : undefined; + const searchHighlight = (!this.Document.searchFields ? (null) : + <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> + {this.Document.searchFields} + </div>); + const captionView = (!showCaption ? (null) : + <div className="documentView-captionWrapper" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> + <FormattedTextBox {...this.props} + onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} + isSelected={this.isSelected} focus={emptyFunction} select={this.select} + fieldExt={""} hideOnLeave={true} fieldKey={showCaption} + /> + </div>); + const titleView = (!showTitle ? (null) : + <div className="documentView-titleWrapper" style={{ + position: showTextTitle ? "relative" : "absolute", + pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", + width: `${100 * this.props.ContentScaling()}%`, + transform: `scale(${1 / this.props.ContentScaling()})` + }}> + <EditableView + contents={(this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle]} + display={"block"} height={72} fontSize={12} + GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle])} + SetValue={(value: string) => ((this.layoutDoc.isTemplate ? this.layoutDoc : Doc.GetProto(this.layoutDoc))[showTitle] = value) ? true : true} + /> + </div>); return ( <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} style={{ + transition: iconAnimating ? "transform .5s" : StrCast(this.layoutDoc.transition), pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all", - color: foregroundColor, + color: StrCast(this.layoutDoc.color), outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree], outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree], outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px", border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined, - borderRadius: "inherit", background: backgroundColor, width: nativeWidth, height: nativeHeight, @@ -796,37 +833,89 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > - {!showTitle && !showCaption ? this.contents : - <div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}> - - <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 29px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}> + {!showTitle && !showCaption ? + this.Document.searchFields ? + (<div className="documentView-searchWrapper"> + {this.contents} + {searchHighlight} + </div>) + : + this.contents + : + <div className="documentView-styleWrapper" > + <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? "calc(100% - 29px)" : "100%", top: showTextTitle ? "29px" : undefined }}> {this.contents} </div> - {!showTitle ? (null) : - <div style={{ - position: showTextTitle ? "relative" : "absolute", top: 0, padding: "4px", textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre", - pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", - overflow: "hidden", width: `${100 * this.props.ContentScaling()}%`, height: 25, background: "rgba(0, 0, 0, .4)", color: "white", - transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})` - }}> - <EditableView - contents={(this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle]} - display={"block"} - height={72} - fontSize={12} - GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle!])} - SetValue={(value: string) => ((this.layoutDoc.isTemplate ? this.layoutDoc : Doc.GetProto(this.layoutDoc))[showTitle!] = value) ? true : true} - /> - </div> - } - {!showCaption ? (null) : - <div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> - <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} /> - </div> - } + {titleView} + {captionView} + {searchHighlight} </div> } </div> ); } -}
\ No newline at end of file +} + + +let makeNativeView = (doc: any): void => { + doc.layout = doc.nativeLayout; + doc.nativeLayout = undefined; + doc.type = doc.nativeType; + + doc.customAutoHeight = doc.autoHeight; + doc.customWidth = doc.width; + doc.customHeight = doc.height; + doc.customNativeWidth = doc.nativeWidth; + doc.customNativeHeight = doc.nativeHeight; + doc.customIgnoreAspect = doc.ignoreAspect; + + doc.autoHeight = doc.nonCustomAutoHeight; + doc.width = doc.nonCustomWidth; + doc.height = doc.nonCustomHeight; + doc.nativeWidth = doc.nonCustomNativeWidth; + doc.nativeHeight = doc.nonCustomNativeHeight; + doc.ignoreAspect = doc.nonCustomIgnoreAspect; + doc.nonCustomAutoHeight = undefined; + doc.nonCustomWidth = undefined; + doc.nonCustomHeight = undefined; + doc.nonCustomNativeWidth = undefined; + doc.nonCustomNativeHeight = undefined; + doc.nonCustomIgnoreAspect = undefined; +} +let makeCustomView = (doc: any): void => { + doc.nativeLayout = doc.layout; + doc.nativeType = doc.type; + doc.nonCustomAutoHeight = doc.autoHeight; + doc.nonCustomWidth = doc.nativeWidth; + doc.nonCustomHeight = doc.nativeHeight; + doc.nonCustomNativeWidth = doc.nativeWidth; + doc.nonCustomNativeHeight = doc.nativeHeight; + doc.nonCustomIgnoreAspect = doc.ignoreAspect; + let custom = doc.customLayout as Doc; + if (custom instanceof Doc) { + doc.type = DocumentType.TEMPLATE; + doc.layout = custom; + !custom.nativeWidth && (doc.nativeWidth = 0); + !custom.nativeHeight && (doc.nativeHeight = 0); + !custom.nativeWidth && (doc.ignoreAspect = true); + doc.autoHeight = doc.autoHeight; + doc.width = doc.customWidth; + doc.height = doc.customHeight; + doc.nativeWidth = doc.customNativeWidth; + doc.nativeHeight = doc.customNativeHeight; + doc.ignoreAspect = doc.ignoreAspect; + doc.customAutoHeight = undefined; + doc.customWidth = undefined; + doc.customHeight = undefined; + doc.customNativeWidth = undefined; + doc.customNativeHeight = undefined; + doc.customIgnoreAspect = undefined; + } +} +Scripting.addGlobal(function toggleDetail(doc: any) { + if (doc.type !== DocumentType.COL && doc.type !== DocumentType.TEMPLATE) { + makeCustomView(doc); + } else if (doc.nativeLayout) { + makeNativeView(doc); + } +});
\ No newline at end of file diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx index 1f2c88086..067d47de4 100644 --- a/src/client/views/nodes/DragBox.tsx +++ b/src/client/views/nodes/DragBox.tsx @@ -55,7 +55,7 @@ export class DragBox extends DocComponent<FieldViewProps, DragDocument>(DragDocu let doc = res !== undefined && res.success ? res.result as Doc : Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" }); - doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc], [undefined]), e.clientX, e.clientY); + doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); } e.stopPropagation(); e.preventDefault(); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index d9774303b..943d181d6 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -30,6 +30,7 @@ export interface FieldViewProps { leaveNativeSize?: boolean; fitToBox?: boolean; ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; + ruleProvider: Doc | undefined; Document: Doc; DataDoc?: Doc; onClick?: ScriptField; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index d7ac7a9c5..0d7277cbe 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -4,7 +4,6 @@ width: 100%; height: 100%; min-height: 100%; - font-family: $serif; } .ProseMirror:focus { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9abc7b329..a3cfe10fc 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -39,7 +39,6 @@ import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; -import { select } from 'async'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -79,15 +78,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _linkClicked = ""; private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; - private _reactionDisposer: Opt<IReactionDisposer>; private _searchReactionDisposer?: Lambda; + private _reactionDisposer: Opt<IReactionDisposer>; private _textReactionDisposer: Opt<IReactionDisposer>; private _heightReactionDisposer: Opt<IReactionDisposer>; + private _rulesReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; private _pullReactionDisposer: Opt<IReactionDisposer>; private _pushReactionDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; + @observable private _fontSize = 13; + @observable private _fontFamily = "Arial"; + @observable private _fontAlign = ""; @observable private _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; public static SelectOnLoad = ""; @@ -119,14 +122,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch public setFontColor(color: string) { - this._editorView!.state.storedMarks; - if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false; - if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) { + let view = this._editorView!; + if (view.state.selection.from === view.state.selection.to) return false; + if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { this.props.Document.color = color; } - let colorMark = this._editorView!.state.schema.mark(this._editorView!.state.schema.marks.pFontColor, { color: color }); - this._editorView!.dispatch(this._editorView!.state.tr.addMark(this._editorView!.state.selection.from, - this._editorView!.state.selection.to, colorMark)); + let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); + view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); return true; } @@ -230,6 +232,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + linkOnDeselect: Map<string, string> = new Map(); + + doLinkOnDeselect() { + Array.from(this.linkOnDeselect.entries()).map(entry => { + let key = entry[0]; + let value = entry[1]; + let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + DocServer.GetRefField(value).then(doc => { + DocServer.GetRefField(id).then(linkDoc => { + this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); + if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } + else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); + }) + }); + }) + this.linkOnDeselect.clear(); + } + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); @@ -244,15 +265,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (split.length > 1 && split[1]) { let key = split[0]; let value = split[split.length - 1]; + this.linkOnDeselect.set(key, value); let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); - if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id); - }) - }); const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); const mval = this._editorView!.state.schema.marks.metadataVal.create(); let offset = (tx.selection.to === range!.end - 1 ? -1 : 0); @@ -316,12 +331,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } }); - // const fieldkey = 'search_string'; - // if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) { - // this.props.Document[fieldkey] = undefined; - // } - // else this.props.Document.proto![fieldkey] = undefined; - // } } } setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { @@ -351,16 +360,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; - if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") { - this.props.Document.layout = draggedDoc; - draggedDoc.isTemplate = true; + if (draggedDoc && draggedDoc.type === DocumentType.TEXT) { + if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) { + draggedDoc.isTemplate = true; + if (typeof (draggedDoc.layout) === "string") { + let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); + layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); + this.props.Document.layout = layoutDelegateToOverrideFieldKey; + } else { + this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + } + } e.stopPropagation(); } } } recordKeyHandler = (e: KeyboardEvent) => { - if (this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { + if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { if (e.key === "R" && e.altKey) { e.stopPropagation(); e.preventDefault(); @@ -439,6 +456,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentDidMount() { + if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { @@ -521,6 +539,39 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + + + this._rulesReactionDisposer = reaction(() => { + let ruleProvider = this.props.ruleProvider; + let heading = NumCast(this.props.Document.heading); + if (ruleProvider instanceof Doc) { + return { + align: StrCast(ruleProvider["ruleAlign_" + heading], ""), + font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"), + size: NumCast(ruleProvider["ruleSize_" + heading], 13) + }; + } + return undefined; + }, + action((rules: any) => { + this._fontFamily = rules ? rules.font : "Arial"; + this._fontSize = rules ? rules.size : 13; + rules && setTimeout(() => { + const view = this._editorView!; + if (this._proseRef) { + let n = new NodeSelection(view.state.doc.resolve(0)); + if (this._editorView!.state.doc.textContent === "") { + view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))). + replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true)); + } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) { + view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align })); + } + this.tryUpdateHeight(); + } + }, 0); + }), { fireImmediately: true } + ); + setTimeout(() => this.tryUpdateHeight(), 0); } @@ -728,27 +779,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe else if (this.props.isOverlay) 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. this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - let heading = this.props.Document.heading; - if (heading && selectOnLoad) { - PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { - if (ruleProvider) { - let align = StrCast(ruleProvider["ruleAlign_" + heading]); - let font = StrCast(ruleProvider["ruleFont_" + heading]); - let size = NumCast(ruleProvider["ruleSize_" + heading]); - if (align) { - let tr = this._editorView!.state.tr; - tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). - replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: align }), true). - setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); - this._editorView!.dispatch(tr); - } - let sm = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - size && (sm = [...sm, schema.marks.pFontSize.create({ fontSize: size })]); - font && (sm = [...sm, this.getFont(font)]); - this._editorView!.dispatch(this._editorView!.state.tr.setStoredMarks(sm)); - } - }); - } } getFont(font: string) { switch (font) { @@ -764,6 +794,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { + this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._textReactionDisposer && this._textReactionDisposer(); @@ -921,6 +952,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping.end(); this._undoTyping = undefined; } + this.doLinkOnDeselect(); } onKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Escape") { @@ -930,7 +962,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); @@ -949,12 +981,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - render() { let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground || - (this.props.Document.isButton && !this.props.isSelected()) ? "none" : "all"; + let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground + //|| (this.props.Document.isButton && !this.props.isSelected()) + ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return ( <div className={`formattedTextBox-cont-${style}`} ref={this._ref} @@ -965,7 +997,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive, - fontSize: "13px" + fontSize: this._fontSize, + fontFamily: this._fontFamily, }} onKeyDown={this.onKeyPress} onFocus={this.onFocused} @@ -978,7 +1011,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > - <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap" }} /> + <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: (this.props.Document.isButton && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} /> </div> ); } diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 7e78ec684..92cb5a9c9 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -12,6 +12,8 @@ import { IconField } from "../../../new_fields/IconField"; import { ContextMenu } from "../ContextMenu"; import Measure from "react-measure"; import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss"; +import { Scripting, CompileScript } from "../../util/Scripting"; +import { ComputedField } from "../../../new_fields/ScriptField"; library.add(faCaretUp); @@ -27,6 +29,26 @@ export class IconBox extends React.Component<FieldViewProps> { @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; } @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } + public static summaryTitleScript(inputDoc: Doc) { + const sumDoc = Cast(inputDoc.summaryDoc, Doc) as Doc; + if (sumDoc && StrCast(sumDoc.title).startsWith("-")) { + return sumDoc.title + ".expanded"; + } + return "???"; + } + public static titleScript(inputDoc: Doc) { + const maxDoc = DocListCast(inputDoc.maximizedDocs); + if (maxDoc.length === 1) { + return maxDoc[0].title + ".icon"; + } + return maxDoc.length > 1 ? "-multiple-.icon" : "???"; + } + + public static AutomaticTitle(doc: Doc) { + let computed = CompileScript(`return iconTitle(this);`, { params: { this: "Doc" }, typecheck: false }); + computed.compiled && (Doc.GetProto(doc).title = new ComputedField(computed)); + } + public static DocumentIcon(layout: string) { let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : layout.indexOf("ImageBox") !== -1 ? faImage : @@ -38,35 +60,20 @@ export class IconBox extends React.Component<FieldViewProps> { } setLabelField = (): void => { - this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel); - } - setUseOwnTitleField = (): void => { - this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle); + this.props.Document.hideLabel = !this.props.Document.hideLabel; } specificContextMenu = (): void => { - ContextMenu.Instance.addItem({ - description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon", - event: this.setLabelField, - icon: "tag" - }); - let maxDocs = DocListCast(this.props.Document.maximizedDocs); - if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) { - ContextMenu.Instance.addItem({ - description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label", - event: this.setUseOwnTitleField, - icon: "text-height" - }); + let cm = ContextMenu.Instance; + cm.addItem({ description: this.props.Document.hideLabel ? "Show label with icon" : "Remove label from icon", event: this.setLabelField, icon: "tag" }); + if (!this.props.Document.hideLabel) { + cm.addItem({ description: "Use Target Title", event: () => IconBox.AutomaticTitle(this.props.Document), icon: "text-height" }); } } @observable _panelWidth: number = 0; @observable _panelHeight: number = 0; render() { - let labelField = StrCast(this.props.Document.labelField); - let hideLabel = BoolCast(this.props.Document.hideLabel); - let maxDocs = DocListCast(this.props.Document.maximizedDocs); - let firstDoc = maxDocs.length ? maxDocs[0] : undefined; - let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle) ? firstDoc[labelField] : this.props.Document.title); + let label = this.props.Document.hideLabel ? "" : this.props.Document.title; return ( <div className="iconBox-container" onContextMenu={this.specificContextMenu}> {this.minimizedIcon} @@ -82,4 +89,6 @@ export class IconBox extends React.Component<FieldViewProps> { </Measure> </div>); } -}
\ No newline at end of file +} +Scripting.addGlobal(function iconTitle(doc: any) { return IconBox.titleScript(doc); }); +Scripting.addGlobal(function summaryTitle(doc: any) { return IconBox.summaryTitleScript(doc); });
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 6fc94a140..beccce9dd 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -216,12 +216,13 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD funcs.push({ description: "Record 1sec audio", event: this.recordAudioAnnotation, icon: "expand-arrows-alt" }); funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); - let modes: ContextMenuProps[] = []; + let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); + let modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); + !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }) ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" }); - ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" }); } } @@ -261,13 +262,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD onDotDown(index: number) { this.Document.curPage = index; } - - @computed get fieldExtensionDoc() { - return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); - } - @computed private get url() { - let data = Cast(Doc.GetProto(this.props.Document).data, ImageField); + let data = Cast(Doc.GetProto(this.props.Document)[this.props.fieldKey], ImageField); return data ? data.url.href : undefined; } @@ -380,7 +376,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); // let w = bptX - sptX; - let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx let nativeWidth = FieldValue(this.Document.nativeWidth, pw); let nativeHeight = FieldValue(this.Document.nativeHeight, 0); let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; @@ -406,11 +401,11 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD if (!this.props.Document.ignoreAspect && !this.props.leaveNativeSize) this.resize(srcpath, this.props.Document); return ( - <div id={id} className={`imageBox-cont${interactive}`} style={{ background: "transparent" }} + <div className={`imageBox-cont${interactive}`} style={{ background: "transparent" }} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <div id="cf"> - <img id={id} + <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys src={srcpath} style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }} diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 653c5c27f..ee70942de 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -2,26 +2,21 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { CompileScript, ScriptOptions, CompiledScript } from "../../util/Scripting"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./KeyValueBox.scss"; -import { KeyValuePair } from "./KeyValuePair"; -import React = require("react"); -import { NumCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { Doc, Field, FieldResult, DocListCastAsync } from "../../../new_fields/Doc"; -import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; -import { SetupDrag } from "../../util/DragManager"; -import { Docs } from "../../documents/Documents"; -import { RawDataOperationParameters } from "../../northstar/model/idea/idea"; -import { Templates } from "../Templates"; +import { Doc, Field, FieldResult } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; -import { TextField } from "../../util/ProsemirrorCopy/prompt"; import { RichTextField } from "../../../new_fields/RichTextField"; -import { ImageField } from "../../../new_fields/URLField"; -import { SelectionManager } from "../../util/SelectionManager"; import { listSpec } from "../../../new_fields/Schema"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { ImageField } from "../../../new_fields/URLField"; +import { Docs } from "../../documents/Documents"; +import { SetupDrag } from "../../util/DragManager"; +import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting"; import { undoBatch } from "../../util/UndoManager"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./KeyValueBox.scss"; +import { KeyValuePair } from "./KeyValuePair"; +import React = require("react"); export type KVPScript = { script: CompiledScript; @@ -203,7 +198,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { return; } let previousViewType = fieldTemplate.viewType; - Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc)); + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc)); previousViewType && (fieldTemplate.viewType = previousViewType); Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5afd4d834..7e0f3735d 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -55,6 +55,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { Document: this.props.doc, DataDoc: this.props.doc, ContainingCollectionView: undefined, + ruleProvider: undefined, fieldKey: this.props.keyName, fieldExt: "", isSelected: returnFalse, @@ -112,7 +113,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { <div className="keyValuePair-td-value-container"> <EditableView contents={contents} - height={36} + maxHeight={36} + height={"auto"} GetValue={() => { return Field.toKeyValueString(props.Document, props.fieldKey); }} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 3f4ee8960..96f011eff 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { InkTool } from "../../../new_fields/InkField"; import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from "../../../Utils"; @@ -204,7 +204,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } } specificContextMenu = (e: React.MouseEvent): void => { - let field = Cast(this.Document[this.props.fieldKey], VideoField); + let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); if (field) { let url = field.url.href; let subitems: ContextMenuProps[] = []; @@ -216,7 +216,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } @computed get content() { - let field = Cast(this.Document[this.props.fieldKey], VideoField); + let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ? <div>Loading</div> : @@ -228,7 +228,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } @computed get youtubeVideoId() { - let field = Cast(this.Document[this.props.fieldKey], VideoField); + let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; } @@ -269,6 +269,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } + @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + @computed get youtubeContent() { this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; @@ -281,6 +283,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } render() { + Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}> {this.youtubeVideoId ? this.youtubeContent : this.content} </div>; diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 856e883e7..533247170 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -137,7 +137,7 @@ export default class Page extends React.Component<IPageProps> { view.nativeWidth = this.props.Document.nativeWidth; view.startY = marquee.top + this.props.getScrollFromPage(this.props.page); view.width = this.props.Document[WidthSym](); - DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view], [undefined]), 0, 0); + DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0); } @action diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 80aa25f48..7be44faf6 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -351,6 +351,7 @@ export default class PresentationElement extends React.Component<PresentationEle Document={this.props.document} addDocument={returnFalse} removeDocument={returnFalse} + ruleProvider={undefined} ScreenToLocalTransform={Transform.Identity} addDocTab={returnFalse} pinToPres={returnFalse} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2ad69daca..0d50124dd 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,25 +1,23 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action, runInAction, flow, computed } from 'mobx'; -import "./SearchBox.scss"; -import "./FilterBox.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { SetupDrag } from '../../util/DragManager'; -import { Docs } from '../../documents/Documents'; -import { NumCast, Cast } from '../../../new_fields/Types'; -import { Doc } from '../../../new_fields/Doc'; -import { SearchItem } from './SearchItem'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import * as rp from 'request-promise'; +import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { SearchUtil } from '../../util/SearchUtil'; +import { Cast, NumCast } from '../../../new_fields/Types'; import { RouteStore } from '../../../server/RouteStore'; -import { FilterBox } from './FilterBox'; -import { ReadStream } from 'fs'; -import * as $ from 'jquery'; -import { MainView } from '../MainView'; import { Utils } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { SetupDrag } from '../../util/DragManager'; +import { SearchUtil } from '../../util/SearchUtil'; +import { MainView } from '../MainView'; +import { FilterBox } from './FilterBox'; +import "./FilterBox.scss"; +import "./SearchBox.scss"; +import { SearchItem } from './SearchItem'; library.add(faTimes); @@ -141,7 +139,7 @@ export class SearchBox extends React.Component { private get filterQuery() { const types = FilterBox.Instance.filterTypes; const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); + return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } @@ -222,7 +220,6 @@ export class SearchBox extends React.Component { doc.width = size; doc.height = size; } - doc.zoomBasis = 1; x += 250; if (x > 1000) { x = 0; @@ -304,14 +301,16 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} highlighting={highlights} />; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} highlighting={highlights} />; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 386b5fe74..510672788 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -28,7 +28,7 @@ import "./SelectorContextMenu.scss"; export interface SearchItemProps { doc: Doc; - query?: string; + query: string; highlighting: string[]; } @@ -71,8 +71,8 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } @@ -128,68 +128,31 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> { export class SearchItem extends React.Component<SearchItemProps> { @observable _selected: boolean = false; - private _previewDoc?: Doc; onClick = () => { // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like DocumentManager.Instance.jumpToDocument(this.props.doc, false); - if (this.props.doc.data instanceof RichTextField) { - this.highlightTextBox(this.props.doc); - } - // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; - highlightTextBox = (doc: Doc) => { - if (this.props.query) { - const fieldkey = 'search_string'; - if (Object.keys(doc).indexOf(fieldkey) === -1) { - doc.search_string = this.props.query; - } - else { - doc.search_string = undefined; - } - - } - } - - fitToBox = () => { - let bounds = Doc.ComputeContentBounds([this.props.doc]); - return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim]; + componentDidMount() { + this.props.doc.search_string = this.props.query; + this.props.doc.search_fields = this.props.highlighting.join(", "); } - componentWillUnmount() { - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } + this.props.doc.search_string = undefined; + this.props.doc.search_fields = undefined; } - //@computed @action public DocumentIcon() { let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { - let renderDoc = this.props.doc; - //let box: number[] = []; - if (layoutresult.indexOf(DocumentType.COL) !== -1) { - renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim]; - } let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); let returnYDimension = () => this._displayDim; - let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - this._previewDoc = newRenderDoc; + let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); const docview = <div onPointerDown={action(() => { this._useIcons = !this._useIcons; @@ -202,6 +165,7 @@ export class SearchItem extends React.Component<SearchItemProps> { Document={this.props.doc} addDocument={returnFalse} removeDocument={returnFalse} + ruleProvider={undefined} ScreenToLocalTransform={Transform.Identity} addDocTab={returnFalse} pinToPres={returnFalse} @@ -219,15 +183,8 @@ export class SearchItem extends React.Component<SearchItemProps> { ContentScaling={scale} /> </div>; - const data = renderDoc.data; - if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - newRenderDoc.preview = true; - newRenderDoc.search_string = this.props.query; return docview; } - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : @@ -279,8 +236,7 @@ export class SearchItem extends React.Component<SearchItemProps> { Doc.BrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element => - Doc.BrushDoc(element.props.Document)); + Doc.BrushDoc(this.props.doc); } } @@ -294,8 +250,7 @@ export class SearchItem extends React.Component<SearchItemProps> { Doc.UnBrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc). - forEach(element => Doc.UnBrushDoc(element.props.Document)); + Doc.UnBrushDoc(this.props.doc); } } @@ -315,7 +270,7 @@ export class SearchItem extends React.Component<SearchItemProps> { onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { e.stopPropagation(); const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; - DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, { + DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, hideSource: false, }); |
