From 8efea66fd5723becf36dd6e3b2a95435d8528748 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 21 Oct 2019 16:16:59 -0400 Subject: got rid of fieldExt from layoutstring. set directly by annotated fields --- src/client/views/nodes/DocuLinkBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes/DocuLinkBox.tsx') diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 3294a5aa2..22e44948a 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -18,7 +18,7 @@ const DocLinkDocument = makeInterface(documentSchema); @observer export class DocuLinkBox extends DocComponent(DocLinkDocument) { - public static LayoutString(fieldKey: string, fieldExt?: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey, fieldExt); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey); } _downx = 0; _downy = 0; @observable _x = 0; -- cgit v1.2.3-70-g09d2 From 891b9706ddabc0a73ea6b25dc504297d6efb90fe Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 21 Oct 2019 22:03:02 -0400 Subject: big cleanup of layoutStrings, fieldExt, fieldKey, etc --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/documents/Documents.ts | 55 ++++---- src/client/northstar/dash-nodes/HistogramBox.tsx | 2 +- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/views/CollectionLinearView.tsx | 4 +- src/client/views/DocComponent.tsx | 45 +++---- src/client/views/DocumentDecorations.tsx | 13 +- src/client/views/MainView.tsx | 5 +- .../views/collections/CollectionSchemaView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 2 - src/client/views/collections/CollectionSubView.tsx | 9 +- src/client/views/collections/CollectionView.tsx | 18 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++-- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 8 +- src/client/views/nodes/ButtonBox.tsx | 20 +-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 18 +-- src/client/views/nodes/ColorBox.tsx | 10 +- src/client/views/nodes/DocuLinkBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 71 ++-------- src/client/views/nodes/FieldView.tsx | 5 +- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 27 ++-- src/client/views/nodes/IconBox.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 18 +-- src/client/views/nodes/KeyValueBox.tsx | 5 +- src/client/views/nodes/PDFBox.tsx | 8 +- src/client/views/nodes/PresBox.tsx | 2 +- src/client/views/nodes/QueryBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 40 +++--- src/client/views/nodes/WebBox.tsx | 9 +- src/client/views/pdf/PDFViewer.tsx | 17 ++- .../views/presentationview/PresElementBox.tsx | 148 ++++++++++----------- src/new_fields/Doc.ts | 3 +- src/new_fields/documentSchemas.ts | 51 +++++++ 36 files changed, 319 insertions(+), 348 deletions(-) create mode 100644 src/new_fields/documentSchemas.ts (limited to 'src/client/views/nodes/DocuLinkBox.tsx') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index d73988bb8..bed812852 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -40,7 +40,7 @@ export class YoutubeBox extends React.Component { @observable curVideoTemplates: VideoTemplate[] = []; - public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(YoutubeBox, fieldKey); } /** * When component mounts, last search's results are laoded in based on the back up stored diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 937d3c058..a400e68a3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -84,7 +84,8 @@ export interface DocumentOptions { columnWidth?: number; fontSize?: number; curPage?: number; - currentTimecode?: number; + currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) + displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) documentText?: string; borderRounding?: string; boxShadow?: string; @@ -119,11 +120,11 @@ export namespace Docs { export namespace Prototypes { - type LayoutSource = { LayoutString: (ext?: string) => string }; + type LayoutSource = { LayoutString: (key: string) => string }; type PrototypeTemplate = { layout: { view: LayoutSource, - ext?: string, // optional extension field for layout source + dataField: string }, options?: Partial }; @@ -133,80 +134,80 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.TEXT, { - layout: { view: FormattedTextBox }, + layout: { view: FormattedTextBox, dataField: data }, options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" } }], [DocumentType.HIST, { - layout: { view: HistogramBox }, + layout: { view: HistogramBox, dataField: data }, options: { height: 300, backgroundColor: "black" } }], [DocumentType.QUERY, { - layout: { view: QueryBox }, + layout: { view: QueryBox, dataField: data }, options: { width: 400 } }], [DocumentType.COLOR, { - layout: { view: ColorBox }, + layout: { view: ColorBox, dataField: data }, options: { nativeWidth: 220, nativeHeight: 300 } }], [DocumentType.IMG, { - layout: { view: ImageBox }, + layout: { view: ImageBox, dataField: data }, options: {} }], [DocumentType.WEB, { - layout: { view: WebBox }, + layout: { view: WebBox, dataField: data }, options: { height: 300 } }], [DocumentType.COL, { - layout: { view: CollectionView }, + layout: { view: CollectionView, dataField: data }, options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 } }], [DocumentType.KVP, { - layout: { view: KeyValueBox }, + layout: { view: KeyValueBox, dataField: data }, options: { height: 150 } }], [DocumentType.VID, { - layout: { view: VideoBox }, + layout: { view: VideoBox, dataField: data }, options: { currentTimecode: 0 }, }], [DocumentType.AUDIO, { - layout: { view: AudioBox }, + layout: { view: AudioBox, dataField: data }, options: { height: 32 } }], [DocumentType.PDF, { - layout: { view: PDFBox }, + layout: { view: PDFBox, dataField: data }, options: { nativeWidth: 1200, curPage: 1 } }], [DocumentType.ICON, { - layout: { view: IconBox }, + layout: { view: IconBox, dataField: data }, options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }, }], [DocumentType.IMPORT, { - layout: { view: DirectoryImportBox }, + layout: { view: DirectoryImportBox, dataField: data }, options: { height: 150 } }], [DocumentType.LINKDOC, { data: new List(), - layout: { view: EmptyBox }, + layout: { view: EmptyBox, dataField: data }, }], [DocumentType.YOUTUBE, { - layout: { view: YoutubeBox } + layout: { view: YoutubeBox, dataField: data } }], [DocumentType.BUTTON, { - layout: { view: ButtonBox }, + layout: { view: ButtonBox, dataField: data }, }], [DocumentType.PRES, { - layout: { view: PresBox }, + layout: { view: PresBox, dataField: data }, options: {} }], [DocumentType.FONTICON, { - layout: { view: FontIconBox }, + layout: { view: FontIconBox, dataField: data }, options: { width: 40, height: 40, borderRounding: "100%" }, }], [DocumentType.LINKFOLLOW, { - layout: { view: LinkFollowBox } + layout: { view: LinkFollowBox, dataField: data } }], [DocumentType.PRESELEMENT, { - layout: { view: PresElementBox } + layout: { view: PresElementBox, dataField: data } }], ]); @@ -285,7 +286,7 @@ export namespace Docs { // synthesize the default options, the type and title from computed values and // whatever options pertain to this specific prototype let options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; - options.layout = layout.view.LayoutString(); + options.layout = layout.view.LayoutString(layout.dataField); return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: options.layout }); } @@ -701,12 +702,12 @@ export namespace DocUtils { linkDocProto.linkDescription = description; linkDocProto.anchor1 = source.doc; - linkDocProto.anchor1Context = source.ctx; - linkDocProto.anchor1Timecode = source.doc.currentTimecode; - linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2 = target.doc; + linkDocProto.anchor1Context = source.ctx; linkDocProto.anchor2Context = target.ctx; + linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2Groups = new List([]); + linkDocProto.anchor1Timecode = source.doc.currentTimecode; linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index b81eafbee..854135648 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -24,7 +24,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; @observer export class HistogramBox extends React.Component { - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(HistogramBox, fieldStr); } + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(HistogramBox, fieldStr); } private _dropXRef = React.createRef(); private _dropYRef = React.createRef(); private _dropXDisposer?: DragManager.DragDropDisposer; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index d74b51993..f27d05487 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -50,7 +50,7 @@ export default class DirectoryImportBox extends React.Component @observable private uploading = false; @observable private removeHover = false; - public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } constructor(props: FieldViewProps) { super(props); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 4e03b8c95..1f28ef35d 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -10,7 +10,8 @@ import { Transform } from '../util/Transform'; import "./CollectionLinearView.scss"; import { CollectionViewType } from './collections/CollectionBaseView'; import { CollectionSubView } from './collections/CollectionSubView'; -import { documentSchema, DocumentView } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { documentSchema } from '../../new_fields/documentSchemas'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -75,7 +76,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ //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 dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } - @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); } } return Component; } - -/// DocStaticProps return a base class for React views of document fields that are interactive only when selected (e.g. ColorBox) -interface DocStaticProps { +/// DocStaticProps return a base class for React document views that have data extensions but aren't annotatable (e.g. AudioBox, FormattedTextBox) +interface DocExtendableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; isSelected: () => boolean; renderDepth: number; } -export function DocStaticComponent

(schemaCtor: (doc: Doc) => T) { +export function DocExtendableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ //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 dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } + @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools } @@ -59,23 +51,20 @@ interface DocAnnotatableProps { isSelected: () => boolean; renderDepth: number; } -export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T, fieldExt: string) { +export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ _isChildActive = false; - _fieldExt = fieldExt; //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 Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get fieldExt() { return this._fieldExt; } + @computed get annotationsKey() { return "annotations"; } @action.bound removeDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = undefined; - let value = this.extensionDoc && Cast(this.extensionDoc[this._fieldExt], listSpec(Doc), []); + let value = this.extensionDoc && Cast(this.extensionDoc[this.annotationsKey], listSpec(Doc), []); let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1; return index !== -1 && value && value.splice(index, 1) ? true : false; } @@ -88,7 +77,7 @@ export function DocAnnotatableComponent

(schema @action.bound addDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = this.props.Document; - return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this._fieldExt, doc) ? true : false; + return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this.annotationsKey, doc) ? true : false; } whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 252f90d46..b46caf3ea 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -4,27 +4,26 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; +import { PositionDocument } from '../../new_fields/documentSchemas'; import { List } from "../../new_fields/List"; import { ObjectField } from '../../new_fields/ObjectField'; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../new_fields/Types"; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { emptyFunction, Utils } from "../../Utils"; +import { Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; +import { TooltipTextMenu } from '../util/TooltipTextMenu'; import { undoBatch, UndoManager } from "../util/UndoManager"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { CollectionView } from "./collections/CollectionView"; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; -import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); -import { TooltipTextMenu } from '../util/TooltipTextMenu'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -281,7 +280,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd); if (selectedDocs.length > 1) { - this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString()); + this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString("")); this.moveIconDoc(this._iconDoc); } else { this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon)); @@ -339,7 +338,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc); if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) { - const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView)); + const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView, "")); iconDoc = this.createIcon([docView], layout); } return iconDoc; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d4b92a110..55a61f098 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -270,7 +270,6 @@ export class MainView extends React.Component { doc) { this._heightMap.set(key, sectionHeight); } - get layoutDoc() { return Doc.Layout(this.props.Document); } - get Sections() { if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7f16fe9e0..55365de8c 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -41,7 +41,7 @@ export interface SubCollectionViewProps extends CollectionViewProps { ruleProvider: Doc | undefined; children?: never | (() => JSX.Element[]) | React.ReactNode; isAnnotationOverlay?: boolean; - fieldExt: string; + annotationsKey: string; } export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @@ -74,12 +74,15 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this._childLayoutDisposer && this._childLayoutDisposer(); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through // to its children which may be templates. - // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. + // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { - return this.props.fieldExt ? (this.extensionDoc ? this.extensionDoc[this.props.fieldExt] : undefined) : this.dataDoc[this.props.fieldKey]; + return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey]; } get childLayoutPairs() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f3b0b17f8..f8eb28ade 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -32,7 +32,7 @@ library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faF @observer export class CollectionView extends React.Component { - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); } + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _reactionDisposer: IReactionDisposer | undefined; @observable private _isLightboxOpen = false; @@ -63,18 +63,18 @@ export class CollectionView extends React.Component { private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { let props = { ...this.props, ...renderProps }; switch (type) { - case CollectionViewType.Schema: return (); + case CollectionViewType.Schema: return (); // 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 - case CollectionViewType.Docking: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } - case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } - case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } - case CollectionViewType.Linear: { return (); } + case CollectionViewType.Docking: return (); + case CollectionViewType.Tree: return (); + case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } + case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } + case CollectionViewType.Linear: { return (); } case CollectionViewType.Freeform: default: this.props.Document.freeformLayoutEngine = undefined; - return (); + return (); } return (null); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cc89dc2d4..0419bc3fa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -25,8 +25,8 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; -import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import { CollectionSubView } from "../CollectionSubView"; @@ -35,6 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { documentSchema, positionSchema } from "../../DocComponent"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -677,13 +678,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey. // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document - return ( + return !this.extensionDoc ? (null) :

- + {!this.extensionDoc ? (null) : @@ -694,8 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { {this.overlayViews} -
- ); + ; } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3eaad3ad7..e0bf4a2f0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,23 +19,24 @@ import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); +import { SubCollectionViewProps } from "../CollectionSubView"; interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; - container: CollectionFreeFormView; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; selectDocuments: (docs: Doc[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; + extensionDoc: Doc; isAnnotationOverlay?: boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @observer -export class MarqueeView extends React.Component +export class MarqueeView extends React.Component { private _mainCont = React.createRef(); @observable _lastX: number = 0; @@ -187,13 +188,13 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + if (!this.props.active()) this.props.selectDocuments([this.props.Document]); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + this.props.selectDocuments(mselect.length ? mselect : [this.props.Document]); } this.cleanupInteractions(true); @@ -246,12 +247,11 @@ export class MarqueeView extends React.Component } get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. - return this.props.container.extensionDoc && Cast(this.props.container.extensionDoc.ink, InkField); + return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField); } set ink(value: InkField | undefined) { - let cprops = this.props.container.props; - this.props.container.extensionDoc && (this.props.container.extensionDoc.ink = value); + this.props.extensionDoc && (this.props.extensionDoc.ink = value); } @undoBatch @@ -290,11 +290,11 @@ export class MarqueeView extends React.Component } let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); - let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); + let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.Document.colorPalette = new List(defaultPalette); + let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map(); - [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { + [...this.props.activeDocuments(), this.props.Document].map(child => { let bg = StrCast(Doc.Layout(child).backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 32ebe7c61..ef194624a 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -37,7 +37,7 @@ enum FollowOptions { @observer export class LinkFollowBox extends React.Component { - public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); } public static Instance: LinkFollowBox | undefined; @observable static linkDoc: Doc | undefined = undefined; @observable static destinationDoc: Doc | undefined = undefined; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 3e5deb55b..4c1c3a465 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -4,18 +4,18 @@ import { observer } from "mobx-react"; import "./AudioBox.scss"; import { Cast } from "../../../new_fields/Types"; import { AudioField } from "../../../new_fields/URLField"; -import { DocStaticComponent } from "../DocComponent"; +import { DocExtendableComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -import { documentSchema } from "./DocumentView"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type AudioDocument = makeInterface<[typeof documentSchema]>; const AudioDocument = makeInterface(documentSchema); const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3")); @observer -export class AudioBox extends DocStaticComponent(AudioDocument) { +export class AudioBox extends DocExtendableComponent(AudioDocument) { - public static LayoutString() { return FieldView.LayoutString(AudioBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } _ref = React.createRef(); componentDidMount() { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index b4d33fb0f..1531d825b 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -3,11 +3,11 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons'; import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCastAsync, DocListCast } from '../../../new_fields/Doc'; +import { Doc, DocListCast } from '../../../new_fields/Doc'; import { List } from '../../../new_fields/List'; import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, StrCast, Cast } from '../../../new_fields/Types'; +import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; @@ -15,26 +15,28 @@ import './ButtonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; +import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit as any); const ButtonSchema = createSchema({ onClick: ScriptField, + buttonParams: listSpec("string"), text: "string" }); -type ButtonDocument = makeInterface<[typeof ButtonSchema]>; -const ButtonDocument = makeInterface(ButtonSchema); +type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>; +const ButtonDocument = makeInterface(ButtonSchema, documentSchema); @observer export class ButtonBox extends DocComponent(ButtonDocument) { - public static LayoutString() { return FieldView.LayoutString(ButtonBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); } private dropDisposer?: DragManager.DragDropDisposer; @computed get dataDoc() { return this.props.DataDoc && - (BoolCast(this.props.Document.isTemplateField) || BoolCast(this.props.DataDoc.isTemplateField) || + (this.Document.isTemplateField || BoolCast(this.props.DataDoc.isTemplateField) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } @@ -52,7 +54,7 @@ export class ButtonBox extends DocComponent(Butt let funcs: ContextMenuProps[] = []; funcs.push({ description: "Clear Script Params", event: () => { - let params = Cast(this.props.Document.buttonParams, listSpec("string")); + let params = FieldValue(this.Document.buttonParams); params && params.map(p => this.props.Document[p] = undefined); }, icon: "trash" }); @@ -70,13 +72,13 @@ export class ButtonBox extends DocComponent(Butt } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { - let params = Cast(this.props.Document.buttonParams, listSpec("string")); + let params = this.Document.buttonParams; let missingParams = params && params.filter(p => this.props.Document[p] === undefined); params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... return (
-
+
{(this.Document.text || this.Document.title)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index e3ca02fa4..58cb831f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -2,14 +2,15 @@ import { random } from "animejs"; import { computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { percent2frac } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import "./CollectionFreeFormDocumentView.scss"; -import { documentSchema, DocumentView, DocumentViewProps } from "./DocumentView"; +import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); +import { PositionDocument } from "../../../new_fields/documentSchemas"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; @@ -20,15 +21,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { jitterRotation: number; transition?: string; } -export const positionSchema = createSchema({ - zIndex: "number", - x: "number", - y: "number", - z: "number", -}); - -export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; -export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { @@ -92,8 +84,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; - get layoutDoc() { return Doc.Layout(this.props.Document); } - @observable _animPos: number[] | undefined = undefined; finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth(); diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index fdcedb3a5..fda6d64f4 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -4,20 +4,20 @@ import { SketchPicker } from 'react-color'; import { FieldView, FieldViewProps } from './FieldView'; import "./ColorBox.scss"; import { InkingControl } from "../InkingControl"; -import { DocStaticComponent } from "../DocComponent"; -import { documentSchema } from "./DocumentView"; +import { DocExtendableComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -import { trace, reaction, observable, action, IReactionDisposer } from "mobx"; +import { reaction, observable, action, IReactionDisposer } from "mobx"; import { SelectionManager } from "../../util/SelectionManager"; import { StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type ColorDocument = makeInterface<[typeof documentSchema]>; const ColorDocument = makeInterface(documentSchema); @observer -export class ColorBox extends DocStaticComponent(ColorDocument) { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); } +export class ColorBox extends DocExtendableComponent(ColorDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); } _selectedDisposer: IReactionDisposer | undefined; _penDisposer: IReactionDisposer | undefined; diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 22e44948a..7119b0db0 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -7,11 +7,11 @@ import { Utils } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragLinksAsDocuments } from "../../util/DragManager"; import { DocComponent } from "../DocComponent"; -import { documentSchema } from "./DocumentView"; import "./DocuLinkBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); import { DocumentType } from "../../documents/DocumentTypes"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type DocLinkSchema = makeInterface<[typeof documentSchema]>; const DocLinkDocument = makeInterface(documentSchema); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9e36f0811..f741dae9a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,29 +1,37 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction, trace, observable } 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 } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; +import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { ImageField } from '../../../new_fields/URLField'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnTrue, Utils, returnTransparent, returnOne } from "../../../Utils"; +import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils"; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; import { LinkManager } from '../../util/LinkManager'; +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/CollectionBaseView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; +import { DictationOverlay } from '../DictationOverlay'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { OverlayView } from '../OverlayView'; @@ -33,13 +41,6 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; -import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; -import { ImageField } from '../../../new_fields/URLField'; -import SharingManager from '../../util/SharingManager'; -import { Scripting } from '../../util/Scripting'; -import { DictationOverlay } from '../DictationOverlay'; -import { CollectionViewType } from '../collections/CollectionBaseView'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -67,7 +68,6 @@ library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCom export interface DocumentViewProps { ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; - fieldKey: string; Document: Doc; DataDoc?: Doc; fitToBox?: boolean; @@ -96,41 +96,6 @@ export interface DocumentViewProps { layoutKey?: string; } -export const documentSchema = createSchema({ - // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out - title: "string", // document title (can be on either data document or layout) - nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set - nativeHeight: "number", // " - width: "number", // width of document in its container's coordinate system - height: "number", // " - backgroundColor: "string", // background color of document - opacity: "number", // opacity of document - //links: listSpec(Doc), // computed (readonly) list of links associated with this document - dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") - removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped - onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) - onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. - ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document - autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents - isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed - isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) - type: "string", // enumerated type of document - maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) - lockedPosition: "boolean", // whether the document can be spatially manipulated - inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently - borderRounding: "string", // border radius rounding of document - searchFields: "string", // the search fields to display when this document matches a search in its metadata - heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) - showCaption: "string", // whether editable caption text is overlayed at the bottom of the document - showTitle: "string", // whether an editable title banner is displayed at tht top of the document - isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) - ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) -}); - - -type Document = makeInterface<[typeof documentSchema]>; -const Document = makeInterface(documentSchema); @observer export class DocumentView extends DocComponent(Document) { @@ -572,13 +537,6 @@ export class DocumentView extends DocComponent(Docu }); } - - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field. In that case, all layout information comes from there unless overriden by Document - get layoutDoc(): Document { - return Document(Doc.Layout(this.props.Document)); - } - // does Document set a layout prop setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. @@ -599,7 +557,6 @@ export class DocumentView extends DocComponent(Docu return ((Docu } } -Scripting.addGlobal(function toggleDetail(doc: any) { - doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; -}); \ No newline at end of file +Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; }); \ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 7d69b5b51..5108954bb 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -52,9 +52,8 @@ export interface FieldViewProps { @observer export class FieldView extends React.Component { - public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") { - return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; - //"" + public static LayoutString(fieldType: { name: string }, fieldStr: string) { + return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; //e.g., "" } @computed diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index fd6a475fb..ae9273639 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -17,7 +17,7 @@ type FontIconDocument = makeInterface<[typeof FontIconSchema]>; const FontIconDocument = makeInterface(FontIconSchema); @observer export class FontIconBox extends DocComponent(FontIconDocument) { - public static LayoutString() { return FieldView.LayoutString(FontIconBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } @observable _foregroundColor = "white"; _ref: React.RefObject = React.createRef(); _backgroundReaction: IReactionDisposer | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 995fcee17..5c2d39d98 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -33,7 +33,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocComponent } from "../DocComponent"; +import { DocExtendableComponent } from "../DocComponent"; import { DocumentButtonBar } from '../DocumentButtonBar'; import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; @@ -44,6 +44,7 @@ import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -62,16 +63,14 @@ const richTextSchema = createSchema({ export const GoogleRef = "googleDocId"; -type RichTextDocument = makeInterface<[typeof richTextSchema]>; -const RichTextDocument = makeInterface(richTextSchema); +type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; +const RichTextDocument = makeInterface(richTextSchema, documentSchema); type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { - public static LayoutString(fieldStr: string = "data") { - return FieldView.LayoutString(FormattedTextBox, fieldStr); - } +export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; @@ -135,17 +134,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return true; } - constructor(props: FieldViewProps) { + constructor(props: any) { super(props); FormattedTextBox.Instance = this; } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field. In that case, all layout information comes from there unless overriden by Document - @computed get layoutDoc(): Doc { return Doc.Layout(this.props.Document); } - linkOnDeselect: Map = new Map(); doLinkOnDeselect() { @@ -266,8 +261,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe newLayout = Doc.MakeDelegate(draggedDoc); newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); } - this.props.Document.layoutCustom = newLayout; - this.props.Document.layoutKey = "layoutCustom"; + this.Document.layoutCustom = newLayout; + this.Document.layoutKey = "layoutCustom"; e.stopPropagation(); // embed document when dragging with a userDropAction or an embedDoc flag set } else if (de.data.userDropAction || de.data.embedDoc) { @@ -1015,7 +1010,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - let nh = this.props.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); + let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.layoutDoc.height, 0); this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; @@ -1052,7 +1047,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
+
{ diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 4971f61b7..60f547b1e 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -24,7 +24,7 @@ library.add(faFilm, faTag, faTextHeight); @observer export class IconBox extends React.Component { - public static LayoutString() { return FieldView.LayoutString(IconBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(IconBox, fieldKey); } @observable _panelWidth: number = 0; @observable _panelHeight: number = 0; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4d623a04f..4c2f8e22d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -20,12 +20,12 @@ import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from '../DocComponent'; import { InkingControl } from '../InkingControl'; -import { documentSchema } from './DocumentView'; import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { documentSchema } from '../../../new_fields/documentSchemas'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -38,7 +38,9 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", fitWidth: "boolean", - rotation: "number" + rotation: "number", + googlePhotosUrl: "string", + googlePhotosTags: "string" }); interface Window { @@ -54,8 +56,8 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); @observer -export class ImageBox extends DocAnnotatableComponent(ImageDocument, "annotations") { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(ImageBox, fieldKey); } +export class ImageBox extends DocAnnotatableComponent(ImageDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; @observable private _audioState = 0; @@ -251,7 +253,7 @@ export class ImageBox extends DocAnnotatableComponent this.recordAudioAnnotation(); considerGooglePhotosLink = () => { - const remoteUrl = StrCast(this.props.Document.googlePhotosUrl); + const remoteUrl = this.Document.googlePhotosUrl; return !remoteUrl ? (null) : ( { - const tags = StrCast(this.props.Document.googlePhotosTags); + const tags = this.Document.googlePhotosTags; return !tags ? (null) : (); } @@ -287,7 +289,7 @@ export class ImageBox extends DocAnnotatableComponent { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); } + private _mainCont = React.createRef(); private _keyHeader = React.createRef(); - @observable private rows: KeyValuePair[] = []; - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); } + @observable private rows: KeyValuePair[] = []; @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b1110daf4..396a5356a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -16,18 +16,18 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from "../DocComponent"; import { PDFViewer } from "../pdf/PDFViewer"; -import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { documentSchema } from '../../../new_fields/documentSchemas'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer -export class PDFBox extends DocAnnotatableComponent(PdfDocument, "annotations") { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(PDFBox, fieldKey); } +export class PDFBox extends DocAnnotatableComponent(PdfDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; @@ -232,7 +232,7 @@ export class PDFBox extends DocAnnotatableComponent pinToPres={this.props.pinToPres} addDocument={this.addDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} - fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} /> + fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} /> {this.settingsPanel()}
); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 15fafb022..7ec5d0471 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -31,7 +31,7 @@ library.add(faEdit); @observer export class PresBox extends React.Component { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { this._docListChangedReaction = reaction(() => { diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index ced597b59..99b5810fc 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -18,7 +18,7 @@ library.add(faEdit); @observer export class QueryBox extends React.Component { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(QueryBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 64871ef41..48a699e58 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,33 +1,32 @@ import React = require("react"); -import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from "mobx"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faVideo } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; +import { Doc } from "../../../new_fields/Doc"; import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; -import { Utils, emptyFunction, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { DocAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; -import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faVideo } from "@fortawesome/free-solid-svg-icons"; -import { Doc } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { positionSchema } from "./CollectionFreeFormDocumentView"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; var path = require('path'); export const timeSchema = createSchema({ - currentTimecode: "number", + currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first }); type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>; const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); @@ -35,7 +34,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); library.add(faVideo); @observer -export class VideoBox extends DocAnnotatableComponent(VideoDocument, "annotations") { +export class VideoBox extends DocAnnotatableComponent(VideoDocument) { static _youtubeIframeCounter: number = 0; private _reactionDisposer?: IReactionDisposer; private _youtubeReactionDisposer?: IReactionDisposer; @@ -49,7 +48,7 @@ export class VideoBox extends DocAnnotatableComponent this._playing ? this.Pause() : this.Play() + onPlayDown = () => this._playing ? this.Pause() : this.Play(); - @action onFullDown = (e: React.PointerEvent) => { this.FullScreen(); e.stopPropagation(); e.preventDefault(); } - @action onSnapshot = (e: React.PointerEvent) => { this.Snapshot(); e.stopPropagation(); e.preventDefault(); } - @action onResetDown = (e: React.PointerEvent) => { this.Pause(); e.stopPropagation(); @@ -309,12 +304,12 @@ export class VideoBox extends DocAnnotatableComponent { this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY); this.Seek(Math.max(0, (this.Document.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); e.stopImmediatePropagation(); } + @action onResetUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onResetMove, true); @@ -322,7 +317,6 @@ export class VideoBox extends DocAnnotatableComponent; const WebDocument = makeInterface(documentSchema); @observer -export class WebBox extends DocAnnotatableComponent(WebDocument, "annotations") { +export class WebBox extends DocAnnotatableComponent(WebDocument) { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(WebBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @observable private collapsed: boolean = true; @observable private url: string = ""; - get layoutDoc() { return Doc.Layout(this.props.Document); } componentWillMount() { let field = Cast(this.props.Document[this.props.fieldKey], WebField); @@ -199,7 +198,7 @@ export class WebBox extends DocAnnotatableComponent ; - extensionDoc: Doc; PanelWidth: () => number; PanelHeight: () => number; ContentScaling: () => number; @@ -73,7 +72,7 @@ interface IViewerProps { * Handles rendering and virtualization of the pdf */ @observer -export class PDFViewer extends DocAnnotatableComponent(PdfDocument, "annotations") { +export class PDFViewer extends DocAnnotatableComponent(PdfDocument) { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @@ -109,8 +108,8 @@ export class PDFViewer extends DocAnnotatableComponent this._script.run({ this: anno }, console.log, true).result); + return this.extensionDoc ? DocListCast(this.extensionDoc.annotations).filter( + anno => this._script.run({ this: anno }, console.log, true).result) : []; } @computed get nonDocAnnotations() { @@ -200,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations), + () => this.extensionDoc && DocListCast(this.extensionDoc.annotations), annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), { fireImmediately: true }); @@ -629,10 +628,10 @@ export class PDFViewer extends DocAnnotatableComponent {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => - )} + )}
(this.Document.scrollHeight || this.Document.nativeHeight || 0)} PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0)} @@ -673,7 +672,7 @@ export class PDFViewer extends DocAnnotatableComponent this._marqueeing; visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; render() { - return (
{this.pdfViewerDiv} {this.annotationLayer} diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 0824808eb..5f758f496 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -5,18 +5,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; -import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils"; +import { createSchema, makeInterface } from '../../../new_fields/Schema'; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnFalse } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionBaseView'; -import { DocumentView } from "../nodes/DocumentView"; +import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; +import { DocComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); -import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; - library.add(faArrowUp); library.add(fileSolid); @@ -24,34 +25,34 @@ library.add(faLocationArrow); library.add(fileRegular as any); library.add(faSearch); library.add(faArrowDown); + +export const presSchema = createSchema({ + presentationTargetDoc: Doc, + presBox: Doc, + presBoxKey: "string", + showButton: "boolean", + navButton: "boolean", + hideTillShownButton: "boolean", + fadeButton: "boolean", + hideAfterButton: "boolean", + groupButton: "boolean", + embedOpen: "boolean" +}); + +type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>; +const PresDocument = makeInterface(presSchema, documentSchema); /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. */ @observer -export class PresElementBox extends React.Component { +export class PresElementBox extends DocComponent(PresDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } - public static LayoutString() { return FieldView.LayoutString(PresElementBox); } - - @computed get myIndex() { return DocListCast(this.presentationDoc[this.presentationFieldKey]).indexOf(this.props.Document); } - @computed get presentationDoc() { return this.props.Document.presBox as Doc; } - @computed get presentationFieldKey() { return StrCast(this.props.Document.presBoxKey); } + @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); } + @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; } + @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc } @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); } - @computed get showButton() { return BoolCast(this.props.Document.showButton); } - @computed get navButton() { return BoolCast(this.props.Document.navButton); } - @computed get hideTillShownButton() { return BoolCast(this.props.Document.hideTillShownButton); } - @computed get fadeButton() { return BoolCast(this.props.Document.fadeButton); } - @computed get hideAfterButton() { return BoolCast(this.props.Document.hideAfterButton); } - @computed get groupButton() { return BoolCast(this.props.Document.groupButton); } - @computed get embedOpen() { return BoolCast(this.props.Document.embedOpen); } - - set embedOpen(value: boolean) { this.props.Document.embedOpen = value; } - set showButton(val: boolean) { this.props.Document.showButton = val; } - set navButton(val: boolean) { this.props.Document.navButton = val; } - set hideTillShownButton(val: boolean) { this.props.Document.hideTillShownButton = val; } - set fadeButton(val: boolean) { this.props.Document.fadeButton = val; } - set hideAfterButton(val: boolean) { this.props.Document.hideAfterButton = val; } - set groupButton(val: boolean) { this.props.Document.groupButton = val; } /** * The function that is called on click to turn Hiding document till press option on/off. @@ -60,14 +61,14 @@ export class PresElementBox extends React.Component { @action onHideDocumentUntilPressClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.hideTillShownButton = !this.hideTillShownButton; - if (!this.hideTillShownButton) { - if (this.myIndex >= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.hideTillShownButton = !this.Document.hideTillShownButton; + if (!this.Document.hideTillShownButton) { + if (this.indexInPres >= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0; + if (this.presentationDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 0; } } } @@ -80,15 +81,15 @@ export class PresElementBox extends React.Component { @action onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.hideAfterButton = !this.hideAfterButton; - if (!this.hideAfterButton) { - if (this.myIndex <= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.hideAfterButton = !this.Document.hideAfterButton; + if (!this.Document.hideAfterButton) { + if (this.indexInPres <= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - if (this.fadeButton) this.fadeButton = false; - if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0; + if (this.Document.fadeButton) this.Document.fadeButton = false; + if (this.presentationDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 0; } } } @@ -101,15 +102,15 @@ export class PresElementBox extends React.Component { @action onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.fadeButton = !this.fadeButton; - if (!this.fadeButton) { - if (this.myIndex <= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.fadeButton = !this.Document.fadeButton; + if (!this.Document.fadeButton) { + if (this.indexInPres <= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - this.hideAfterButton = false; - if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5; + this.Document.hideAfterButton = false; + if (this.presentationDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) { + this.targetDoc.opacity = 0.5; } } } @@ -120,10 +121,10 @@ export class PresElementBox extends React.Component { @action onNavigateDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.navButton = !this.navButton; - if (this.navButton) { - this.showButton = false; - if (this.currentIndex === this.myIndex) { + this.Document.navButton = !this.Document.navButton; + if (this.Document.navButton) { + this.Document.showButton = false; + if (this.currentIndex === this.indexInPres) { this.props.focus(this.props.Document); } } @@ -136,12 +137,12 @@ export class PresElementBox extends React.Component { onZoomDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.showButton = !this.showButton; - if (!this.showButton) { + this.Document.showButton = !this.Document.showButton; + if (!this.Document.showButton) { this.props.Document.viewScale = 1; } else { - this.navButton = false; - if (this.currentIndex === this.myIndex) { + this.Document.navButton = false; + if (this.currentIndex === this.indexInPres) { this.props.focus(this.props.Document); } } @@ -151,13 +152,12 @@ export class PresElementBox extends React.Component { */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; - get layoutDoc() { return Doc.Layout(this.props.Document); } /** * The function that is responsible for rendering the a preview or not for this * presentation element. */ renderEmbeddedInline = () => { - if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) { + if (!this.Document.embedOpen || !this.targetDoc) { return (null); } @@ -170,8 +170,9 @@ export class PresElementBox extends React.Component { width: propDocWidth === 0 ? "auto" : propDocWidth * scale(), }}> { } render() { - let p = this.props; - let treecontainer = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.viewType === CollectionViewType.Tree; - let className = "presElementBox-item" + (this.currentIndex === this.myIndex ? " presElementBox-selected" : ""); + let className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : ""); let pbi = "presElementBox-interaction"; return ( -
{ p.focus(p.Document); e.stopPropagation(); }}> +
{ this.props.focus(this.props.Document); e.stopPropagation(); }}> {treecontainer ? (null) : <> - {`${this.myIndex + 1}. ${p.Document.title}`} + {`${this.indexInPres + 1}. ${this.Document.title}`} - +
- - } - - - - - - - + } + + + + + + +
{this.renderEmbeddedInline()} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index c6f654c33..08cb66d5f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -645,7 +645,8 @@ export namespace Doc { @observable BrushedDoc: ObservableMap = new ObservableMap(); } - // returns the active layout document for 'doc'. + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; } export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } const manager = new DocData(); diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts new file mode 100644 index 000000000..8c3b62067 --- /dev/null +++ b/src/new_fields/documentSchemas.ts @@ -0,0 +1,51 @@ +import { makeInterface, createSchema, listSpec } from "./Schema"; +import { ScriptField } from "./ScriptField"; +import { Doc } from "./Doc"; + +export const documentSchema = createSchema({ + // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out + layoutKey: "string", // holds the field key for the field that actually holds the current lyoat + layoutCustom: Doc, // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey') + title: "string", // document title (can be on either data document or layout) + nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set + nativeHeight: "number", // " + width: "number", // width of document in its container's coordinate system + height: "number", // " + color: "string", // foreground color of document + backgroundColor: "string", // background color of document + opacity: "number", // opacity of document + //links: listSpec(Doc), // computed (readonly) list of links associated with this document + dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") + removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped + onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. + ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document + autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents + isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed + isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) + type: "string", // enumerated type of document + maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) + lockedPosition: "boolean", // whether the document can be spatially manipulated + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently + borderRounding: "string", // border radius rounding of document + searchFields: "string", // the search fields to display when this document matches a search in its metadata + heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) + showCaption: "string", // whether editable caption text is overlayed at the bottom of the document + showTitle: "string", // whether an editable title banner is displayed at tht top of the document + isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) +}); + +export const positionSchema = createSchema({ + zIndex: "number", + x: "number", + y: "number", + z: "number", +}); + +export type Document = makeInterface<[typeof documentSchema]>; +export const Document = makeInterface(documentSchema); + +export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +export const PositionDocument = makeInterface(documentSchema, positionSchema); -- cgit v1.2.3-70-g09d2 From 910e4d8a22a312d2ca0b8a970ff434604f7c6f91 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 23 Oct 2019 23:35:01 -0400 Subject: several fixes to audio documents to make linking work better and to customize the audio controls UI --- src/client/documents/Documents.ts | 3 +- src/client/util/DocumentManager.ts | 4 +- src/client/views/nodes/AudioBox.scss | 62 +++++++++++++++++++--- src/client/views/nodes/AudioBox.tsx | 94 +++++++++++++++++++++------------ src/client/views/nodes/DocuLinkBox.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 16 ++++-- src/new_fields/Doc.ts | 2 + 7 files changed, 134 insertions(+), 51 deletions(-) (limited to 'src/client/views/nodes/DocuLinkBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cad417d33..f26594e04 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -171,7 +171,7 @@ export namespace Docs { }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: data }, - options: { height: 32 } + options: { height: 35, backgroundColor: "lightGray", borderRounding: "20%" } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: data }, @@ -691,6 +691,7 @@ export namespace DocUtils { } }); } + export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) { let sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0f2a47dd0..346e88f40 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -101,10 +101,10 @@ export class DocumentManager { @computed public get LinkedDocumentViews() { let pairs = DocumentManager.Instance.DocumentViews.filter(dv => - dv.isSelected() || Doc.IsBrushed(dv.props.Document) // draw links from DocumentViews that are selected or brushed OR + (dv.isSelected() || Doc.IsBrushed(dv.props.Document)) // draw links from DocumentViews that are selected or brushed OR || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors - let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document); // on a view that is selected or brushed + let init = (dv2.isSelected() || Doc.IsBrushed(dv2.props.Document)) && dv2.Document.type !== DocumentType.AUDIO; // on a view that is selected or brushed return init && rest; }) ).reduce((pairs, dv) => { diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 14b90c570..3d6f6c4f9 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,10 +1,10 @@ .audiobox-container, .audiobox-container-interactive { width: 100%; height: 100%; - min-height: 32px; position: inherit; display:flex; pointer-events: all; + cursor:default; .audiobox-handle { width:20px; height:100%; @@ -35,18 +35,66 @@ width:100%; height:100%; position: relative; - display: grid; + display: flex; .audiobox-player { margin-top:auto; margin-bottom:auto; width:100%; + height: 80%; position: relative; - display: grid; - .audiobox-marker { - position:absolute; - width:10px; + display: flex; + .audiobox-playhead { + position: relative; + margin-top: auto; + margin-bottom: auto; + width: 25px; + } + .audiobox-timeline { + position:relative; height:100%; - background:green; + width:100%; + .audiobox-current { + width: 1px; + height:100%; + background-color: red; + position: absolute;; + } + .audiobox-linker { + position:absolute; + width:15px; + min-height:10px; + height:15px; + margin-left:-2.55px; + background:gray; + border-radius: 100%; + background-color: transparent; + box-shadow: black 2px 2px 1px; + .docuLinkBox-cont { + width:15px !important; + height:15px !important; + left: calc(100% - 15px) !important; + top: calc(100% - 15px) !important; + } + } + .audiobox-linker:hover { + transform:scale(1.5); + } + .audiobox-marker-container { + position:absolute; + width:10px; + height:100%; + background:gray; + border-radius: 5px; + box-shadow: black 2px 2px 1px; + .audiobox-marker { + position:relative; + height: calc(100% - 15px); + margin-top: 15px; + } + .audio-marker:hover { + border: orange 2px solid; + } + } } } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 57360272e..3933c6257 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -7,17 +7,18 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField"; import { DocExtendableComponent } from "../DocComponent"; import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Utils } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; import { DateField } from "../../../new_fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, WidthSym } from "../../../new_fields/Doc"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; import { Id } from "../../../new_fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { DocumentManager } from "../../util/DocumentManager"; +import { DocumentView } from "./DocumentView"; interface Window { MediaRecorder: MediaRecorder; @@ -44,16 +45,14 @@ export class AudioBox extends DocExtendableComponent AudioBox._scrubTime = timeInMillisFrom1970); public static ActiveRecordings: Doc[] = []; componentDidMount() { - runInAction(() => this._duration = NumCast(this.dataDoc.duration, 1)); runInAction(() => this._audioState = this.path ? "recorded" : "unrecorded"); this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { @@ -75,14 +74,12 @@ export class AudioBox extends DocExtendableComponent { + timecodeChanged = () => { const extensionDoc = this.extensionDoc; const htmlEle = this._ele; const start = extensionDoc && DateCast(extensionDoc.recordingStart); - if (htmlEle && !htmlEle.paused && start) { - htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = this._duration = htmlEle.duration); - setTimeout(this.updateHighlights, 30); - this.Document.currentTimecode = htmlEle.currentTime; + if (start && htmlEle) { + htmlEle && htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); DocListCast(this.dataDoc.links).map(l => { let la1 = l.anchor1 as Doc; let linkTime = NumCast(l.anchor2Timecode); @@ -90,25 +87,29 @@ export class AudioBox extends DocExtendableComponent this._lastUpdate && linkTime < htmlEle.currentTime) { + if (linkTime > NumCast(this.Document.currentTimecode) && linkTime < htmlEle.currentTime) { Doc.linkFollowHighlight(la1); } }); - this._lastUpdate = htmlEle.currentTime; + this.Document.currentTimecode = htmlEle.currentTime; } } + pause = action(() => { + this._ele!.pause(); + this._playing = false; + }) + playFrom = (seekTimeInSeconds: number) => { if (this._ele) { if (seekTimeInSeconds < 0) { - this._ele.pause(); + this.pause(); } else if (seekTimeInSeconds <= this._ele.duration) { this._ele.currentTime = seekTimeInSeconds; this._ele.play(); - this._lastUpdate = seekTimeInSeconds; - setTimeout(this.updateHighlights, 0); + runInAction(() => this._playing = true); } else { - this._ele.pause(); + this.pause(); } } } @@ -170,6 +171,7 @@ export class AudioBox extends DocExtendableComponent { this._recorder.stop(); + this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000; this._audioState = "recorded"; let ind = AudioBox.ActiveRecordings.indexOf(this.props.Document); ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1)); @@ -182,12 +184,15 @@ export class AudioBox extends DocExtendableComponent setTimeout(this.updateHighlights, 30); onPlay = (e: any) => this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1); + onStop = (e: any) => { + this.pause(); + this._ele!.currentTime = 0; + e.stopPropagation(); + } setRef = (e: HTMLAudioElement | null) => { - e && e.addEventListener("play", this.playClick as any); - e && e.addEventListener("timeupdate", () => this.props.Document.currentTimecode = e!.currentTime); + e && e.addEventListener("timeupdate", action(() => this.Document.currentTimecode = e!.currentTime)); this._ele = e; } @@ -216,21 +221,40 @@ export class AudioBox extends DocExtendableComponent :
- - {DocListCast(this.dataDoc.links).map((l, i) => { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - let linkTime = NumCast(l.anchor2Timecode); - if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; - linkTime = NumCast(l.anchor1Timecode); - } - return
- DocumentManager.Instance.FollowLink(l, la2, document => this.props.addDocTab(document, undefined, "onRight"), false)} - style={{ left: `${linkTime / this._duration * 100}%` }} />; - })} - {this.audio} + +
+
e.stopPropagation()} + onPointerDown={e => { + if (e.button === 0 && !e.ctrlKey) { + let rect = (e.target as any).getBoundingClientRect(); + this._ele!.currentTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); + this.pause(); + e.stopPropagation(); + } + }} > + {DocListCast(this.dataDoc.links).map((l, i) => { + let la1 = l.anchor1 as Doc; + let la2 = l.anchor2 as Doc; + let linkTime = NumCast(l.anchor2Timecode); + if (Doc.AreProtosEqual(la1, this.dataDoc)) { + la1 = l.anchor2 as Doc; + la2 = l.anchor1 as Doc; + linkTime = NumCast(l.anchor1Timecode); + } + return !linkTime ? (null) :
+
+ +
+
Doc.linkFollowHighlight(la1)} + onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }} + onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} /> +
; + })} +
+ {this.audio} +
} diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 7119b0db0..f2ab6fcd8 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -33,7 +33,7 @@ export class DocuLinkBox extends DocComponent(Doc document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); + (e.button === 0 && !e.ctrlKey) && e.stopPropagation(); } onPointerMove = action((e: PointerEvent) => { let cdiv = this._ref.current!.parentElement; @@ -43,7 +43,7 @@ export class DocuLinkBox extends DocComponent(Doc let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)); if (separation > 100) { - DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.ContainingCollectionDoc as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. + DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } else if (dragdist > separation) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c82fd0f0f..ed93aa83e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,6 +41,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { link } from 'fs'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -585,8 +586,16 @@ export class DocumentView extends DocComponent(Docu layoutKey={this.props.layoutKey || "layout"} DataDoc={this.props.DataDoc} />); } - linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; - linkEndpointDoc = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor1, Doc) as Doc : Cast(linkDoc.anchor2, Doc) as Doc; + linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); + + // used to decide whether a link document should be created or not. + // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. + // would be good to generalize this some way. + isNonTemporalLink = (linkDoc: Doc) => { + let anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; + let ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode; + return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true; + } render() { if (!this.props.Document) return (null); @@ -658,8 +667,7 @@ export class DocumentView extends DocComponent(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > - {this.Document.links && DocListCast(this.Document.links).map((d, i) => - //this.linkEndpointDoc(d).type === DocumentType.PDFANNO ? (null) : + {this.Document.links && DocListCast(this.Document.links).filter(this.isNonTemporalLink).map((d, i) =>
)} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 08cb66d5f..6aad4a6be 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -669,6 +669,8 @@ export namespace Doc { return doc; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } + export function linkFollowUnhighlight() { Doc.UnhighlightAll(); document.removeEventListener("pointerdown", linkFollowUnhighlight); -- cgit v1.2.3-70-g09d2 From c97b98c165e318da787361b8fc11382b0b98afa4 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 24 Oct 2019 11:54:03 -0400 Subject: several fixes to audioboxes. --- src/client/documents/Documents.ts | 3 ++- .../collections/CollectionMasonryViewFieldRow.tsx | 2 +- .../views/collections/CollectionStackingView.scss | 5 ++++ .../CollectionFreeFormLinkView.scss | 1 + .../CollectionFreeFormLinkView.tsx | 3 +++ src/client/views/nodes/AudioBox.scss | 28 ++++++++++++++-------- src/client/views/nodes/AudioBox.tsx | 19 +++++++-------- src/client/views/nodes/DocuLinkBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 9 files changed, 40 insertions(+), 25 deletions(-) (limited to 'src/client/views/nodes/DocuLinkBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f26594e04..d1fcabc4a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -171,7 +171,7 @@ export namespace Docs { }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: data }, - options: { height: 35, backgroundColor: "lightGray", borderRounding: "20%" } + options: { height: 35, backgroundColor: "lightGray" } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: data }, @@ -714,6 +714,7 @@ export namespace DocUtils { linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); + linkDocProto.borderRounding = "20"; linkDocProto.width = linkDocProto.height = 0; linkDocProto.isBackground = true; linkDocProto.isButton = true; diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 4259e948b..0249b90b4 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -366,7 +366,7 @@ export class CollectionMasonryViewFieldRow extends React.Component {headingView} {collapsed ? (null) : - < div > + < div style={{ position: "relative" }}>
div { + height: 100%; + position: relative; + display: inline-block; + } .collectionSchemaView-previewDoc { height: 100%; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 1f1bca2f2..75af11537 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -3,6 +3,7 @@ opacity: 0.8; pointer-events: all; stroke-width: 3px; + transition: opacity 0.5s ease-in; } .collectionfreeformlinkview-linkCircle { stroke: rgb(0,0,0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 962fe2a1c..837413842 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -17,10 +17,12 @@ export interface CollectionFreeFormLinkViewProps { @observer export class CollectionFreeFormLinkView extends React.Component { @observable _alive: number = 0; + @observable _opacity: number = 1; @action componentDidMount() { this._alive = 1; setTimeout(this.rerender, 50); + setTimeout(action(() => this._opacity = 0.05), 50); } @action componentWillUnmount() { @@ -42,6 +44,7 @@ export class CollectionFreeFormLinkView extends React.Component); } diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index ccbf0d75f..3b19a6dba 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -44,22 +44,27 @@ width:100%; height: 80%; position: relative; + padding-right: 5px; display: flex; .audiobox-playhead { position: relative; margin-top: auto; margin-bottom: auto; width: 25px; + padding: 2px; } .audiobox-timeline { position:relative; height:100%; width:100%; + background: white; + border: gray solid 1px; + border-radius: 3px; .audiobox-current { width: 1px; height:100%; background-color: red; - position: absolute;; + position: absolute; } .audiobox-linker, .audiobox-linker-mini { position:absolute; @@ -72,10 +77,11 @@ background-color: transparent; box-shadow: black 2px 2px 1px; .docuLinkBox-cont { - width:15px !important; - height:15px !important; - left: calc(100% - 15px) !important; - top: calc(100% - 15px) !important; + position: relative !important; + height: 100% !important; + width: 100% !important; + left:unset !important; + top:unset !important; } } .audiobox-linker-mini { @@ -86,10 +92,11 @@ margin-left: -1; margin-top: -2; .docuLinkBox-cont { - width:8px !important; - height:8px !important; - left: calc(100% - 8px) !important; - top: calc(100% - 8px) !important; + position: relative !important; + height: 100% !important; + width: 100% !important; + left:unset !important; + top:unset !important; } } .audiobox-linker:hover, .audiobox-linker-mini:hover { @@ -98,7 +105,8 @@ .audiobox-marker-container, .audiobox-marker-minicontainer { position:absolute; width:10px; - height:100%; + height:90%; + top:2.5%; background:gray; border-radius: 5px; box-shadow: black 2px 2px 1px; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index cc1c63d44..bd1caaf1b 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -12,12 +12,11 @@ import { RouteStore } from "../../../server/RouteStore"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; import { DateField } from "../../../new_fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; -import { Doc, DocListCast, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; import { Id } from "../../../new_fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { DocumentManager } from "../../util/DocumentManager"; import { DocumentView } from "./DocumentView"; interface Window { @@ -59,7 +58,7 @@ export class AudioBox extends DocExtendableComponent l[Id] === scrollLinkId).map(l => { let la1 = l.anchor1 as Doc; let linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode); - setTimeout(() => this.playFrom(linkTime), 250); + setTimeout(() => { this.playFrom(linkTime); Doc.linkFollowHighlight(l); }, 250); }); scrollLinkId && Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); }, { fireImmediately: true }); @@ -75,11 +74,9 @@ export class AudioBox extends DocExtendableComponent { - const extensionDoc = this.extensionDoc; const htmlEle = this._ele; - const start = extensionDoc && DateCast(extensionDoc.recordingStart); - if (start && htmlEle) { - htmlEle && htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); + if (this._audioState === "recorded" && htmlEle) { + htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); DocListCast(this.dataDoc.links).map(l => { let la1 = l.anchor1 as Doc; let linkTime = NumCast(l.anchor2Timecode); @@ -195,7 +192,7 @@ export class AudioBox extends DocExtendableComponent { - e && e.addEventListener("timeupdate", action(() => this.Document.currentTimecode = e!.currentTime)); + e && e.addEventListener("timeupdate", this.timecodeChanged); this._ele = e; } @@ -225,13 +222,13 @@ export class AudioBox extends DocExtendableComponent :
- -
+
+
e.stopPropagation()} onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { let rect = (e.target as any).getBoundingClientRect(); - this._ele!.currentTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); + this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); this.pause(); e.stopPropagation(); } diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index f2ab6fcd8..8f68effe8 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -73,7 +73,7 @@ export class DocuLinkBox extends DocComponent(Doc let c = StrCast(this.props.Document.backgroundColor, "lightblue"); return
; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 566b698bd..cf55a10ba 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -192,7 +192,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } let tsel = this._editorView.state.selection.$from; - tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 5000))); + tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); -- cgit v1.2.3-70-g09d2 From cf9fff0cf86e120cd4d26169ce8455bd8b438b24 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 24 Oct 2019 14:01:35 -0400 Subject: minor fixes including fix to focus() to restore animation panning --- src/client/views/collections/CollectionSubView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +++++++-------- src/client/views/nodes/AudioBox.tsx | 2 +- src/client/views/nodes/DocuLinkBox.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/DocuLinkBox.tsx') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 55365de8c..d7e9494a3 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -74,7 +74,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this._childLayoutDisposer && this._childLayoutDisposer(); } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f6518a01d..6e0f75bc1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -354,14 +354,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - setPan(panX: number, panY: number) { - if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) { - this.props.Document.panTransformType = "None"; + setPan(panX: number, panY: number, panType: string = "None") { + if (!this.Document.lockedPosition || this.Document.inOverlay) { + this.Document.panTransformType = panType; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); - const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); - this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; - this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; + const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); + this.Document.panX = this.isAnnotationOverlay ? newPanX : panX; + this.Document.panY = this.isAnnotationOverlay ? newPanY : panY; } } @@ -415,8 +415,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType }; - this.setPan(newPanX, newPanY); - this.Document.panTransformType = "Ease"; + this.setPan(newPanX, newPanY, "Ease"); Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); willZoom && this.setScaleToZoom(layoutdoc, scale); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index bd1caaf1b..268255b46 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -95,7 +95,7 @@ export class AudioBox extends DocExtendableComponent { this._ele!.pause(); this._playing = false; - }) + }); playFrom = (seekTimeInSeconds: number) => { if (this._ele) { diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 8f68effe8..f4c7d43aa 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -36,7 +36,7 @@ export class DocuLinkBox extends DocComponent(Doc (e.button === 0 && !e.ctrlKey) && e.stopPropagation(); } onPointerMove = action((e: PointerEvent) => { - let cdiv = this._ref.current!.parentElement; + let cdiv = this._ref && this._ref.current && this._ref.current.parentElement; if (cdiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) { let bounds = cdiv.getBoundingClientRect(); let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); @@ -65,6 +65,7 @@ export class DocuLinkBox extends DocComponent(Doc } e.stopPropagation(); } + render() { let anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc); let hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ed93aa83e..11066145b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -667,9 +667,9 @@ export class DocumentView extends DocComponent(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > - {this.Document.links && DocListCast(this.Document.links).filter(this.isNonTemporalLink).map((d, i) => + {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
- + Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} layoutKey={this.linkEndpoint(d)} />
)} {!showTitle && !showCaption ? this.Document.searchFields ? -- cgit v1.2.3-70-g09d2 From cb7078190a06ea19cb443626b9023b93c1930e3a Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 4 Nov 2019 11:35:50 -0500 Subject: getting back in the saddle: small changes -audio play icons, link titling, dictation. --- src/client/views/nodes/AudioBox.tsx | 1 + src/client/views/nodes/DocuLinkBox.tsx | 5 ++++- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/DocuLinkBox.tsx') diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 268255b46..d652a11d5 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -193,6 +193,7 @@ export class AudioBox extends DocExtendableComponent { e && e.addEventListener("timeupdate", this.timecodeChanged); + e && e.addEventListener("ended", this.pause); this._ele = e; } diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index f4c7d43aa..d73407903 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -72,7 +72,10 @@ export class DocuLinkBox extends DocComponent(Doc let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100); let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100); let c = StrCast(this.props.Document.backgroundColor, "lightblue"); - return
+ style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.2 }} icon={"microphone"} size="sm" />
); -- cgit v1.2.3-70-g09d2