diff options
Diffstat (limited to 'src/client/views')
42 files changed, 923 insertions, 575 deletions
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index ce48e1215..0bf944f22 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,4 +1,4 @@ -import { Doc } from '../../new_fields/Doc'; +import { Doc, Opt } from '../../new_fields/Doc'; import { Touchable } from './Touchable'; import { computed, action, observable } from 'mobx'; import { Cast } from '../../new_fields/Types'; @@ -11,12 +11,13 @@ import { PositionDocument } from '../../new_fields/documentSchemas'; /// DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, ButtonBox) interface DocComponentProps { Document: Doc; + LayoutDoc?: () => Opt<Doc>; } export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) { class Component extends Touchable<P> { //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { return schemaCtor(this.props.Document); } - @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); } + @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document, this.props.LayoutDoc?.())); } } return Component; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index d6029dbe5..ee850922a 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -24,6 +24,7 @@ import React = require("react"); import { DragManager } from '../util/DragManager'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; +import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -162,7 +163,8 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | title={`${published ? "Push" : "Publish"} to Google Docs`} className="documentButtonBar-linker" style={{ animation }} - onClick={() => { + onClick={async () => { + await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); !published && runInAction(() => this.isAnimatingPulse = true); DocumentButtonBar.hasPushedHack = false; targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 32346165d..d57b1456a 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -98,6 +98,18 @@ $linkGap : 3px; pointer-events: auto; overflow: hidden; text-align: center; + display:inline-block; + } + .publishBox { + width: 20px; + height: 22px; + grid-column-start: 3; + grid-column-end: 4; + pointer-events: all; + background: darkgray; + display: inline-block; + position: absolute; + right: 0; } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4ec1659cc..7359835c5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,5 +1,5 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faTextHeight, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction } from "mobx"; import { observer } from "mobx-react"; @@ -18,12 +18,17 @@ import { undoBatch, UndoManager } from "../util/UndoManager"; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; import { DocumentView } from "./nodes/DocumentView"; -import { IconBox } from "./nodes/IconBox"; import React = require("react"); +import { Id } from '../../new_fields/FieldSymbols'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm, faTextHeight); library.add(faLink); library.add(faTag); library.add(faTimes); @@ -482,6 +487,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> this.TextBar = ele; } } + public static DocumentIcon(layout: string) { + const button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : + layout.indexOf("ImageBox") !== -1 ? faImage : + layout.indexOf("Formatted") !== -1 ? faStickyNote : + layout.indexOf("Video") !== -1 ? faFilm : + layout.indexOf("Collection") !== -1 ? faObjectGroup : + faCaretUp; + return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />; + } render() { const bounds = this.Bounds; const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; @@ -491,7 +505,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const minimizeIcon = ( <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}> {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} - {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} + {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} </div>); bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; @@ -524,8 +538,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> }}> {minimizeIcon} - {this._edtingTitle ? - <input ref={this._keyinput} className="title" type="text" name="dynbox" value={this._accumulatedTitle} onBlur={e => this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> : + {this._edtingTitle ? <> + <input ref={this._keyinput} className="title" type="text" name="dynbox" value={this._accumulatedTitle} style={{ width: "calc(100% - 20px)" }} + onBlur={e => this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> + <div className="publishBox" title="make document referenceable by its title" + onPointerDown={e => { + const promoteDoc = SelectionManager.SelectedDocuments()[0]; + DocUtils.Publish(promoteDoc.props.Document, this._accumulatedTitle, promoteDoc.props.addDocument, promoteDoc.props.removeDocument); + }}> + <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon> + </div> + </> : <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>} <div className="documentDecorations-closeButton" title="Close Document" onPointerDown={this.onCloseDown}> <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" /> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a9e81093a..401a4b15c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -493,7 +493,6 @@ export class MainView extends React.Component { fieldKey={"data"} annotationsKey={""} select={emptyFunction} - chromeCollapsed={true} active={returnFalse} isSelected={returnFalse} moveDocument={this.moveButtonDoc} diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index d24256886..cc5d7640e 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -93,29 +93,33 @@ export class ScriptBox extends React.Component<ScriptBoxProps> { 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) => { - const script = CompileScript(text, { - params: { this: Doc.name, ...contextParams }, - typecheck: false, - editable: true, - transformer: DocumentIconContainer.getTransformer() - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - return; - } + if (!text) { + doc[fieldKey] = undefined; + } else { + const script = CompileScript(text, { + params: { this: Doc.name, ...contextParams }, + typecheck: false, + editable: true, + transformer: DocumentIconContainer.getTransformer() + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + return; + } - const div = document.createElement("div"); - div.style.width = "90"; - div.style.height = "20"; - div.style.background = "gray"; - div.style.position = "absolute"; - div.style.display = "inline-block"; - div.style.transform = `translate(${clientX}px, ${clientY}px)`; - div.innerHTML = "button"; - params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY); + const div = document.createElement("div"); + div.style.width = "90"; + div.style.height = "20"; + div.style.background = "gray"; + div.style.position = "absolute"; + div.style.display = "inline-block"; + div.style.transform = `translate(${clientX}px, ${clientY}px)`; + div.innerHTML = "button"; + params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY); - doc[fieldKey] = new ScriptField(script); - overlayDisposer(); + doc[fieldKey] = new ScriptField(script); + overlayDisposer(); + } }} showDocumentIcons />; 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 f61eb9cd0..c9c6af054 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -4,14 +4,11 @@ import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import './TemplateMenu.scss'; import { DocumentView } from "./nodes/DocumentView"; -import { Template, Templates } from "./Templates"; +import { Template } from "./Templates"; import React = require("react"); import { Doc, DocListCast } from "../../new_fields/Doc"; import { StrCast, Cast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; -const higflyout = require("@hig/flyout"); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; @observer class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> { @@ -48,6 +45,8 @@ export interface TemplateMenuProps { @observer export class TemplateMenu extends React.Component<TemplateMenuProps> { + _addedKeys = new ObservableSet(); + _customRef = React.createRef<HTMLInputElement>(); @observable private _hidden: boolean = true; toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => { @@ -62,7 +61,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { DocumentView.FloatDoc(topDocView, ex, ey); } - @undoBatch @action toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => { @@ -83,7 +81,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { toggleChrome = (): void => { this.props.docViews.map(dv => { const layout = Doc.Layout(dv.Document); - layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : "enabled"); + layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : StrCast(layout._replacedChrome, "enabled")); }); } @@ -105,8 +103,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { }); } - _addedKeys = new ObservableSet(); - _customRef = React.createRef<HTMLInputElement>(); render() { const layout = Doc.Layout(this.props.docViews[0].Document); const templateMenu: Array<JSX.Element> = []; diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 4815f1a59..ad369bbff 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -14,27 +14,23 @@ width: 100%; } } -.carouselView-back { +.carouselView-back, .carouselView-fwd { position: absolute; display: flex; - left: 0; top: 50%; width: 30; height: 30; - background: lightgray; align-items: center; border-radius: 5px; justify-content: center; + background : rgba(255, 255, 255, 0.46); } -.carouselView-fwd { - position: absolute; - display: flex; +.carouselView-fwd { right: 0; - top: 50%; - width: 30; - height: 30; +} +.carouselView-back { + left: 0; +} +.carouselView-back:hover, .carouselView-fwd:hover { background: lightgray; - align-items: center; - border-radius: 5px; - justify-content: center; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d772ace23..d21ae32bc 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,25 +4,25 @@ import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import Switch from 'rc-switch'; -import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc"; +import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types"; -import { emptyFunction, Utils } from "../../../Utils"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; +import { TraceMobx } from "../../../new_fields/util"; +import { Utils } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; +import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; -import { TraceMobx } from "../../../new_fields/util"; import { CollectionViewType } from "./CollectionView"; @observer @@ -55,7 +55,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { this._docXfs.length = 0; return docs.map((d, i) => { const height = () => this.getDocHeight(d); - const width = () => this.widthScale * Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); + const width = () => this.getDocWidth(d); const dref = React.createRef<HTMLDivElement>(); const dxf = () => this.getDocTransform(d, dref.current!); this._docXfs.push({ dxf: dxf, width: width, height: height }); @@ -154,11 +154,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); } getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) { - const layoutDoc = Doc.Layout(doc); + const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.()); const height = () => this.getDocHeight(doc); return <ContentFittingDocumentView Document={doc} DataDocument={dataDoc} + LayoutDoc={this.props.childLayoutTemplate} LibraryPath={this.props.LibraryPath} renderDepth={this.props.renderDepth + 1} fitToBox={this.props.fitToBox} @@ -178,19 +179,26 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { pinToPres={this.props.pinToPres}> </ContentFittingDocumentView>; } + + getDocWidth(d?: Doc) { + if (!d) return 0; + const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const nw = NumCast(layoutDoc._nativeWidth); + return Math.min(nw && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); + } getDocHeight(d?: Doc) { if (!d) return 0; - const layoutDoc = Doc.Layout(d); + let layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); const nw = NumCast(layoutDoc._nativeWidth); const nh = NumCast(layoutDoc._nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); if (!layoutDoc.ignoreAspect && !layoutDoc._fitWidth && nw && nh) { const aspect = nw && nh ? nh / nw : 1; - if (!(d._nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid); + if (!(!layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid); return wid * aspect; } - return layoutDoc._fitWidth ? !layoutDoc._nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : - Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym](); + return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin : + Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym](); } columnDividerDown = (e: React.PointerEvent) => { @@ -376,16 +384,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1])); } - @computed get contentScale() { - const heightExtra = this.heightPercent > 1 ? this.heightPercent : 1; - return Math.min(this.props.Document[WidthSym]() / this.props.PanelWidth(), heightExtra * this.props.Document[HeightSym]() / this.props.PanelHeight()); - } - @computed get widthScale() { - return this.heightPercent < 1 ? Math.max(1, this.contentScale) : 1; - } - @computed get heightPercent() { - return this.props.PanelHeight() / this.layoutDoc[HeightSym](); - } + + @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); } + render() { TraceMobx(); const editableViewProps = { @@ -399,9 +400,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ref={this.createRef} style={{ overflowY: this.props.active() ? "auto" : "hidden", - transform: `scale(${Math.min(1, this.heightPercent)})`, - height: `${100 * Math.max(1, this.contentScale)}%`, - width: `${100 * this.widthScale}%`, + transform: `scale(${this.scaling}`, + height: `${1 / this.scaling * 100}%`, + width: `${1 / this.scaling * 100}%`, transformOrigin: "top left", }} onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 21982f1ca..3fc05c6b7 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -285,8 +285,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC description: ":" + fieldKey, event: () => { const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); if (created) { - if (this.props.parent.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document); + const container = this.props.parent.Document.resolvedDataDoc ? Doc.GetProto(this.props.parent.Document) : this.props.parent.Document; + if (container.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, container); + return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); } return this.props.parent.props.addDocument(created); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 62b9e8380..20941493f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,7 +1,7 @@ import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; -import { Doc, DocListCast, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; @@ -33,7 +33,6 @@ export interface CollectionViewProps extends FieldViewProps { PanelWidth: () => number; PanelHeight: () => number; VisibleHeight?: () => number; - chromeCollapsed: boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; fieldKey: string; } @@ -167,6 +166,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { return true; } let added = false; + if (this.props.Document._freezeOnDrop) { + de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]())); + } if (docDragData.dropAction || docDragData.userDropAction) { added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (docDragData.moveDocument) { @@ -230,8 +232,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { const split = img.split("src=\"")[1].split("\"")[0]; let source = split; if (split.startsWith("data:image") && split.includes("base64")) { - const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); - source = Utils.prepend(clientAccessPath); + const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); + source = Utils.prepend(accessPaths.agnostic.client); } const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); ImageUtils.ExtractExif(doc); @@ -310,9 +312,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { const dropFileName = file ? file.name : "-empty-"; promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => { results.map(action((result: any) => { - const { clientAccessPath, nativeWidth, nativeHeight, contentSize } = result; + const { accessPaths, nativeWidth, nativeHeight, contentSize } = result; const full = { ...options, _width: 300, title: dropFileName }; - const pathname = Utils.prepend(clientAccessPath); + const pathname = Utils.prepend(accessPaths.agnostic.client); Docs.Get.DocumentFromType(type, pathname, full).then(doc => { if (doc) { const proto = Doc.GetProto(doc); diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss index a5ce73a92..2dffb3ea0 100644 --- a/src/client/views/collections/CollectionTimeView.scss +++ b/src/client/views/collections/CollectionTimeView.scss @@ -1,21 +1,26 @@ -.collectionTimeView, .collectionTimeView-pivot { +.collectionTimeView, +.collectionTimeView-pivot { display: flex; flex-direction: row; position: absolute; height: 100%; width: 100%; overflow: hidden; + .collectionTimeView-backBtn { background: green; display: inline; margin-right: 20px; } + .collectionFreeform-customText { text-align: left; } + .collectionFreeform-customDiv { position: absolute; } + .collectionTimeView-thumb { position: absolute; width: 30px; @@ -28,19 +33,21 @@ border-radius: 9px; opacity: 0.25; } + .collectionTimeView-thumb-min { - margin-left:25%; + margin-left: 25%; } + .collectionTimeView-thumb-max { - margin-left:75%; + margin-left: 75%; } + .collectionTimeView-thumb-mid { - margin-left:50%; + margin-left: 50%; } .collectionTimeView-flyout { width: 400px; - height: 300px; display: inline-block; .collectionTimeView-flyout-item { @@ -119,6 +126,7 @@ left: -10px; } } + .collectionTimeView-pivot { .collectionFreeform-customText { text-align: center; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 4983acbc2..50e297f0b 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,27 +1,29 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, trace, runInAction } from "mobx"; +import { action, computed, observable, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Set } from "typescript-collections"; -import { Doc, DocListCast, Field } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Field, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; +import { ObjectField } from "../../../new_fields/ObjectField"; import { RichTextField } from "../../../new_fields/RichTextField"; import { listSpec } from "../../../new_fields/Schema"; import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Docs } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { Scripting } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; -import { anchorPoints, Flyout } from "../TemplateMenu"; import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTimeView.scss"; -import React = require("react"); import { CollectionTreeView } from "./CollectionTreeView"; -import { ObjectField } from "../../../new_fields/ObjectField"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; +import React = require("react"); @observer export class CollectionTimeView extends CollectionSubView(doc => doc) { @@ -29,11 +31,12 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @observable _layoutEngine = "pivot"; componentDidMount() { + this.props.Document._freezeOnDrop = true; const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked; if (!this.props.Document._facetCollection) { - const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true }); + const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true }); facetCollection.target = this.props.Document; - this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]); + this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]); const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)"; const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); "; @@ -43,7 +46,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { this.props.Document._fitToBox = true; } if (!this.props.Document.onViewDefClick) { - this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }) + this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); } } @@ -54,7 +57,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { const facets = new Set<string>(); this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); - return facets.toArray(); + return Array.from(facets); } /** @@ -66,19 +69,58 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader); if (found !== -1) { (facetCollection.data as List<Doc>).splice(found, 1); - const docFilter = Cast(this.props.Document._docFilter, listSpec("string")); + const docFilter = Cast(this.props.Document._docFilters, listSpec("string")); if (docFilter) { let index: number; while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) { docFilter.splice(index, 3); } } + const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string")); + if (docRangeFilters) { + let index: number; + while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { + docRangeFilters.splice(index, 3); + } + } } else { - const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); - const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; - const params = { layoutDoc: Doc.name, dataDoc: Doc.name, }; - newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables); - Doc.AddDocToList(facetCollection, "data", newFacet); + const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]); + const facetValues = Array.from(allCollectionDocs.reduce((set, child) => + set.add(Field.toString(child[facetHeader] as Field)), new Set<string>())); + + let nonNumbers = 0; + let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; + facetValues.map(val => { + const num = Number(val); + if (Number.isNaN(num)) { + nonNumbers++; + } else { + minVal = Math.min(num, minVal); + maxVal = Math.max(num, maxVal); + } + }); + if (nonNumbers / allCollectionDocs.length < .1) { + const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader); + const newFacet = Docs.Create.SliderDocument({ title: facetHeader }); + Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox + newFacet.treeViewExpandedView = "layout"; + newFacet.treeViewOpen = true; + newFacet._sliderMin = ranged === undefined ? minVal : ranged[0]; + newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1]; + newFacet._sliderMinThumb = minVal; + newFacet._sliderMaxThumb = maxVal; + newFacet.target = this.props.Document; + const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`; + newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); + + Doc.AddDocToList(facetCollection, "data", newFacet); + } else { + const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); + const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; + const params = { layoutDoc: Doc.name, dataDoc: Doc.name, }; + newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables); + Doc.AddDocToList(facetCollection, "data", newFacet); + } } } } @@ -120,9 +162,9 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { pair.layout[fieldKey] instanceof RichTextField || typeof (pair.layout[fieldKey]) === "number" || typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey))); - keySet.toArray().map(fieldKey => + Array.from(keySet).map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" })); - docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" }) + docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" }); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y, ":"); @@ -148,6 +190,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq + (maxReq - minReq) * delta / this.props.PanelWidth(); + this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined; } onMinUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onMinMove); @@ -170,6 +213,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq + (maxReq - minReq) * delta / this.props.PanelWidth(); + this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined; } onMaxUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onMaxMove); @@ -209,7 +253,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { trace(); const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null); const flyout = ( - <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}` }}> + <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30, display: "block" }} onWheel={e => e.stopPropagation()}> {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}> <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} /> <span className="checkmark" /> @@ -227,7 +271,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { </Flyout> </div> <div className="collectionTimeView-tree" key="tree"> - <CollectionTreeView {...this.props} Document={facetCollection} /> + <CollectionTreeView {...this.props} PanelWidth={() => this._facetWidth} Document={facetCollection} /> </div> </div>; } @@ -240,9 +284,9 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { const layoutItems: ContextMenuProps[] = []; const doc = this.props.Document; - layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline" }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot" }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined }, icon: "compress-arrows-alt" }); + layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline"; }, icon: "compress-arrows-alt" }); + layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, icon: "compress-arrows-alt" }); + layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined; }, icon: "compress-arrows-alt" }); layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" }); @@ -289,14 +333,16 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu} style={{ height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}> <div className={"pivotKeyEntry"}> - <button className="collectionTimeView-backBtn" style={{ width: 50, height: 20, background: "green" }} + <button className="collectionTimeView-backBtn" onClick={action(() => { - let pfilterIndex = NumCast(this.props.Document._pfilterIndex); - if (pfilterIndex > 0) { - this.props.Document._docFilter = ObjectField.MakeCopy(this.props.Document["_pfilter" + --pfilterIndex] as ObjectField); - this.props.Document._pfilterIndex = pfilterIndex; + let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex); + if (prevFilterIndex > 0) { + prevFilterIndex--; + this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField); + this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField); + this.props.Document._prevFilterIndex = prevFilterIndex; } else { - this.props.Document._docFilter = new List([]); + this.props.Document._docFilters = new List([]); } })}> back @@ -320,11 +366,12 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { - let pfilterIndex = NumCast(pivotDoc._pfilterIndex); - pivotDoc["_pfilter" + pfilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilter as ObjectField); - pivotDoc._pfilterIndex = ++pfilterIndex; + let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); + pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); + pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); + pivotDoc._prevFilterIndex = ++prevFilterIndex; runInAction(() => { - pivotDoc._docFilter = new List(); + pivotDoc._docFilters = new List(); (bounds.payload as string[]).map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check")); }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 001064b30..be2947dff 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -60,7 +60,7 @@ export interface TreeViewProps { treeViewId: Doc; parentKey: string; active: (outsideReaction?: boolean) => boolean; - hideHeaderFields: () => boolean; + treeViewHideHeaderFields: () => boolean; preventTreeViewOpen: boolean; renderedIds: string[]; onCheckedClick?: ScriptField; @@ -292,7 +292,7 @@ class TreeView extends React.Component<TreeViewProps> { contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : 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.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen, [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick); } else { contentElement = <EditableView @@ -335,7 +335,7 @@ class TreeView extends React.Component<TreeViewProps> { TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document), 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.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen, [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)} </ul >; } else if (this.treeViewExpandedView === "fields") { @@ -425,7 +425,7 @@ class TreeView extends React.Component<TreeViewProps> { }} > {this.editableView("title")} </div > - {this.props.hideHeaderFields() ? (null) : headerElements} + {this.props.treeViewHideHeaderFields() ? (null) : headerElements} {openRight} </>; } @@ -464,7 +464,7 @@ class TreeView extends React.Component<TreeViewProps> { panelWidth: () => number, ChromeHeight: undefined | (() => number), renderDepth: number, - hideHeaderFields: () => boolean, + treeViewHideHeaderFields: () => boolean, preventTreeViewOpen: boolean, renderedIds: string[], libraryPath: Doc[] | undefined, @@ -574,7 +574,7 @@ class TreeView extends React.Component<TreeViewProps> { outerXf={outerXf} parentKey={key} active={active} - hideHeaderFields={hideHeaderFields} + treeViewHideHeaderFields={treeViewHideHeaderFields} preventTreeViewOpen={preventTreeViewOpen} renderedIds={renderedIds} />; }); @@ -625,7 +625,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { } else { const layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: (this.props.Document.preventTreeViewOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" }); - layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" }); + layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" }); layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" }); ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" }); } @@ -633,11 +633,10 @@ export class CollectionTreeView extends CollectionSubView(Document) { description: "Buxton Layout", icon: "eye", event: () => { DocListCast(this.dataDoc[this.props.fieldKey]).map(d => { DocListCast(d.data).map((img, i) => { - const caption = (d.captions as any)[i]?.data; - if (caption instanceof ObjectField) { - Doc.GetProto(img).caption = ObjectField.MakeCopy(caption); + const caption = (d.captions as any)[i]; + if (caption) { + Doc.GetProto(img).caption = caption; } - d.captions = undefined; }); }); const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create; @@ -649,13 +648,12 @@ export class CollectionTreeView extends CollectionSubView(Document) { const detailView = Docs.Create.StackingDocument([ CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }), textDoc, - TextDocument("", { title: "short_description", _autoHeight: true }), + TextDocument("", { title: "shortDescription", _autoHeight: true }), TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true }) ], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" }); textDoc.data = new RichTextField(detailedTemplate, "year company"); detailView.isTemplateDoc = makeTemplate(detailView); - const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work? heroView.proto!.layout = ImageBox.LayoutString("hero"); heroView.showTitle = "title"; @@ -673,7 +671,6 @@ export class CollectionTreeView extends CollectionSubView(Document) { dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "file-alt" })); - Document.childLayout = heroView; Document.childDetailed = detailView; Document._viewType = CollectionViewType.Time; @@ -732,7 +729,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { { TreeView.GetChildElements(this.childDocs, this.props.Document, 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), + this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields), BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick)) } </ul> @@ -764,7 +761,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey }); Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { - const docFilters = Cast(layoutDoc._docFilter, listSpec("string"), []); + const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); for (let i = 0; i < docFilters.length; i += 3) { const [header, value, state] = docFilters.slice(i, i + 3); if (header === facetHeader && value === facetValue) { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index bdd908807..8dd844711 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast } from '../../../new_fields/Doc'; +import { Doc, DocListCast, DataSym } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { listSpec } from '../../../new_fields/Schema'; import { BoolCast, Cast, StrCast, NumCast } from '../../../new_fields/Types'; @@ -54,8 +54,7 @@ export enum CollectionViewType { Time, Carousel, Linear, - Staff, - Timeline + Staff } export namespace CollectionViewType { @@ -116,7 +115,7 @@ export class CollectionView extends Touchable<FieldViewProps> { // chrome status is one of disabled, collapsed, or visible. this determines initial state from document // chrome status may also be view-mode, in reference to stacking view's toggle mode. it is essentially disabled mode, but prevents the toggle button from showing up on the left sidebar. const chromeStatus = this.props.Document._chromeStatus; - if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) { + if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed" || chromeStatus === "replaced")) { runInAction(() => this._collapsed = true); } }); @@ -133,7 +132,7 @@ export class CollectionView extends Touchable<FieldViewProps> { @action.bound addDocument(doc: Doc): boolean { - const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document); + const targetDataDoc = this.props.Document[DataSym]; Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); Doc.GetProto(doc).lastOpened = new DateField; @@ -144,7 +143,7 @@ export class CollectionView extends Touchable<FieldViewProps> { removeDocument(doc: Doc): boolean { const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); docView && SelectionManager.DeselectDoc(docView); - const value = Cast((this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []); + const value = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []); let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); @@ -176,14 +175,14 @@ export class CollectionView extends Touchable<FieldViewProps> { } private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { - const props = { ...this.props, ...renderProps, chromeCollapsed: this._collapsed, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" }; + const props = { ...this.props, ...renderProps, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" }; switch (type) { case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />); 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.Multirow: return (<CollectionMultirowView chromeCollapsed={true} key="rpwview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />); + case CollectionViewType.Staff: return (<CollectionStaffView key="collview" {...props} />); + case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />); + case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />); case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); } case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); } case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); } @@ -202,7 +201,7 @@ export class CollectionView extends Touchable<FieldViewProps> { private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - const chrome = this.props.Document._chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) : + const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) : <CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />; return [chrome, this.SubViewHelper(type, renderProps)]; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index baf09fe5b..050ca8347 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -43,6 +43,9 @@ export interface ViewDefResult { bounds?: ViewDefBounds; } function toLabel(target: FieldResult<Field>) { + if (typeof target === "number" || Number(target)) { + return Number(target).toFixed(2).toString(); + } if (target instanceof ObjectField || target instanceof RefField) { return target[ToString](); } @@ -186,7 +189,7 @@ export function computePivotLayout( const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols); const dividers = sortedPivotKeys.map((key, i) => - ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); + ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); groupNames.push(...dividers); return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c))); } @@ -211,8 +214,8 @@ export function computeTimelineLayout( const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field); const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]); const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null); - const minTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTimeSpan && (curTime - curTimeSpan); - const maxTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTimeSpan && (curTime + curTimeSpan); + const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTime && (curTime - curTimeSpan); + const maxTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTime && (curTime + curTimeSpan); const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); const fontHeight = panelDim[1] > 58 ? 30 : panelDim[1] / 2; const findStack = (time: number, stack: number[]) => { @@ -220,11 +223,11 @@ export function computeTimelineLayout( return index === -1 ? stack.length : index; } - let minTime = Number.MAX_VALUE; - let maxTime = -Number.MAX_VALUE; + let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq; + let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq; filterDocs.map(doc => { const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey]))); - if (!(Number.isNaN(num) || (minTimeReq && num < minTimeReq) || (maxTimeReq && num > maxTimeReq))) { + if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) { !pivotDateGroups.get(num) && pivotDateGroups.set(num, []); pivotDateGroups.get(num)!.push(doc); minTime = Math.min(num, minTime); @@ -254,10 +257,10 @@ export function computeTimelineLayout( let prevKey = Math.floor(minTime); if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) { - groupNames.push({ type: "text", text: prevKey.toString(), x: x, y: 0, height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: "text", text: toLabel(prevKey), x: x, y: 0, height: fontHeight, fontSize, payload: undefined }); } if (!sortedKeys.length && curTime !== undefined) { - groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined }); } const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5); @@ -265,7 +268,7 @@ export function computeTimelineLayout( let zind = 0; sortedKeys.forEach(key => { if (curTime !== undefined && curTime > prevKey && curTime <= key) { - groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key }); + groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key }); } const keyDocs = pivotDateGroups.get(key)!; x += scaling * (key - prevKey); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index bb9ae4326..92fa2781c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -8,74 +8,65 @@ import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormView.scss"; import React = require("react"); import v5 = require("uuid/v5"); +import { computed } from "mobx"; +import { FieldResult } from "../../../../new_fields/Doc"; +import { List } from "../../../../new_fields/List"; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { - protected getCursors(): CursorField[] { + @computed protected get cursors(): CursorField[] { const doc = this.props.Document; - const id = CurrentUserUtils.id; - if (!id) { + let cursors: FieldResult<List<CursorField>>; + const { id } = CurrentUserUtils; + if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) { return []; } - - const cursors = Cast(doc.cursors, listSpec(CursorField)); - const now = mobxUtils.now(); - // const now = Date.now(); - return (cursors || []).filter(cursor => cursor.data.metadata.id !== id && (now - cursor.data.metadata.timestamp) < 1000); + return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && (now - metadata.timestamp) < 1000); } - private crosshairs?: HTMLCanvasElement; - drawCrosshairs = (backgroundColor: string) => { - if (this.crosshairs) { - const ctx = this.crosshairs.getContext('2d'); - if (ctx) { - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, 20, 20); - - ctx.fillStyle = "black"; - ctx.lineWidth = 0.5; - - ctx.beginPath(); + @computed get renderedCursors() { + return this.cursors.map(({ data: { metadata, position: { x, y } } }) => { + return ( + <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" + style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }} + > + <canvas className="collectionFreeFormRemoteCursors-canvas" + ref={(el) => { + if (el) { + const ctx = el.getContext('2d'); + if (ctx) { + ctx.fillStyle = "#" + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + "22"; + ctx.fillRect(0, 0, 20, 20); - ctx.moveTo(10, 0); - ctx.lineTo(10, 8); + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; - ctx.moveTo(10, 20); - ctx.lineTo(10, 12); + ctx.beginPath(); - ctx.moveTo(0, 10); - ctx.lineTo(8, 10); + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); - ctx.moveTo(20, 10); - ctx.lineTo(12, 10); + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); - ctx.stroke(); + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); - // ctx.font = "10px Arial"; - // ctx.fillText(Doc.CurrentUserEmail[0].toUpperCase(), 10, 10); - } - } - } + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); - get sharedCursors() { - return this.getCursors().map(c => { - const m = c.data.metadata; - const l = c.data.position; - this.drawCrosshairs("#" + v5(m.id, v5.URL).substring(0, 6).toUpperCase() + "22"); - return ( - <div key={m.id} className="collectionFreeFormRemoteCursors-cont" - style={{ transform: `translate(${l.x - 10}px, ${l.y - 10}px)` }} - > - <canvas className="collectionFreeFormRemoteCursors-canvas" - ref={(el) => { if (el) this.crosshairs = el; }} + ctx.stroke(); + } + } + }} width={20} height={20} /> <p className="collectionFreeFormRemoteCursors-symbol"> - {m.identifier[0].toUpperCase()} + {metadata.identifier[0].toUpperCase()} </p> </div> ); @@ -83,6 +74,6 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV } render() { - return this.sharedCursors; + return this.renderedCursors; } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2518a4a55..07a5a2c7b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,20 +1,22 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer, trace } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc"; +import { computedFn } from "mobx-utils"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkTool, InkField, InkData } from "../../../../new_fields/InkField"; -import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema"; +import { InkTool } from "../../../../new_fields/InkField"; +import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types"; +import { Cast, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types"; +import { TraceMobx } from "../../../../new_fields/util"; +import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; -import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; +import { Docs } from "../../../documents/Documents"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; @@ -32,15 +34,12 @@ import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; import { CollectionSubView } from "../CollectionSubView"; -import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData, ViewDefBounds } from "./CollectionFreeFormLayoutEngines"; +import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { computedFn } from "mobx-utils"; -import { TraceMobx } from "../../../../new_fields/util"; -import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -807,7 +806,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { doFreeformLayout(poolData: Map<string, any>) { const layoutDocs = this.childLayoutPairs.map(pair => pair.layout); const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log); - let state = initResult && initResult.success ? initResult.result.scriptState : undefined; + const state = initResult && initResult.success ? initResult.result.scriptState : undefined; const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { @@ -827,7 +826,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @computed get filterDocs() { - const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []); + const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []); + const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []); const clusters: { [key: string]: { [value: string]: string } } = {}; for (let i = 0; i < docFilters.length; i += 3) { const [key, value, modifiers] = docFilters.slice(i, i + 3); @@ -853,7 +853,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } return true; }) : this.childDocs; - return filteredDocs; + const rangeFilteredDocs = filteredDocs.filter(d => { + for (let i = 0; i < docRangeFilters.length; i += 3) { + const key = docRangeFilters[i]; + const min = Number(docRangeFilters[i + 1]); + const max = Number(docRangeFilters[i + 2]); + const val = Cast(d[key], "number", null); + if (val !== undefined && (val < min || val > max)) { + return false; + } + } + return true; + }); + return rangeFilteredDocs; } get doLayoutComputation() { const { newPool, computedElementData } = this.doInternalLayoutComputation; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e16f4011e..4b0855635 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -346,8 +346,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque summary = (e: KeyboardEvent | React.PointerEvent | undefined) => { const bounds = this.Bounds; const selected = this.marqueeSelect(false); - const newCollection = this.getCollection(selected, false); - selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; @@ -355,23 +353,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque d.page = -1; return d; }); - newCollection._chromeStatus = "disabled"; - const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 300, _height: 100, _autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); - newCollection.x = bounds.left + bounds.width; - Doc.GetProto(newCollection).summaryDoc = summary; - Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); - if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. - const container = Docs.Create.FreeformDocument([summary, newCollection], { - x: bounds.left, y: bounds.top, _width: 300, _height: 200, _autoHeight: true, - _viewType: CollectionViewType.Stacking, _chromeStatus: "disabled", title: "-summary-" - }); - Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" - this.props.addLiveTextDocument(container); - } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // 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); - } + const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + Doc.GetProto(summary)["data-annotations"] = new List<Doc>(selected); + this.props.addLiveTextDocument(summary); MarqueeOptionsMenu.Instance.fadeOut(true); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index 0c74b8ddb..821c8d804 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -1,8 +1,8 @@ .collectionMulticolumnView_contents { display: flex; + overflow: hidden; width: 100%; height: 100%; - overflow: hidden; .document-wrapper { display: flex; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index 64f607680..a7e2c5707 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -1,8 +1,8 @@ .collectionMultirowView_contents { display: flex; + overflow: hidden; width: 100%; height: 100%; - overflow: hidden; flex-direction: column; .document-wrapper { diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index ff7c4998f..ec05443e5 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -258,6 +258,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) return ( <div className={"collectionMultirowView_contents"} style={{ + width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`, + height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`, marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin), marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin) }} ref={this.createDashEventsTarget}> diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index bd1b6166f..671f5b96e 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { action, computed } from "mobx"; import { observer } from "mobx-react"; import "react-table/react-table.css"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; @@ -18,6 +18,7 @@ import { TraceMobx } from "../../../new_fields/util"; interface ContentFittingDocumentViewProps { Document?: Doc; DataDocument?: Doc; + LayoutDoc?: () => Opt<Doc>; LibraryPath: Doc[]; childDocs?: Doc[]; renderDepth: number; @@ -42,7 +43,7 @@ interface ContentFittingDocumentViewProps { @observer export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive - private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); } + private get layoutDoc() { return this.props.Document && (this.props.LayoutDoc?.() || Doc.Layout(this.props.Document)); } private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); } private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); } @computed get scaling() { @@ -97,6 +98,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo <DocumentView {...this.props} Document={this.props.Document} DataDoc={this.props.DataDocument} + LayoutDoc={this.props.LayoutDoc} LibraryPath={this.props.LibraryPath} fitToBox={this.props.fitToBox} onClick={this.props.onClick} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index ac80e2ec1..11a33b70a 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,7 +1,6 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, StrCast } from "../../../new_fields/Types"; import { OmitKeys, Without } from "../../../Utils"; import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; @@ -14,13 +13,13 @@ import { LinkFollowBox } from "../linking/LinkFollowBox"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; import { ButtonBox } from "./ButtonBox"; +import { SliderBox } from "./SliderBox"; import { DocumentBox } from "./DocumentBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; import { FieldView, FieldViewProps } from "./FieldView"; import { FormattedTextBox } from "./FormattedTextBox"; -import { IconBox } from "./IconBox"; import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; @@ -83,10 +82,10 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc; } get layoutDoc() { - if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") { + if (this.props.DataDoc === undefined && (this.props.LayoutDoc || typeof Doc.LayoutField(this.props.Document) !== "string")) { // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), // then we render the layout document as a template and use this document as the data context for the template layout. - return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document); + return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document); } return Doc.Layout(this.props.Document); } @@ -106,7 +105,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { <ObserverJsxParser blacklistedAttrs={[]} components={{ - FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView, + FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index b9045b11e..b121c6c18 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -71,7 +71,6 @@ width: 100%; height: 25; background: rgba(0, 0, 0, .4); - padding: 4px; text-align: center; text-overflow: ellipsis; white-space: pre; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 787f6b79b..95858af4c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,36 +1,42 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction, trace } from "mobx"; +import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { Document, PositionDocument } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; +import { InkTool } from '../../../new_fields/InkField'; +import { List } from '../../../new_fields/List'; +import { RichTextField } from '../../../new_fields/RichTextField'; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { ImageField, PdfField, VideoField, AudioField } from '../../../new_fields/URLField'; +import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField'; +import { TraceMobx } from '../../../new_fields/util'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; -import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents"; +import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; +import { InteractionUtils } from '../../util/InteractionUtils'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionViewType } from '../collections/CollectionView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionView } from "../collections/CollectionView"; +import { CollectionView, CollectionViewType } from '../collections/CollectionView'; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; +import { InkingControl } from '../InkingControl'; import { OverlayView } from '../OverlayView'; import { ScriptBox } from '../ScriptBox'; import { ScriptingRepl } from '../ScriptingRepl'; @@ -38,19 +44,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { InteractionUtils } from '../../util/InteractionUtils'; -import { InkingControl } from '../InkingControl'; -import { InkTool } from '../../../new_fields/InkField'; -import { TraceMobx } from '../../../new_fields/util'; -import { List } from '../../../new_fields/List'; -import { FormattedTextBoxComment } from './FormattedTextBoxComment'; -import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { RadialMenu } from './RadialMenu'; -import { RadialMenuProps } from './RadialMenuItem'; -import { CollectionStackingView } from '../collections/CollectionStackingView'; -import { RichTextField } from '../../../new_fields/RichTextField'; -import { HistoryUtil } from '../../util/History'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -61,6 +55,7 @@ export interface DocumentViewProps { ContainingCollectionDoc: Opt<Doc>; Document: Doc; DataDoc?: Doc; + LayoutDoc?: () => Opt<Doc>; LibraryPath: Doc[]; fitToBox?: boolean; onClick?: ScriptField; @@ -269,12 +264,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } else if (this.onClickHandler && this.onClickHandler.script) { + SelectionManager.DeselectAll(); this.onClickHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log); } else if (this.Document.type === DocumentType.BUTTON) { 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)])); } 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); @@ -284,29 +277,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } preventDefault && e.preventDefault(); } - }) + }); buttonClick = async (altKey: boolean, ctrlKey: boolean) => { - const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs); - const summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs); const linkDocs = DocListCast(this.props.Document.links); - let expandedDocs: Doc[] = []; - expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; - expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; - // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; - if (expandedDocs.length) { - SelectionManager.DeselectAll(); - let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); - maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); - if (maxLocation === "inPlace") { - expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc)); - const scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2); - DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); - } else { - expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); - } - } - else if (linkDocs.length) { + if (linkDocs.length) { DocumentManager.Instance.FollowLink(undefined, this.props.Document, // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)), @@ -563,7 +538,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate)); - Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, customName, undefined); + Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); } else { doc.layoutKey = customName; } @@ -617,13 +592,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @undoBatch - @action - freezeNativeDimensions = (): void => { - this.layoutDoc._autoHeight = false; - this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect; - if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) { - this.layoutDoc._nativeWidth = this.props.PanelWidth(); - this.layoutDoc._nativeHeight = this.props.PanelHeight(); + public static unfreezeNativeDimensions = action((layoutDoc: Doc): void => { + layoutDoc._nativeWidth = undefined; + layoutDoc._nativeHeight = undefined; + }); + + toggleNativeDimensions = () => { + if (this.Document._nativeWidth || this.Document._nativeHeight) { + DocumentView.unfreezeNativeDimensions(this.layoutDoc); + } + else { + Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight()); } } @@ -657,7 +636,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (StrCast(tempDoc.title) === layout) { foundLayout = tempDoc; } - }) + }); DocumentView. makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout); } else { @@ -737,7 +716,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); layoutItems.push({ description: `${this.Document._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.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); @@ -817,7 +796,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } }); const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc().LibraryBtn as Doc).sourcePanel as Doc) ? "" : d.title), ""); - cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); cm.addItem({ description: `path: ${path}`, event: () => { this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true); @@ -856,6 +834,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContainingCollectionDoc={this.props.ContainingCollectionDoc} Document={this.props.Document} DataDoc={this.props.DataDoc} + LayoutDoc={this.props.LayoutDoc} fitToBox={this.props.fitToBox} LibraryPath={this.props.LibraryPath} addDocument={this.props.addDocument} @@ -938,7 +917,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.contents : <div className="documentView-styleWrapper" > - <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? "calc(100% - 29px)" : "100%", top: showTextTitle ? "29px" : undefined }}> + <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? "calc(100% - 25px)" : "100%", top: showTextTitle ? "25px" : undefined }}> {this.contents} </div> {titleView} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index dbbb76f83..8250f41f3 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -5,15 +5,10 @@ import { DateField } from "../../../new_fields/DateField"; import { Doc, FieldResult, Opt } from "../../../new_fields/Doc"; import { IconField } from "../../../new_fields/IconField"; import { List } from "../../../new_fields/List"; -import { RichTextField } from "../../../new_fields/RichTextField"; import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField"; import { Transform } from "../../util/Transform"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { IconBox } from "./IconBox"; -import { ImageBox } from "./ImageBox"; -import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; import { ScriptField } from "../../../new_fields/ScriptField"; @@ -48,6 +43,7 @@ export interface FieldViewProps { setVideoBox?: (player: VideoBox) => void; ContentScaling: () => number; ChromeHeight?: () => number; + childLayoutTemplate?: () => Opt<Doc>; } @observer @@ -78,9 +74,6 @@ export class FieldView extends React.Component<FieldViewProps> { // else if (field instaceof PresBox) { // return <PresBox {...this.props} />; // } - else if (field instanceof IconField) { - return <IconBox {...this.props} />; - } else if (field instanceof VideoField) { return <VideoBox {...this.props} />; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9370d3745..0cc276458 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -26,7 +26,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { DictationManager } from '../../util/DictationManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; -import { inpRules } from "../../util/RichTextRules"; +import { RichTextRules } from "../../util/RichTextRules"; import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView, DashFieldView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; @@ -197,7 +197,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } updateTitle = () => { - if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { + if ((this.props.Document.isTemplateForField === "data" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { const str = this._editorView.state.doc.textContent; const titlestr = str.substr(0, Math.min(40, str.length)); this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); @@ -464,13 +465,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } _keymap: any = undefined; + _rules: RichTextRules | undefined; @computed get config() { this._keymap = buildKeymap(schema); - (schema as any).Document = this.props.Document; + this._rules = new RichTextRules(this.props.Document, this); return { schema, plugins: [ - inputRules(inpRules), + inputRules(this._rules.inpRules), this.richTextMenuPlugin(), history(), keymap(this._keymap), @@ -574,7 +576,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (ret.frag.size > 2 && ret.start >= 0) { let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected } editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); @@ -769,7 +771,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); - this._editorView.state.schema.Document = this.props.Document; const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { this._editorView.dispatch(this._editorView.state.tr.insertText(startupText)); @@ -1010,14 +1011,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } const state = this._editorView!.state; if (!state.selection.empty && e.key === "%") { - state.schema.EnteringStyle = true; + this._rules!.EnteringStyle = true; e.preventDefault(); e.stopPropagation(); return; } - if (state.selection.empty || !state.schema.EnteringStyle) { - state.schema.EnteringStyle = false; + if (state.selection.empty || !this._rules!.EnteringStyle) { + this._rules!.EnteringStyle = false; } if (e.key === "Escape") { this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); @@ -1122,8 +1123,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & CollectionView={undefined} ScreenToLocalTransform={this.sidebarScreenToLocal} renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - chromeCollapsed={true}> + ContainingCollectionDoc={this.props.ContainingCollectionDoc}> </CollectionFreeFormView> <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> </div>} diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss deleted file mode 100644 index 488681027..000000000 --- a/src/client/views/nodes/IconBox.scss +++ /dev/null @@ -1,23 +0,0 @@ - -@import "../globalCssVariables"; -.iconBox-container { - position: inherit; - left:0; - top:0; - height: auto; - width: max-content; - // overflow: hidden; - pointer-events: all; - svg { - width: $MINIMIZED_ICON_SIZE !important; - height: $MINIMIZED_ICON_SIZE !important; - height: auto; - background: white; - } - .iconBox-label { - position: absolute; - width:max-content; - font-size: 14px; - margin-top: 3px; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx deleted file mode 100644 index 172338eb6..000000000 --- a/src/client/views/nodes/IconBox.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React = require("react"); -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faTag, faTextHeight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./IconBox.scss"; -import { Cast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; -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 } from "../../util/Scripting"; -import { ComputedField } from "../../../new_fields/ScriptField"; - - -library.add(faCaretUp); -library.add(faObjectGroup); -library.add(faStickyNote); -library.add(faFilePdf); -library.add(faFilm, faTag, faTextHeight); - -@observer -export class IconBox extends React.Component<FieldViewProps> { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(IconBox, fieldKey); } - - @observable _panelWidth: number = 0; - @observable _panelHeight: number = 0; - @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) { - Doc.GetProto(doc).title = ComputedField.MakeFunction('iconTitle(this);'); - } - - public static DocumentIcon(layout: string) { - const button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : - layout.indexOf("ImageBox") !== -1 ? faImage : - layout.indexOf("Formatted") !== -1 ? faStickyNote : - layout.indexOf("Video") !== -1 ? faFilm : - layout.indexOf("Collection") !== -1 ? faObjectGroup : - faCaretUp; - return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />; - } - - setLabelField = (): void => { - this.props.Document.hideLabel = !this.props.Document.hideLabel; - } - - specificContextMenu = (): void => { - const 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" }); - } - } - render() { - const label = this.props.Document.hideLabel ? "" : this.props.Document.title; - return ( - <div className="iconBox-container" onContextMenu={this.specificContextMenu}> - {this.minimizedIcon} - <Measure offset onResize={(r) => runInAction(() => { - if (r.offset!.width || this.props.Document.hideLabel) { - this.props.Document.iconWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); - if (this.props.Document._height === Number(MINIMIZED_ICON_SIZE)) this.props.Document._width = this.props.Document.iconWidth; - } - })}> - {({ measureRef }) => - <span ref={measureRef} className="iconBox-label">{label}</span> - } - </Measure> - </div>); - } -} -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 896ac1685..c0e102195 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -207,7 +207,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum return url.href; } else if (url.href.indexOf(window.location.origin) === -1) { return Utils.CorsProxy(url.href); - } else if (!(lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) { + } else if (!/\.(png|jpg|jpeg|gif)$/.test(lower)) { return url.href;//Why is this here } const ext = path.extname(url.href); @@ -321,12 +321,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const { dataDoc } = this; const { success, failure, idle, loading } = uploadIcons; runInAction(() => this.uploadIcon = loading); - const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] }); + const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] }); dataDoc.originalUrl = primary; let succeeded = true; let data: ImageField | undefined; try { - data = new ImageField(Utils.prepend(clientAccessPath)); + data = new ImageField(Utils.prepend(accessPaths.agnostic.client)); } catch { succeeded = false; } @@ -436,8 +436,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum CollectionView={undefined} ScreenToLocalTransform={this.props.ScreenToLocalTransform} renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - chromeCollapsed={true}> + ContainingCollectionDoc={this.props.ContainingCollectionDoc}> {this.contentFunc} </CollectionFreeFormView> </div >); diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss index 6e8a36c6a..a26880c9e 100644 --- a/src/client/views/nodes/KeyValueBox.scss +++ b/src/client/views/nodes/KeyValueBox.scss @@ -74,7 +74,7 @@ $header-height: 30px; .keyValueBox-evenRow { position: relative; - display: inline-block; + display: flex; width:100%; height:$header-height; background: $light-color; @@ -114,7 +114,7 @@ $header-height: 30px; .keyValueBox-oddRow { position: relative; - display: inline-block; + display: flex; width:100%; height:30px; background: $light-color-secondary; diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 7618aa7e3..6a20751cc 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -2,13 +2,11 @@ position: absolute; z-index: 2; box-shadow: #AAAAAA .2vw .2vw .4vw; - right: 0; - top: 0; bottom: 0; width: 100%; min-width: 120px; height: 100%; - min-height: 50px; + min-height: 41px; letter-spacing: 2px; overflow: hidden; transition: 0.7s opacity ease; @@ -17,6 +15,10 @@ .presBox-buttons { padding: 10px; width: 100%; + background: gray; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; .presBox-button { margin-right: 2.5%; margin-left: 2.5%; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 6c4cbba12..d4a47c159 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -2,27 +2,25 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { listSpec, makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { InkTool } from "../../../new_fields/InkField"; +import { listSpec } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { returnFalse } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./PresBox.scss"; -import { CollectionCarouselView } from "../collections/CollectionCarouselView"; -import { returnFalse } from "../../../Utils"; import { ContextMenuProps } from "../ContextMenuItem"; -import { CollectionTimeView } from "../collections/CollectionTimeView"; -import { documentSchema } from "../../../new_fields/documentSchemas"; import { InkingControl } from "../InkingControl"; -import { InkTool } from "../../../new_fields/InkField"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./PresBox.scss"; +import { PrefetchProxy } from "../../../new_fields/Proxy"; library.add(faArrowLeft); library.add(faArrowRight); @@ -37,33 +35,14 @@ library.add(faEdit); export class PresBox extends React.Component<FieldViewProps> { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } _childReaction: IReactionDisposer | undefined; - _slideshowReaction: IReactionDisposer | undefined; @observable _isChildActive = false; - componentDidMount() { - const userDoc = CurrentUserUtils.UserDocument; - this._slideshowReaction = reaction(() => this.props.Document._slideshow, - (slideshow) => { - if (!slideshow) { - let presTemp = Cast(userDoc.presentationTemplate, Doc); - if (presTemp instanceof Promise) { - presTemp.then(presTemp => this.props.Document.childLayout = presTemp); - } - else if (presTemp === undefined) { - presTemp = userDoc.presentationTemplate = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent", _xMargin: 5, isTemplateDoc: true, isTemplateForField: "data" }); - } - else { - this.props.Document.childLayout = presTemp; - } - } else { - this.props.Document.childLayout = undefined; - } - }, { fireImmediately: true }); + this.props.Document._forceRenderEngine = "timeline"; + this.props.Document._replacedChrome = "replaced"; this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true }); } componentWillUnmount() { this._childReaction?.(); - this._slideshowReaction?.(); } @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); } @@ -343,24 +322,27 @@ export class PresBox extends React.Component<FieldViewProps> { }); } - toggleMinimize = undoBatch(action((e: React.PointerEvent) => { - if (this.props.Document.inOverlay) { - Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); - CollectionDockingView.AddRightSplit(this.props.Document, this.props.DataDoc); - this.props.Document.inOverlay = false; - } else { - this.props.Document.x = e.clientX + 25; - this.props.Document.y = e.clientY - 25; - this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); - Doc.AddDocToList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); + updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: number) => { + const toggle = BoolCast(this.props.Document.inOverlay) !== (mode === CollectionViewType.Invalid); + if (toggle) { + if (this.props.Document.inOverlay) { + Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); + CollectionDockingView.AddRightSplit(this.props.Document, this.props.DataDoc); + this.props.Document.inOverlay = false; + } else { + this.props.Document.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25; + this.props.Document.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25; + this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + Doc.AddDocToList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); + } } })); specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._slideshow = "slideshow"), icon: "asterisk" }); - funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._slideshow = "timeline"), icon: "asterisk" }); - funcs.push({ description: "Show as List", event: action(() => this.props.Document._slideshow = undefined), icon: "asterisk" }); + funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._viewType = CollectionViewType.Carousel), icon: "asterisk" }); + funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._viewType = CollectionViewType.Time), icon: "asterisk" }); + funcs.push({ description: "Show as List", event: action(() => this.props.Document._viewType = CollectionViewType.Invalid), icon: "asterisk" }); ContextMenu.Instance.addItem({ description: "Presentation Funcs...", subitems: funcs, icon: "asterisk" }); } @@ -369,13 +351,11 @@ export class PresBox extends React.Component<FieldViewProps> { * that they will be displayed in a canvas with scale 1. */ initializeScaleViews = (docList: Doc[], viewtype: number) => { - this.props.Document._chromeStatus = "disabled"; const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; docList.forEach((doc: Doc) => { doc.presBox = this.props.Document; doc.presBoxKey = this.props.fieldKey; doc.collapsedHeight = hgt; - doc._nativeWidth = doc._nativeHeight = undefined; const curScale = NumCast(doc.viewScale, null); if (curScale === undefined) { doc.viewScale = 1; @@ -389,42 +369,48 @@ export class PresBox extends React.Component<FieldViewProps> { } getTransform = () => { - return this.props.ScreenToLocalTransform().translate(0, -50);// listBox padding-left and pres-box-cont minHeight + return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight } panelHeight = () => { return this.props.PanelHeight() - 20; } + + @undoBatch + viewChanged = action((e: React.ChangeEvent) => { + //@ts-ignore + this.props.Document._viewType = Number(e.target.selectedOptions[0].value); + this.updateMinimize(e, Number(this.props.Document._viewType)); + }); + + childLayoutTemplate = () => this.props.Document._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined; render() { - this.initializeScaleViews(this.childDocs, NumCast(this.props.Document._viewType)); - return (this.props.Document._slideshow ? - <div className="presBox-cont" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this.active() ? "all" : "none" }} > - {this.props.Document.inOverlay ? (null) : - <div className="presBox-listCont" > - {this.props.Document._slideshow === "slideshow" ? - <CollectionCarouselView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined} - moveDocument={returnFalse} - addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> - : - <CollectionTimeView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined} - moveDocument={returnFalse} - addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> - } - </div>} - <button className="presBox-backward" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> - <button className="presBox-forward" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> + const mode = NumCast(this.props.Document._viewType, CollectionViewType.Invalid); + this.initializeScaleViews(this.childDocs, mode); + return <div className="presBox-cont" onContextMenu={this.specificContextMenu} style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} > + <div className="presBox-buttons" style={{ display: this.props.Document._chromeStatus === "disabled" ? "none" : undefined }}> + <select style={{ minWidth: 50, width: "5%", height: "25", position: "relative", display: "inline-block" }} + className="collectionViewBaseChrome-viewPicker" + onPointerDown={e => e.stopPropagation()} + onChange={this.viewChanged} + value={mode}> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Invalid}>Min</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Time}>Time</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option> + </select> + <button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> + <button className="presBox-button" title={"Reset Presentation" + this.props.Document.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}> + <FontAwesomeIcon icon={this.props.Document.presStatus ? "stop" : "play"} /> + </button> + <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> + </div> + <div className="presBox-listCont" > + {mode !== CollectionViewType.Invalid ? + <CollectionView {...this.props} PanelHeight={this.panelHeight} moveDocument={returnFalse} childLayoutTemplate={this.childLayoutTemplate} + addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> + : (null) + } </div> - : <div className="presBox-cont" onContextMenu={this.specificContextMenu}> - <div className="presBox-buttons"> - <button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> - <button className="presBox-button" title={"Reset Presentation" + this.props.Document.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}> - <FontAwesomeIcon icon={this.props.Document.presStatus ? "stop" : "play"} /> - </button> - <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> - <button className="presBox-button" title={this.props.Document.inOverlay ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button> - {this.props.Document.inOverlay ? (null) : - <div className="presBox-listCont"> - <CollectionView {...this.props} whenActiveChanged={this.whenActiveChanged} PanelHeight={this.panelHeight} addDocument={this.addDocument} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> - </div>} - </div></div>); + </div>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx new file mode 100644 index 000000000..874a1108f --- /dev/null +++ b/src/client/views/nodes/SliderBox-components.tsx @@ -0,0 +1,256 @@ +import * as React from "react"; +import { SliderItem } from "react-compound-slider"; +import "./SliderBox-tooltip.css"; + +const { Component, Fragment } = React; + +// ******************************************************* +// TOOLTIP RAIL +// ******************************************************* +const railStyle: React.CSSProperties = { + position: "absolute", + width: "100%", + height: 40, + top: -13, + borderRadius: 7, + cursor: "pointer", + opacity: 0.3, + zIndex: 300, + border: "1px solid grey" +}; + +const railCenterStyle: React.CSSProperties = { + position: "absolute", + width: "100%", + height: 14, + borderRadius: 7, + cursor: "pointer", + pointerEvents: "none", + backgroundColor: "rgb(155,155,155)" +}; + +interface TooltipRailProps { + activeHandleID: string; + getRailProps: (props: object) => object; + getEventData: (e: Event) => object; +} + +export class TooltipRail extends Component<TooltipRailProps> { + state = { + value: null, + percent: null + }; + + static defaultProps = { + disabled: false + }; + + onMouseEnter = () => { + document.addEventListener("mousemove", this.onMouseMove); + } + + onMouseLeave = () => { + this.setState({ value: null, percent: null }); + document.removeEventListener("mousemove", this.onMouseMove); + } + + onMouseMove = (e: Event) => { + const { activeHandleID, getEventData } = this.props; + + if (activeHandleID) { + this.setState({ value: null, percent: null }); + } else { + this.setState(getEventData(e)); + } + } + + render() { + const { value, percent } = this.state; + const { activeHandleID, getRailProps } = this.props; + + return ( + <Fragment> + {!activeHandleID && value ? ( + <div + style={{ + left: `${percent}%`, + position: "absolute", + marginLeft: "-11px", + marginTop: "-35px" + }} + > + <div className="tooltip"> + <span className="tooltiptext">Value: {value}</span> + </div> + </div> + ) : null} + <div + style={railStyle} + {...getRailProps({ + onMouseEnter: this.onMouseEnter, + onMouseLeave: this.onMouseLeave + })} + /> + <div style={railCenterStyle} /> + </Fragment> + ); + } +} + +// ******************************************************* +// HANDLE COMPONENT +// ******************************************************* +interface HandleProps { + key: string; + handle: SliderItem; + isActive: Boolean; + disabled?: Boolean; + domain: number[]; + getHandleProps: (id: string, config: object) => object; +} + +export class Handle extends Component<HandleProps> { + static defaultProps = { + disabled: false + }; + + state = { + mouseOver: false + }; + + onMouseEnter = () => { + this.setState({ mouseOver: true }); + } + + onMouseLeave = () => { + this.setState({ mouseOver: false }); + } + + render() { + const { + domain: [min, max], + handle: { id, value, percent }, + isActive, + disabled, + getHandleProps + } = this.props; + const { mouseOver } = this.state; + + return ( + <Fragment> + {(mouseOver || isActive) && !disabled ? ( + <div + style={{ + left: `${percent}%`, + position: "absolute", + marginLeft: "-11px", + marginTop: "-35px" + }} + > + <div className="tooltip"> + <span className="tooltiptext">Value: {value}</span> + </div> + </div> + ) : null} + <div + role="slider" + aria-valuemin={min} + aria-valuemax={max} + aria-valuenow={value} + style={{ + left: `${percent}%`, + position: "absolute", + marginLeft: "-11px", + marginTop: "-6px", + zIndex: 400, + width: 24, + height: 24, + cursor: "pointer", + border: 0, + borderRadius: "50%", + boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)", + backgroundColor: disabled ? "#666" : "#3e1db3" + }} + {...getHandleProps(id, { + onMouseEnter: this.onMouseEnter, + onMouseLeave: this.onMouseLeave + })} + /> + </Fragment> + ); + } +} + +// ******************************************************* +// TRACK COMPONENT +// ******************************************************* +interface TrackProps { + source: SliderItem; + target: SliderItem; + disabled: Boolean; + getTrackProps: () => object; +} + +export function Track({ + source, + target, + getTrackProps, + disabled = false +}: TrackProps) { + return ( + <div + style={{ + position: "absolute", + height: 14, + zIndex: 1, + backgroundColor: disabled ? "#999" : "#3e1db3", + borderRadius: 7, + cursor: "pointer", + left: `${source.percent}%`, + width: `${target.percent - source.percent}%` + }} + {...getTrackProps()} + /> + ); +} + +// ******************************************************* +// TICK COMPONENT +// ******************************************************* +interface TickProps { + tick: SliderItem; + count: number; + format: (val: number) => string; +} + +const defaultFormat = (d: number) => `d`; + +export function Tick({ tick, count, format = defaultFormat }: TickProps) { + return ( + <div> + <div + style={{ + position: "absolute", + marginTop: 17, + width: 1, + height: 5, + backgroundColor: "rgb(200,200,200)", + left: `${tick.percent}%` + }} + /> + <div + style={{ + position: "absolute", + marginTop: 25, + fontSize: 10, + textAlign: "center", + marginLeft: `${-(100 / count) / 2}%`, + width: `${100 / count}%`, + left: `${tick.percent}%` + }} + > + {format(tick.value)} + </div> + </div> + ); +} diff --git a/src/client/views/nodes/SliderBox-tooltip.css b/src/client/views/nodes/SliderBox-tooltip.css new file mode 100644 index 000000000..8afde8eb5 --- /dev/null +++ b/src/client/views/nodes/SliderBox-tooltip.css @@ -0,0 +1,33 @@ +.tooltip { + position: relative; + display: inline-block; + border-bottom: 1px dotted #222; + margin-left: 22px; + } + + .tooltip .tooltiptext { + width: 100px; + background-color: #222; + color: #fff; + opacity: 0.8; + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + z-index: 1; + bottom: 150%; + left: 50%; + margin-left: -60px; + } + + .tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #222 transparent transparent transparent; + } +
\ No newline at end of file diff --git a/src/client/views/nodes/SliderBox.scss b/src/client/views/nodes/SliderBox.scss new file mode 100644 index 000000000..4ef277d8c --- /dev/null +++ b/src/client/views/nodes/SliderBox.scss @@ -0,0 +1,8 @@ +.sliderBox-outerDiv { + width: 100%; + height: 100%; + pointer-events: all; + border-radius: inherit; + display: flex; + flex-direction: column; +}
\ No newline at end of file diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx new file mode 100644 index 000000000..844d95d11 --- /dev/null +++ b/src/client/views/nodes/SliderBox.tsx @@ -0,0 +1,130 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit } from '@fortawesome/free-regular-svg-icons'; +import { computed, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Handles, Rail, Slider, Tracks, Ticks } from 'react-compound-slider'; +import { Doc } from '../../../new_fields/Doc'; +import { documentSchema } from '../../../new_fields/documentSchemas'; +import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; +import { ScriptField } from '../../../new_fields/ScriptField'; +import { BoolCast, FieldValue, StrCast, NumCast, Cast } from '../../../new_fields/Types'; +import { DragManager } from '../../util/DragManager'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { DocComponent } from '../DocComponent'; +import './SliderBox.scss'; +import { Handle, TooltipRail, Track, Tick } from './SliderBox-components'; +import { FieldView, FieldViewProps } from './FieldView'; +import { ScriptBox } from '../ScriptBox'; + + +library.add(faEdit as any); + +const SliderSchema = createSchema({ + _sliderMin: "number", + _sliderMax: "number", + _sliderMinThumb: "number", + _sliderMaxThumb: "number", +}); + +type SliderDocument = makeInterface<[typeof SliderSchema, typeof documentSchema]>; +const SliderDocument = makeInterface(SliderSchema, documentSchema); + +@observer +export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(SliderDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); } + private dropDisposer?: DragManager.DragDropDisposer; + + @computed get dataDoc() { + return this.props.DataDoc && + (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) || + this.props.DataDoc.layout === this.Document) ? this.props.DataDoc : Doc.GetProto(this.Document); + } + + specificContextMenu = (e: React.MouseEvent): void => { + const funcs: ContextMenuProps[] = []; + funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) }); + ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" }); + } + onChange = (values: readonly number[]) => runInAction(() => { + this.Document._sliderMinThumb = values[0]; + this.Document._sliderMaxThumb = values[1]; + Cast(this.Document.onThumbChanged, ScriptField, null)?.script.run({ range: values, this: this.props.Document }); + }) + + render() { + const domain = [NumCast(this.props.Document._sliderMin), NumCast(this.props.Document._sliderMax)]; + const defaultValues = [NumCast(this.props.Document._sliderMinThumb), NumCast(this.props.Document._sliderMaxThumb)]; + return ( + <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()} + style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}> + <div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{ + background: this.Document.backgroundColor, color: this.Document.color || "black", + fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "" + }} > + <Slider + mode={2} + step={1} + domain={domain} + rootStyle={{ position: "relative", width: "100%" }} + onChange={this.onChange} + values={defaultValues} + > + + <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> + <Handles> + {({ handles, activeHandleID, getHandleProps }) => ( + <div className="slider-handles"> + {handles.map((handle, i) => { + const value = i === 0 ? this.Document._sliderMinThumb : this.Document._sliderMaxThumb; + return ( + <div title={String(value)}> + <Handle + key={handle.id} + handle={handle} + domain={domain} + isActive={handle.id === activeHandleID} + getHandleProps={getHandleProps} + /> + </div> + ); + })} + </div> + )} + </Handles> + <Tracks left={false} right={false}> + {({ tracks, getTrackProps }) => ( + <div className="slider-tracks"> + {tracks.map(({ id, source, target }) => ( + <Track + key={id} + source={source} + target={target} + disabled={false} + getTrackProps={getTrackProps} + /> + ))} + </div> + )} + </Tracks> + <Ticks count={5}> + {({ ticks }) => ( + <div className="slider-tracks"> + {ticks.map((tick) => ( + <Tick + key={tick.id} + tick={tick} + count={ticks.length} + format={(val: number) => val.toString()} + /> + ))} + </div> + )} + </Ticks> + </Slider> + </div> + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index d12a8d151..6695e04c3 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -354,8 +354,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum CollectionView={undefined} ScreenToLocalTransform={this.props.ScreenToLocalTransform} renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - chromeCollapsed={true}> + ContainingCollectionDoc={this.props.ContainingCollectionDoc}> {this.contentFunc} </CollectionFreeFormView> </div> diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index a48dc286e..7e49d957d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -211,8 +211,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> CollectionView={undefined} ScreenToLocalTransform={this.props.ScreenToLocalTransform} renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - chromeCollapsed={true}> + ContainingCollectionDoc={this.props.ContainingCollectionDoc}> {() => [this.content]} </CollectionFreeFormView> </div >); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a7c1990e9..4f50be5b0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -651,8 +651,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document} - chromeCollapsed={true}> + ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}> </CollectionFreeFormView> </div>; } diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 52773d466..888707ee3 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -2,19 +2,18 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons'; import { faArrowDown, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, reaction, IReactionDisposer } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc } from "../../../new_fields/Doc"; import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; import { createSchema, makeInterface } from '../../../new_fields/Schema'; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnFalse, emptyPath } from "../../../Utils"; -import { DocumentType } from "../../documents/DocumentTypes"; +import { Cast, NumCast } from "../../../new_fields/Types"; +import { emptyFunction, emptyPath, returnFalse } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionView'; +import { DocExtendableComponent } from '../DocComponent'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; -import { DocComponent, DocExtendableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); @@ -50,15 +49,16 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } _heightDisposer: IReactionDisposer | undefined; - @computed get indexInPres() { return NumCast(this.originalLayout?.presentationIndex); } - @computed get presentationDoc() { return Cast(this.originalLayout?.presBox, Doc) as Doc; } - @computed get originalLayout() { return this.props.Document.expandedTemplate as Doc; } - @computed get targetDoc() { return this.originalLayout?.presentationTargetDoc as Doc; } - @computed get currentIndex() { return NumCast(this.presentationDoc?._itemIndex); } + @computed get indexInPres() { return NumCast(this.presElementDoc?.presentationIndex); } + @computed get presBoxDoc() { return Cast(this.presElementDoc?.presBox, Doc) as Doc; } + @computed get presElementDoc() { return this.props.Document.expandedTemplate as Doc; } + @computed get presLayoutDoc() { return this.props.Document; } + @computed get targetDoc() { return this.presElementDoc?.presentationTargetDoc as Doc; } + @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); } componentDidMount() { - this._heightDisposer = reaction(() => [this.originalLayout.embedOpen, this.originalLayout.collapsedHeight], - params => this.originalLayout._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); + this._heightDisposer = reaction(() => [this.presElementDoc.embedOpen, this.presElementDoc.collapsedHeight], + params => this.presLayoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { this._heightDisposer?.(); @@ -71,13 +71,13 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD @action onHideDocumentUntilPressClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.originalLayout.hideTillShownButton = !this.originalLayout.hideTillShownButton; - if (!this.originalLayout.hideTillShownButton) { + this.presElementDoc.hideTillShownButton = !this.presElementDoc.hideTillShownButton; + if (!this.presElementDoc.hideTillShownButton) { if (this.indexInPres >= this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 1; } } else { - if (this.presentationDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) { + if (this.presBoxDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 0; } } @@ -91,14 +91,14 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD @action onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.originalLayout.hideAfterButton = !this.originalLayout.hideAfterButton; - if (!this.originalLayout.hideAfterButton) { + this.presElementDoc.hideAfterButton = !this.presElementDoc.hideAfterButton; + if (!this.presElementDoc.hideAfterButton) { if (this.indexInPres <= this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 1; } } else { - if (this.originalLayout.fadeButton) this.originalLayout.fadeButton = false; - if (this.presentationDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) { + if (this.presElementDoc.fadeButton) this.presElementDoc.fadeButton = false; + if (this.presBoxDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 0; } } @@ -112,14 +112,14 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD @action onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.originalLayout.fadeButton = !this.originalLayout.fadeButton; - if (!this.originalLayout.fadeButton) { + this.presElementDoc.fadeButton = !this.presElementDoc.fadeButton; + if (!this.presElementDoc.fadeButton) { if (this.indexInPres <= this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 1; } } else { - this.originalLayout.hideAfterButton = false; - if (this.presentationDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) { + this.presElementDoc.hideAfterButton = false; + if (this.presBoxDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) { this.targetDoc.opacity = 0.5; } } @@ -131,11 +131,11 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD @action onNavigateDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.originalLayout.navButton = !this.originalLayout.navButton; - if (this.originalLayout.navButton) { - this.originalLayout.showButton = false; + this.presElementDoc.navButton = !this.presElementDoc.navButton; + if (this.presElementDoc.navButton) { + this.presElementDoc.showButton = false; if (this.currentIndex === this.indexInPres) { - this.props.focus(this.originalLayout); + this.props.focus(this.presElementDoc); } } } @@ -147,13 +147,13 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD onZoomDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.originalLayout.showButton = !this.originalLayout.showButton; - if (!this.originalLayout.showButton) { - this.originalLayout.viewScale = 1; + this.presElementDoc.showButton = !this.presElementDoc.showButton; + if (!this.presElementDoc.showButton) { + this.presElementDoc.viewScale = 1; } else { - this.originalLayout.navButton = false; + this.presElementDoc.navButton = false; if (this.currentIndex === this.indexInPres) { - this.props.focus(this.originalLayout); + this.props.focus(this.presElementDoc); } } } @@ -162,14 +162,14 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; - embedHeight = () => this.props.PanelHeight() - NumCast(this.originalLayout.collapsedHeight); + embedHeight = () => this.props.PanelHeight() - NumCast(this.presElementDoc.collapsedHeight); embedWidth = () => this.props.PanelWidth() - 20; /** * The function that is responsible for rendering the a preview or not for this * presentation element. */ renderEmbeddedInline = () => { - return !this.originalLayout.embedOpen || !this.targetDoc ? (null) : + return !this.presElementDoc.embedOpen || !this.targetDoc ? (null) : <div className="presElementBox-embedded" style={{ height: this.embedHeight() }}> <ContentFittingDocumentView Document={this.targetDoc} @@ -196,24 +196,24 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree; const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : ""); const pbi = "presElementBox-interaction"; - return !this.originalLayout ? (null) : ( + return !this.presElementDoc ? (null) : ( <div className={className} key={this.props.Document[Id] + this.indexInPres} style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }} - onClick={e => { this.props.focus(this.originalLayout); e.stopPropagation(); }}> + onClick={e => { this.props.focus(this.presElementDoc); e.stopPropagation(); }}> {treecontainer ? (null) : <> <strong className="presElementBox-name"> {`${this.indexInPres + 1}. ${this.targetDoc?.title}`} </strong> - <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.originalLayout)}>X</button> + <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.presElementDoc)}>X</button> <br /> </>} - <button title="Zoom" className={pbi + (this.originalLayout.showButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button> - <button title="Navigate" className={pbi + (this.originalLayout.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button> - <button title="Hide Before" className={pbi + (this.originalLayout.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button> - <button title="Fade After" className={pbi + (this.originalLayout.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button> - <button title="Hide After" className={pbi + (this.originalLayout.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button> - <button title="Group With Up" className={pbi + (this.originalLayout.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.originalLayout.groupButton = !this.originalLayout.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button> - <button title="Expand Inline" className={pbi + (this.originalLayout.embedOpen ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.originalLayout.embedOpen = !this.originalLayout.embedOpen; }}><FontAwesomeIcon icon={"arrow-down"} /></button> + <button title="Zoom" className={pbi + (this.presElementDoc.showButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button> + <button title="Navigate" className={pbi + (this.presElementDoc.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button> + <button title="Hide Before" className={pbi + (this.presElementDoc.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button> + <button title="Fade After" className={pbi + (this.presElementDoc.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button> + <button title="Hide After" className={pbi + (this.presElementDoc.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button> + <button title="Group With Up" className={pbi + (this.presElementDoc.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.groupButton = !this.presElementDoc.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button> + <button title="Expand Inline" className={pbi + (this.presElementDoc.embedOpen ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.embedOpen = !this.presElementDoc.embedOpen; }}><FontAwesomeIcon icon={"arrow-down"} /></button> <br style={{ lineHeight: 0.1 }} /> {this.renderEmbeddedInline()} |
