diff options
Diffstat (limited to 'src/client/views')
22 files changed, 229 insertions, 155 deletions
diff --git a/src/client/views/CollectionMulticolumnView.scss b/src/client/views/CollectionMulticolumnView.scss new file mode 100644 index 000000000..84e80da4a --- /dev/null +++ b/src/client/views/CollectionMulticolumnView.scss @@ -0,0 +1,7 @@ +.collectionMulticolumnView_outer, +.collectionMulticolumnView_contents { + width: 100%; + height: 100%; + overflow: hidden; +} + diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx index 94e86c048..3231c0da1 100644 --- a/src/client/views/CollectionMulticolumnView.tsx +++ b/src/client/views/CollectionMulticolumnView.tsx @@ -1,40 +1,73 @@ import { observer } from 'mobx-react'; -import { makeInterface } from '../../new_fields/Schema'; +import { makeInterface, listSpec } from '../../new_fields/Schema'; import { documentSchema } from '../../new_fields/documentSchemas'; -import { CollectionSubView, SubCollectionViewProps } from './collections/CollectionSubView'; +import { CollectionSubView } from './collections/CollectionSubView'; import { DragManager } from '../util/DragManager'; import * as React from "react"; -import { Doc } from '../../new_fields/Doc'; -import { NumCast } from '../../new_fields/Types'; +import { Doc, DocListCast } from '../../new_fields/Doc'; +import { NumCast, Cast, StrCast } from '../../new_fields/Types'; +import { List } from '../../new_fields/List'; +import { ContentFittingDocumentView } from './nodes/ContentFittingDocumentView'; +import { Utils } from '../../Utils'; +import { Transform } from '../util/Transform'; +import "./collectionMulticolumnView.scss"; type MulticolumnDocument = makeInterface<[typeof documentSchema]>; const MulticolumnDocument = makeInterface(documentSchema); @observer export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) { - - constructor(props: Readonly<SubCollectionViewProps>) { - super(props); + private _dropDisposer?: DragManager.DragDropDisposer; + private get configuration() { const { Document } = this.props; - Document.multicolumnData = new Doc(); + if (!Document.multicolumnData) { + Document.multicolumnData = new List<Doc>(); + } + return DocListCast(this.Document.multicolumnData); } - private _dropDisposer?: DragManager.DragDropDisposer; - protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } + getTransform = (ele: React.RefObject<HTMLDivElement>) => () => { + if (!ele.current) return Transform.Identity(); + const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current); + return new Transform(-translateX, -translateY, 1 / scale); + } + public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } render() { + const { PanelWidth } = this.props; return ( <div className={"collectionMulticolumnView_outer"}> <div className={"collectionMulticolumnView_contents"}> - {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(({ layout, data }) => { - + {this.configuration.map(config => { + const { target, columnWidth } = config; + if (target instanceof Doc) { + let computedWidth: number = 0; + const widthSpecifier = Cast(columnWidth, "number"); + let matches: RegExpExecArray | null; + if (widthSpecifier !== undefined) { + computedWidth = widthSpecifier; + } else if ((matches = /([\d.]+)\%/.exec(StrCast(columnWidth))) !== null) { + computedWidth = Number(matches[1]) / 100 * PanelWidth(); + } + return (!computedWidth ? (null) : + <ContentFittingDocumentView + {...this.props} + Document={target} + DataDocument={undefined} + PanelWidth={() => computedWidth} + getTransform={this.props.ScreenToLocalTransform} + /> + ); + } + return (null); })} </div> </div> diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 54def38b5..faf02b946 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -36,7 +36,7 @@ export interface EditableProps { resetValue: () => void; value: string, onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void, - autosuggestProps: Autosuggest.AutosuggestProps<string> + autosuggestProps: Autosuggest.AutosuggestProps<string, any> }; oneLine?: boolean; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 305966160..91c7f909b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -349,7 +349,7 @@ export class MainView extends React.Component { addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string, libraryPath?: Doc[]): boolean => { return where === "close" ? CollectionDockingView.CloseRightSplit(doc) : doc.dockingConfig ? this.openWorkspace(doc) : - CollectionDockingView.AddRightSplit(doc, undefined, undefined, libraryPath); + CollectionDockingView.AddRightSplit(doc, undefined, libraryPath); } mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1); diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index ded2329b4..d24256886 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -82,28 +82,19 @@ export class ScriptBox extends React.Component<ScriptBoxProps> { ); } //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) { + public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); let originalText: string | undefined = undefined; if (script) { originalText = script.script.originalScript; - if (prewrapper && originalText.startsWith(prewrapper)) { - originalText = originalText.substr(prewrapper.length); - } - if (postwrapper && originalText.endsWith(postwrapper)) { - originalText = originalText.substr(0, originalText.length - postwrapper.length); - } } // tslint:disable-next-line: no-unnecessary-callback-wrapper const params: string[] = []; const setParams = (p: string[]) => params.splice(0, params.length, ...p); const scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={overlayDisposer} onSave={(text, onError) => { - if (prewrapper) { - text = prewrapper + text + (postwrapper ? postwrapper : ""); - } const script = CompileScript(text, { - params: { this: Doc.name }, + params: { this: Doc.name, ...contextParams }, typecheck: false, editable: true, transformer: DocumentIconContainer.getTransformer() diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index ef78b60d4..8af8a6280 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -56,8 +56,17 @@ export namespace Templates { <div style="width:100%;overflow:auto">{layout}</div> </div> </div>` ); + export const TitleHover = new Template("TitleHover", TemplatePosition.InnerTop, + `<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">{props.Document.title}</span> + </div> + <div style="height:calc(100% - 25px);"> + <div style="width:100%;overflow:auto">{layout}</div> + </div> + </div>` ); - export const TemplateList: Template[] = [Title, Caption]; + export const TemplateList: Template[] = [Title, TitleHover, Caption]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 151b84c50..6c50ea0f2 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -34,6 +34,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { ComputedField } from '../../../new_fields/ScriptField'; import { InteractionUtils } from '../../util/InteractionUtils'; import { TraceMobx } from '../../../new_fields/util'; +import { Scripting } from '../../util/Scripting'; library.add(faFile); const _global = (window /* browser */ || global /* node */) as any; @@ -177,7 +178,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // @undoBatch @action - public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false, libraryPath?: Doc[]) { + public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) { if (!CollectionDockingView.Instance) return false; const instance = CollectionDockingView.Instance; const newItemStackConfig = { @@ -202,11 +203,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp collayout.config.width = 50; newContentItem.config.width = 50; } - if (minimize) { - // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes - // newContentItem.config.width = 10; - // newContentItem.config.height = 10; - } newContentItem.callDownwards('_$init'); instance.layoutChanged(); return true; @@ -674,7 +670,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { if (doc.dockingConfig) { return MainView.Instance.openWorkspace(doc); } else if (location === "onRight") { - return CollectionDockingView.AddRightSplit(doc, dataDoc, undefined, libraryPath); + return CollectionDockingView.AddRightSplit(doc, dataDoc, libraryPath); } else if (location === "close") { return CollectionDockingView.CloseRightSplit(doc); } else { @@ -724,3 +720,4 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { </div >); } } +Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); }); diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index 0114342b9..92dc8780e 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -286,7 +286,6 @@ class KeysDropdown extends React.Component<KeysDropdownProps> { } @undoBatch - @action onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); @@ -296,7 +295,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> { if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { this.onSelect(this._searchTerm); } else { - this._searchTerm = this._key; + this.setSearchTerm(this._key); } } } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index c1e36272c..886a9c870 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -22,7 +22,6 @@ import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewField import { CollectionSubView } from "./CollectionSubView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { ScriptBox } from "../ScriptBox"; import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; import { TraceMobx } from "../../../new_fields/util"; import { CollectionViewType } from "./CollectionView"; @@ -40,9 +39,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @observable _scroll = 0; // used to force the document decoration to update when scrolling @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); } - @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); } + @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d).layout as Doc) || d); } @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); } - @computed get yMargin() { return Math.max(this.props.Document.showTitle ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); } + @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); } @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } @@ -53,22 +52,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } - childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); } - children(docs: Doc[]) { this._docXfs.length = 0; return docs.map((d, i) => { - const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d); - const layoutDoc = pair.layout ? Doc.Layout(pair.layout) : d; - const width = () => Math.min(layoutDoc.nativeWidth && !layoutDoc.ignoreAspect && !this.props.Document.fillColumn ? layoutDoc[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); - const height = () => this.getDocHeight(layoutDoc); + const width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); + const height = () => this.getDocHeight(d); const dref = React.createRef<HTMLDivElement>(); - const dxf = () => this.getDocTransform(layoutDoc, dref.current!); + const dxf = () => this.getDocTransform(d, dref.current!); this._docXfs.push({ dxf: dxf, width: width, height: height }); const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > - {this.getDisplayDoc(pair.layout || d, pair.data, dxf, width)} + {this.getDisplayDoc(d, (d.resolvedDataDoc as Doc) || d, dxf, width)} </div>; }); } @@ -115,7 +110,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => { const r1 = Math.max(maxHght, (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => { - const val = height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap); + const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap); return val; }, this.yMargin)); return r1; @@ -157,7 +152,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } overlays = (doc: Doc) => { - return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), caption: StrCast(this.props.Document.showCaptions) } : {}; + return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), titleHover: StrCast(this.props.Document.showTitleHovers), caption: StrCast(this.props.Document.showCaptions) } : {}; } @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } @@ -292,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const key = this.sectionFilter; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; - const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); + const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } @@ -317,15 +312,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref); const outerXf = Utils.GetScreenTransform(this._masonryGridRef!); + const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]()); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.props.ScreenToLocalTransform(). - translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0)); + const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2; + const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0); + return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling); } sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const key = this.sectionFilter; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; - const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); + const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } @@ -374,11 +371,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { 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" }); ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" }); - - const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); - const 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" }); } } @@ -402,6 +394,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { <div className="collectionStackingMasonry-cont" > <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"} ref={this.createRef} + style={{ + transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`, + height: `${100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]())}%`, + transformOrigin: "top" + }} onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 062521690..5753dd34e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -85,8 +85,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey]; } - 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 childLayoutPairs(): { layout: Doc; data: Doc; }[] { + const { Document, DataDoc, fieldKey } = this.props; + const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, fieldKey, doc)).filter(pair => pair.layout); + return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types } get childDocList() { return Cast(this.dataField, listSpec(Doc)); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 79fc477ab..3356aed68 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -28,6 +28,7 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { ScriptBox } from '../ScriptBox'; export interface TreeViewProps { @@ -57,6 +58,7 @@ export interface TreeViewProps { hideHeaderFields: () => boolean; preventTreeViewOpen: boolean; renderedIds: string[]; + onCheckedClick?: ScriptField; } library.add(faTrashAlt); @@ -286,7 +288,7 @@ class TreeView extends React.Component<TreeViewProps> { DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, 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.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen, - [...this.props.renderedIds, doc[Id]], this.props.libraryPath); + [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick); } else { contentElement = <EditableView key="editableView" @@ -331,7 +333,7 @@ class TreeView extends React.Component<TreeViewProps> { this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, 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.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen, - [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath)} + [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)} </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}> @@ -366,8 +368,13 @@ class TreeView extends React.Component<TreeViewProps> { @action bulletClick = (e: React.MouseEvent) => { - if (this.props.document.onClick) { - ScriptCast(this.props.document.onClick).script.run({ this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document }, console.log); + if (this.props.onCheckedClick) { + this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check"; + ScriptCast(this.props.onCheckedClick).script.run({ + this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document, + heading: this.props.containingCollection.title, + checked: this.props.document.treeViewChecked === "check" ? false : this.props.document.treeViewChecked === "x" ? "x" : "none" + }, console.log); } else { this.treeViewOpen = !this.treeViewOpen; } @@ -376,8 +383,9 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { - return <div className="bullet" title="view inline" onClick={this.bulletClick} 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")} />} + const checked = this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined; + return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "black"), opacity: 0.4 }}> + {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />} </div>; } /** @@ -457,7 +465,8 @@ class TreeView extends React.Component<TreeViewProps> { hideHeaderFields: () => boolean, preventTreeViewOpen: boolean, renderedIds: string[], - libraryPath: Doc[] | undefined + libraryPath: Doc[] | undefined, + onCheckedClick: ScriptField | undefined ) { const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { @@ -549,6 +558,7 @@ class TreeView extends React.Component<TreeViewProps> { key={child[Id]} indentDocument={indent} outdentDocument={outdent} + onCheckedClick={onCheckedClick} renderDepth={renderDepth} deleteDoc={remove} addDocument={addDocument} @@ -617,6 +627,10 @@ export class CollectionTreeView extends CollectionSubView(Document) { layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" }); ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" }); } + const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean" }) }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); @@ -661,7 +675,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.hideHeaderFields), - BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath) + BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick)) } </ul> </div > diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 88023783b..21371dd39 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -34,6 +34,8 @@ import { FieldViewProps, FieldView } from '../nodes/FieldView'; import { Touchable } from '../Touchable'; import { TraceMobx } from '../../../new_fields/util'; import { Utils } from '../../../Utils'; +import { ScriptBox } from '../ScriptBox'; +import CollectionMulticolumnView from '../CollectionMulticolumnView'; const path = require('path'); library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); @@ -47,7 +49,8 @@ export enum CollectionViewType { Masonry, Pivot, Linear, - Staff + Staff, + Multicolumn } export namespace CollectionViewType { @@ -60,7 +63,8 @@ export namespace CollectionViewType { ["stacking", CollectionViewType.Stacking], ["masonry", CollectionViewType.Masonry], ["pivot", CollectionViewType.Pivot], - ["linear", CollectionViewType.Linear] + ["linear", CollectionViewType.Linear], + ["multicolumn", CollectionViewType.Multicolumn] ]); export const valueOf = (value: string) => stringMapping.get(value.toLowerCase()); @@ -172,6 +176,7 @@ export class CollectionView extends Touchable<FieldViewProps> { case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />); case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />); case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />); + case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />); case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); } case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); } case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); } @@ -213,6 +218,7 @@ export class CollectionView extends Touchable<FieldViewProps> { }, icon: "ellipsis-v" }); subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" }); + subItems.push({ description: "Multicolumn", event: () => this.props.Document.viewType = CollectionViewType.Multicolumn, icon: "columns" }); subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" }); switch (this.props.Document.viewType) { @@ -233,6 +239,11 @@ export class CollectionView extends Touchable<FieldViewProps> { const moreItems = more && "subitems" in more ? more.subitems : []; moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); + + const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + const onClicks = 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/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 184504e5a..996c7671e 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -217,9 +217,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : "true"; - const docFilter = StrCast(this.props.CollectionView.props.Document.docFilter); - const finalScript = docFilter && !fullScript.startsWith("(())") ? `${fullScript} ${docFilter ? "&&" : ""} (${docFilter})` : - docFilter ? docFilter : fullScript; + const docFilter = Cast(this.props.CollectionView.props.Document.docFilter, listSpec("string"), []); + const docFilterText = Doc.MakeDocFilter(docFilter); + const finalScript = docFilterText && !fullScript.startsWith("(())") ? `${fullScript} ${docFilterText ? "&&" : ""} (${docFilterText})` : + docFilterText ? docFilterText : fullScript; this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(finalScript, { doc: Doc.name }); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index a965a6cc9..8c8da63cc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -78,7 +78,7 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo x, y: pivotAxisWidth + 50, width: pivotAxisWidth * expander * numCols, - height: 100, + height: NumCast(pivotDoc.pivotFontSize, 10), fontSize: NumCast(pivotDoc.pivotFontSize, 10) }); for (const doc of val) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 936c4413f..7985e541f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,7 +9,7 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkTool, InkField, InkData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types"; import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; @@ -56,6 +56,8 @@ export const panZoomSchema = createSchema({ useClusters: "boolean", isRuleProvider: "boolean", fitToBox: "boolean", + xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set + yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set panTransformType: "string", scrollHeight: "number", fitX: "number", @@ -82,7 +84,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; } @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; } - @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); } + @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); } @computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; } private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } @@ -710,6 +712,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getScale = () => this.Document.scale || 1; @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; } + @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { @@ -719,7 +722,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { LibraryPath: this.libraryPath, layoutKey: undefined, 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 + //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 + onClick: this.onChildClickHandler, ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, PanelWidth: childLayout[WidthSym], @@ -800,9 +804,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair => computedElementData.elements.push({ - ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider} + ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)} + dataProvider={this.childDataProvider} ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} - jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, + jitterRotation={NumCast(this.props.Document.jitterRotation)} + fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />, bounds: this.childDataProvider(pair.layout) })); @@ -988,8 +994,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { </MarqueeView>; } @computed get contentScaling() { - let hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; - let wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; + const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; + const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; return wscale < hscale ? wscale : hscale; } render() { diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index abd17ec4d..ace9a9e4c 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { document.removeEventListener("pointerup", this.onLinkButtonUp); const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; - DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets); + DragManager.StartLinkTargetsDrag(this._drag.current, e.x, e.y, this.props.sourceDoc, targets); } e.stopPropagation(); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 2e598a14d..614a68e7a 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { random } from "animejs"; +import anime from "animejs"; import { computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; @@ -11,6 +11,8 @@ import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); import { PositionDocument } from "../../../new_fields/documentSchemas"; import { TraceMobx } from "../../../new_fields/util"; +import { returnFalse } from "../../../Utils"; +import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; @@ -20,13 +22,14 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { height?: number; jitterRotation: number; transition?: string; + fitToBox?: boolean; } @observer export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive - get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } + get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; } get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); } @@ -83,8 +86,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @observable _animPos: number[] | undefined = undefined; - finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth(); - finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight(); + finalPanelWidth = () => (this.dataProvider ? this.dataProvider.width : this.panelWidth()); + finalPanelHeight = () => (this.dataProvider ? this.dataProvider.height : this.panelHeight()); render() { TraceMobx(); @@ -104,24 +107,22 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF zIndex: this.Document.zIndex || 0, }} > - <DocumentView {...this.props} + + {!this.props.fitToBox ? <DocumentView {...this.props} dragDivName={"collectionFreeFormDocumentView-container"} ContentScaling={this.contentScaling} ScreenToLocalTransform={this.getTransform} backgroundColor={this.clusterColorFunc} PanelWidth={this.finalPanelWidth} PanelHeight={this.finalPanelHeight} - /> - {/* <ContentFittingDocumentView {...this.props} - //dragDivName={"collectionFreeFormDocumentView-container"} - //ContentScaling={this.contentScaling} + /> : <ContentFittingDocumentView {...this.props} + DataDocument={this.props.DataDoc} getTransform={this.getTransform} active={returnFalse} focus={(doc: Doc) => this.props.focus(doc, false)} - // backgroundColor={this.clusterColorFunc} PanelWidth={this.finalPanelWidth} PanelHeight={this.finalPanelHeight} - /> */} + />} </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 8f6bfc8e1..66886165e 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -82,7 +82,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return this.props.DataDoc; } get layoutDoc() { - return this.props.DataDoc === undefined ? Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document) : Doc.Layout(this.props.Document); + return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document); } CreateBindings(): JsxBindings { diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index f44c6dd3b..2ce56c73d 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -55,7 +55,7 @@ position: absolute; } - .documentView-titleWrapper { + .documentView-titleWrapper, .documentView-titleWrapper-hover { overflow: hidden; color: white; transform-origin: top left; @@ -68,6 +68,9 @@ text-overflow: ellipsis; white-space: pre; } + .documentView-titleWrapper-hover { + display:none; + } .documentView-searchHighlight { position: absolute; @@ -85,4 +88,12 @@ } } +} + +.documentView-node:hover, .documentView-node-topmost:hover { + > .documentView-styleWrapper { + > .documentView-titleWrapper-hover { + display:inline-block; + } + } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 10d2e2b3e..b2c2ccff5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -29,7 +29,6 @@ import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; -import { DictationOverlay } from '../DictationOverlay'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { OverlayView } from '../OverlayView'; @@ -64,7 +63,7 @@ export interface DocumentViewProps { moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; renderDepth: number; - showOverlays?: (doc: Doc) => { title?: string, caption?: string }; + showOverlays?: (doc: Doc) => { title?: string, titleHover?: string, caption?: string }; ContentScaling: () => number; ruleProvider: Doc | undefined; PanelWidth: () => number; @@ -196,7 +195,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script FormattedTextBoxComment.Hide(); - this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)])); + this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)])); } else if (this.Document.isButton) { SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. this.buttonClick(e.altKey, e.ctrlKey); @@ -741,7 +740,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu chromeHeight = () => { const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - return (showTitle ? 25 : 0) + 1; + const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.layoutDoc.showTitleHover); + return (showTitle && !showTitleHover ? 0 : 0) + 1; } @computed get finalLayoutKey() { return this.props.layoutKey || "layout"; } @@ -751,6 +751,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView} ContainingCollectionDoc={this.props.ContainingCollectionDoc} Document={this.props.Document} + DataDoc={this.props.DataDoc} fitToBox={this.props.fitToBox} LibraryPath={this.props.LibraryPath} addDocument={this.props.addDocument} @@ -777,8 +778,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu isSelected={this.isSelected} select={this.select} onClick={this.onClickHandler} - layoutKey={this.finalLayoutKey} - DataDoc={this.props.DataDoc} />); + layoutKey={this.finalLayoutKey} />); } linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); @@ -795,6 +795,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu TraceMobx(); const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.getLayoutPropStr("showTitle")); + const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.getLayoutPropStr("showTitleHover")); const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; const searchHighlight = (!this.Document.searchFields ? (null) : @@ -810,15 +811,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu /> </div>); const titleView = (!showTitle ? (null) : - <div className="documentView-titleWrapper" style={{ + <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{ position: showTextTitle ? "relative" : "absolute", pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", }}> <EditableView ref={this._titleRef} - contents={this.Document[showTitle]} + contents={(this.props.DataDoc || this.props.Document)[showTitle]} display={"block"} height={72} fontSize={12} - GetValue={() => StrCast(this.Document[showTitle])} - SetValue={undoBatch((value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true)} + GetValue={() => StrCast((this.props.DataDoc || this.props.Document)[showTitle])} + SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)} /> </div>); return <> diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8e28cf928..60842bcb0 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -906,15 +906,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.tryUpdateHeight(); // see if we need to preserve the insertion point - const prosediv = this.ProseRef ?.children ?.[0] as any; - const keeplocation = prosediv ?.keeplocation; + const prosediv = this.ProseRef?.children?.[0] as any; + const keeplocation = prosediv?.keeplocation; prosediv && (prosediv.keeplocation = undefined); - const pos = this._editorView ?.state.selection.$from.pos || 1; - keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); + const pos = this._editorView?.state.selection.$from.pos || 1; + keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); // jump rich text menu to this textbox - if (this._ref.current) { - const x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); + const { current } = this._ref; + if (current) { + const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50; RichTextMenu.Instance.jumpTo(x, y); } @@ -933,13 +934,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) - if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) { + if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); e.preventDefault(); } if (!node && this.ProseRef) { const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div - if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document + if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); } } @@ -996,7 +997,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & for (let off = 1; off < 100; off++) { const pos = this._editorView!.posAtCoords({ left: x + off, top: y }); const node = pos && this._editorView!.state.doc.nodeAt(pos.pos); - if (node ?.type === schema.nodes.list_item) { + if (node?.type === schema.nodes.list_item) { list_node = node; break; } @@ -1087,7 +1088,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } if (e.key === "Escape") { this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur ?.(); + (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); } e.stopPropagation(); @@ -1109,7 +1110,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @action tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this._ref.current ?.scrollHeight; + let scrollHeight = this._ref.current?.scrollHeight; if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation if (limitHeight && scrollHeight > limitHeight) { @@ -1171,7 +1172,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ? <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> : <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")} - style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc ?.backgroundColor, "transparent")}` }}> + style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}> <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={() => this.sidebarWidth} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 09e627078..634555012 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -214,37 +214,23 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } _curSuffix = "_m"; - _resized = false; - resize = (srcpath: string) => { - requestImageSize(srcpath) + _resized = ""; + resize = (imgPath: string) => { + requestImageSize(imgPath) .then((size: any) => { const rotation = NumCast(this.dataDoc.rotation) % 180; const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; const aspect = realsize.height / realsize.width; if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) { setTimeout(action(() => { - this._resized = true; - this.Document.height = this.Document[WidthSym]() * aspect; - this.Document.nativeHeight = realsize.height; - this.Document.nativeWidth = realsize.width; + if (this.pathInfos.srcpath === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) { + this._resized = imgPath; + this.Document.height = this.Document[WidthSym]() * aspect; + this.Document.nativeHeight = realsize.height; + this.Document.nativeWidth = realsize.width; + } }), 0); - } else this._resized = true; - }) - .catch((err: any) => console.log(err)); - } - fadesize = (srcpath: string) => { - requestImageSize(srcpath) - .then((size: any) => { - const rotation = NumCast(this.dataDoc.rotation) % 180; - const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; - const aspect = realsize.height / realsize.width; - if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) { - setTimeout(action(() => { - this.Document.height = this.Document[WidthSym]() * aspect; - this.Document.nativeHeight = realsize.height; - this.Document.nativeWidth = realsize.width; - }), 0); - } + } else this._resized = imgPath; }) .catch((err: any) => console.log(err)); } @@ -285,18 +271,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />); } - @computed get content() { - TraceMobx(); - const extensionDoc = this.extensionDoc; - if (!extensionDoc) return (null); - // let transform = this.props.ScreenToLocalTransform().inverse(); + @computed get nativeSize() { const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; - // var [sptX, sptY] = transform.transformPoint(0, 0); - // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); - // let w = bptX - sptX; - const nativeWidth = (this.Document.nativeWidth || pw); const nativeHeight = (this.Document.nativeHeight || 1); + return { nativeWidth, nativeHeight }; + } + + @computed get pathInfos() { + const extensionDoc = this.extensionDoc!; + const { nativeWidth, nativeHeight } = this.nativeSize; let paths = [[Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"), nativeWidth / nativeHeight]]; // this._curSuffix = ""; // if (w > 20) { @@ -308,15 +292,24 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum // else if (this._largeRetryCount < 10) this._curSuffix = "_l"; if (field instanceof ImageField) paths = [[this.choosePath(field.url), nativeWidth / nativeHeight]]; paths.push(...altpaths); - // } - const rotation = NumCast(this.Document.rotation, 0); - const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; - const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; const srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][0] as string; const srcaspect = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][1] as number; const fadepath = paths[Math.min(paths.length - 1, 1)][0] as string; + return { srcpath, srcaspect, fadepath }; + } + + @computed get content() { + TraceMobx(); + const extensionDoc = this.extensionDoc; + if (!extensionDoc) return (null); + + const { srcpath, srcaspect, fadepath } = this.pathInfos; + const { nativeWidth, nativeHeight } = this.nativeSize; + const rotation = NumCast(this.Document.rotation, 0); + const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; + const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; - !this.Document.ignoreAspect && !this._resized && this.resize(srcpath); + !this.Document.ignoreAspect && this._resized !== srcpath && this.resize(srcpath); return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <div className="imageBox-fader" > |