diff options
Diffstat (limited to 'src/client/views/nodes')
| -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 |
5 files changed, 106 insertions, 110 deletions
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; |
