diff options
-rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
-rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 10 | ||||
-rw-r--r-- | src/client/views/PropertiesButtons.scss | 4 | ||||
-rw-r--r-- | src/client/views/PropertiesButtons.tsx | 13 | ||||
-rw-r--r-- | src/client/views/StyleProvider.tsx | 6 | ||||
-rw-r--r-- | src/client/views/TemplateMenu.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 19 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 92 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 18 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 83 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 | ||||
-rw-r--r-- | src/fields/documentSchemas.ts | 3 |
12 files changed, 127 insertions, 132 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fee2679b6..7502aa685 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -76,8 +76,7 @@ export interface DocumentOptions { _fitWidth?: boolean; _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents _freeformLOD?: boolean; // whether to use LOD to render a freeform document - _showTitleHover?: string; // - _showTitle?: string; // which field to display in the title area. leave empty to have no title + _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index eeef94d74..209e750d6 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -312,22 +312,20 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return false; } - _ref = React.createRef<HTMLDivElement>(); + _ref = React.createRef<TemplateMenu>(); @observable _tooltipOpen: boolean = false; @computed get templateButton() { const view0 = this.view0; - const templates: Map<string, boolean> = new Map(); const views = this.props.views(); - Array.from(["Caption", "Title", "TitleHover"]).map(template => - templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template] ? true : false, false as boolean))); return !view0 ? (null) : <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom"> <div className="documentButtonBar-linkFlyout" ref={this._dragRef} - onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} > + onPointerEnter={action(() => !(this._ref.current as any as HTMLElement)?.getBoundingClientRect().width && (this._tooltipOpen = true))} > <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} - content={!this._aliasDown ? (null) : <div ref={this._ref}> <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} /></div>}> + content={!this._aliasDown ? (null) : + <TemplateMenu ref={this._ref} docViews={views.filter(v => v).map(v => v as DocumentView)} />}> <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} > {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />} </div> diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss index d63eb874c..ba212da7f 100644 --- a/src/client/views/PropertiesButtons.scss +++ b/src/client/views/PropertiesButtons.scss @@ -47,6 +47,10 @@ $linkGap : 3px; background-color: white; color: black; } +.propertiesButtons-linkButton-empty.toggle-hover { + background-color: gray; + color: black; +} .propertiesButtons-linkButton-empty.toggle-off { color: white; } diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 9a836313c..53a017592 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, NumCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../fields/Types"; import { ImageField } from '../../fields/URLField'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -222,10 +222,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @undoBatch - @action - setDictation = () => { - SelectionManager.Views().forEach(dv => dv.rootDoc._showAudio = dv.rootDoc._showAudio === !dv.rootDoc._showAudio); - } + setDictation = () => SelectionManager.Views().forEach(dv => dv.rootDoc._showAudio = !dv.rootDoc._showAudio); @computed get dictationButton() { @@ -244,7 +241,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setTitle = () => { - SelectionManager.Views().forEach(dv => dv.rootDoc._showTitle = dv.rootDoc._showTitle === undefined ? "title" : undefined); + SelectionManager.Views().forEach(dv => dv.rootDoc._showTitle = !dv.rootDoc._showTitle ? "title" : dv.rootDoc._showTitle === "title" ? "title:hover" : undefined); } @computed @@ -252,7 +249,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Title Header"}</div>} placement="top"> <div> - <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showTitle ? "on" : "off"}`} onPointerDown={this.setTitle}> + <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showTitle === "title" ? "on" : StrCast(targetDoc._showTitle).includes(":hover") ? "hover" : "off"}`} onPointerDown={this.setTitle}> <FontAwesomeIcon className="propertiesButtons-icon" icon="text-width" size="lg" /> </div> <div className="propertiesButtons-title"> Title </div> @@ -502,7 +499,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button"> {this.lockButton} </div> - <div className="propertiesButtons-button" style={{ display: isText || isImage ? "" : "none" }}> + <div className="propertiesButtons-button"> {this.dictationButton} </div> <div className="propertiesButtons-button"> diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 058d21c92..0ab8c5dfb 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -36,7 +36,8 @@ export enum StyleProp { PointerEvents = "pointerEvents", // pointer events for DocumentView -- inherits pointer events if not specified Decorations = "decorations", // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background HeaderMargin = "headerMargin", // margin at top of documentview, typically for displaying a title -- doc contents will start below that - ShowTitle = "showTitle", // whether to display a title on a Document + TitleHeight = "titleHeight", // Height of Title area + ShowTitle = "showTitle", // whether to display a title on a Document (optional :hover suffix) } function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); } @@ -86,7 +87,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | return "white"; case StyleProp.Hidden: return BoolCast(doc?._hidden, BoolCast(doc?.hidden)); case StyleProp.BorderRounding: return StrCast(doc?._borderRounding, StrCast(doc?.borderRounding)); - case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && doc?._showTitle && !doc?._showTitleHover ? 15 : 0; + case StyleProp.TitleHeight: return 15; + case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && doc?._showTitle && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (isAnchor && docProps) return "transparent"; if (isCaption) return "rgba(0,0,0 ,0.4)"; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index cdaf1bd0c..d2e0cb265 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -45,7 +45,7 @@ class OtherToggle extends React.Component<{ checked: boolean, name: string, togg export interface TemplateMenuProps { docViews: DocumentView[]; - templates: Map<string, boolean>; + templates?: Map<string, boolean>; } @@ -115,7 +115,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); const layout = Doc.Layout(firstDoc); const templateMenu: Array<JSX.Element> = []; - this.props.templates.forEach((checked, template) => + this.props.templates?.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />)); templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />); templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 6f041e5ef..749ffa7fd 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -45,6 +45,25 @@ width:10px !important; } } + + .documentView-audioBackground { + display: inline-block; + width: 10%; + position: absolute; + top: 0px; + left: 0px; + border-radius: 25px; + background: white; + opacity: 0.3; + + svg { + width: 90% !important; + height: 70%; + position: absolute; + left: 5%; + top: 15%; + } + } .documentView-treeView { max-height: 1.5em; text-overflow: ellipsis; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7dea3784e..9c844c51e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,18 +1,22 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; +import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { AudioField } from "../../../fields/URLField"; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; import { emptyFunction, hasDescendantTarget, OmitKeys, returnFalse, returnVal, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; +import { Networking } from "../../Network"; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; @@ -41,6 +45,17 @@ import { LinkDocPreview } from "./LinkDocPreview"; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +const { Howl } = require('howler'); + +interface Window { + MediaRecorder: MediaRecorder; +} + +declare class MediaRecorder { + // whatever MediaRecorder has + constructor(e: any); +} + export enum ViewAdjustment { resetView = 1, @@ -135,6 +150,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps, Document>(Document) { @observable _animateScalingTo = 0; + @observable _audioState = 0; private _downX: number = 0; private _downY: number = 0; private _firstX: number = -1; @@ -146,8 +162,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps private _timeout: NodeJS.Timeout | undefined; private _dropDisposer?: DragManager.DragDropDisposer; private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer; - _componentView: Opt<DocComponentView>; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + _componentView: Opt<DocComponentView>; private get topMost() { return this.props.renderDepth === 0; } private get active() { return this.props.isSelected(true) || this.props.parentActive(true); } @@ -165,6 +181,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get backgroundColor() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); } @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); } @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } + @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; } @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); } @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); } @computed get nativeWidth() { return this.props.NativeWidth(); } @@ -737,6 +754,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive; @computed get contents() { TraceMobx(); + const audioView = !this.layoutDoc._showAudio ? (null) : + <div className="documentView-audioBackground" + onPointerDown={this.recordAudioAnnotation} + onPointerEnter={this.onPointerEnter} + style={{ height: 25, position: "absolute", top: 10, left: 10 }} + > + <FontAwesomeIcon className="documentView-audioFont" + style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }} + icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> + </div>; return <div className="documentView-contentsView" style={{ pointerEvents: this.props.contentPointerEvents as any, @@ -757,6 +784,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors} {this.hideLinkButton ? (null) : <DocumentLinksButton View={this.props.DocumentView()} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />} + + {audioView} </div>; } @@ -790,11 +819,62 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps </div >); } + @action + onPointerEnter = () => { + const self = this; + const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]); + if (audioAnnos && audioAnnos.length && this._audioState === 0) { + const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; + anno.data instanceof AudioField && new Howl({ + src: [anno.data.url.href], + format: ["mp3"], + autoplay: true, + loop: false, + volume: 0.5, + onend: function () { + runInAction(() => self._audioState = 0); + } + }); + this._audioState = 1; + } + } + recordAudioAnnotation = () => { + let gumStream: any; + let recorder: any; + const self = this; + navigator.mediaDevices.getUserMedia({ + audio: true + }).then(function (stream) { + gumStream = stream; + recorder = new MediaRecorder(stream); + recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer(e.data); + if (!(result instanceof Error)) { + const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 }); + audioDoc.treeViewExpandedView = "layout"; + const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc)); + if (audioAnnos === undefined) { + self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]); + } else { + audioAnnos.push(audioDoc); + } + } + }; + runInAction(() => self._audioState = 2); + recorder.start(); + setTimeout(() => { + recorder.stop(); + runInAction(() => self._audioState = 0); + gumStream.getAudioTracks()[0].stop(); + }, 5000); + }); + } + captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption"); @computed get innards() { TraceMobx(); - const showTitle = this.ShowTitle; - const showTitleHover = StrCast(this.layoutDoc._showTitleHover); + const showTitle = this.ShowTitle?.split(":")[0]; + const showTitleHover = this.ShowTitle?.includes(":hover"); const showCaption = StrCast(this.layoutDoc._showCaption); const captionView = !showCaption ? (null) : <div className="documentView-captionWrapper" @@ -815,7 +895,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const titleView = !showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{ position: this.headerMargin ? "relative" : "absolute", - height: this.headerMargin, + height: this.titleHeight, background: SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.userColor || (this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"), pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined, }}> @@ -932,9 +1012,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); contentsActive = () => this.docView?.contentsActive(); - focus = (doc: Doc, options?: DocFocusOptions) => { - return this.docView?.focus(doc, options); - } + focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 41055e2db..6d60c70bf 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -94,24 +94,6 @@ height: 100%; } -.imageBox-audioBackground { - display: inline-block; - width: 10%; - position: absolute; - top: 0px; - left: 0px; - border-radius: 25px; - background: white; - opacity: 0.3; - - svg { - width: 90% !important; - height: 70%; - position: absolute; - left: 5%; - top: 15%; - } -} .imageBox-fader { position: relative; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 74bff2bfe..728e5e02f 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,4 +1,3 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; import { Dictionary } from 'typescript-collections'; @@ -10,12 +9,11 @@ import { ObjectField } from '../../../fields/ObjectField'; import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { AudioField, ImageField } from '../../../fields/URLField'; +import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnOne, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; -import { Docs } from '../../documents/Documents'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; @@ -30,8 +28,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); const path = require('path'); -const { Howl } = require('howler'); - export const pageSchema = createSchema({ _curPage: "number", @@ -39,16 +35,6 @@ export const pageSchema = createSchema({ googlePhotosUrl: "string", googlePhotosTags: "string" }); - -interface Window { - MediaRecorder: MediaRecorder; -} - -declare class MediaRecorder { - // whatever MediaRecorder has - constructor(e: any); -} - type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); @@ -64,10 +50,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject<HTMLImageElement> = React.createRef(); + private _curSuffix = "_m"; private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; - @observable private _audioState = 0; - @observable static _showControls: boolean; @observable uploadIcon = uploadIcons.idle; protected createDropTarget = (ele: HTMLDivElement) => { @@ -120,37 +105,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD } } - recordAudioAnnotation = () => { - let gumStream: any; - let recorder: any; - const self = this; - navigator.mediaDevices.getUserMedia({ - audio: true - }).then(function (stream) { - gumStream = stream; - recorder = new MediaRecorder(stream); - recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(e.data); - if (!(result instanceof Error)) { - const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 }); - audioDoc.treeViewExpandedView = "layout"; - const audioAnnos = Cast(self.dataDoc[self.fieldKey + "-audioAnnotations"], listSpec(Doc)); - if (audioAnnos === undefined) { - self.dataDoc[self.fieldKey + "-audioAnnotations"] = new List([audioDoc]); - } else { - audioAnnos.push(audioDoc); - } - } - }; - runInAction(() => self._audioState = 2); - recorder.start(); - setTimeout(() => { - recorder.stop(); - runInAction(() => self._audioState = 0); - gumStream.getAudioTracks()[0].stop(); - }, 5000); - }); - } @undoBatch resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes @@ -250,29 +204,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD } } } - _curSuffix = "_m"; - - @action - onPointerEnter = () => { - const self = this; - const audioAnnos = DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]); - if (audioAnnos && audioAnnos.length && this._audioState === 0) { - const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; - anno.data instanceof AudioField && new Howl({ - src: [anno.data.url.href], - format: ["mp3"], - autoplay: true, - loop: false, - volume: 0.5, - onend: function () { - runInAction(() => self._audioState = 0); - } - }); - this._audioState = 1; - } - } - - audioDown = () => this.recordAudioAnnotation(); considerGooglePhotosLink = () => { const remoteUrl = this.dataDoc.googlePhotosUrl; @@ -387,16 +318,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD ref={this._imgRef} onError={this.onError} /></div>} </div> - {!this.layoutDoc._showAudio ? (null) : - <div className="imageBox-audioBackground" - onPointerDown={this.audioDown} - onPointerEnter={this.onPointerEnter} - style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }} - > - <FontAwesomeIcon className="imageBox-audioFont" - style={{ color: [DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }} - icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> - </div>} {this.considerDownloadIcon} {this.considerGooglePhotosLink()} <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} /> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9e9be74f3..779da91e6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -258,13 +258,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp _lastText = ""; dispatchTransaction = (tx: Transaction) => { - let timeStamp; - clearTimeout(timeStamp); if (this._editorView) { - const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); if (metadata) { - const range = tx.selection.$from.blockRange(tx.selection.$to); let text = range ? tx.doc.textBetween(range.start, range.end) : ""; let textEndSelection = tx.selection.to; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index bdc498c97..df3dd1eb4 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -42,8 +42,7 @@ export const documentSchema = createSchema({ _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container _overflow: "string", // sets overflow behvavior for CollectionFreeForm views _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document - _showTitle: "string", // the fieldkey whose contents should be displayed at the top of the document - _showTitleHover: "string", // the showTitle should be shown only on hover + _showTitle: "string", // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover _showAudio: "boolean", // whether to show the audio record icon on documents _freeformLOD: "boolean", // whether to enable LOD switching for CollectionFreeFormViews _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis |