diff options
Diffstat (limited to 'src/client/views/collections')
15 files changed, 247 insertions, 203 deletions
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}> |
