aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/CurrentUserUtils.ts3
-rw-r--r--src/client/util/DictationManager.ts4
-rw-r--r--src/client/views/DocumentButtonBar.tsx24
-rw-r--r--src/client/views/PropertiesButtons.tsx9
-rw-r--r--src/client/views/nodes/DocumentView.scss6
-rw-r--r--src/client/views/nodes/DocumentView.tsx82
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx9
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx12
8 files changed, 90 insertions, 59 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7856c913b..d19874720 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -107,7 +107,7 @@ export class CurrentUserUtils {
const reqdClickList = reqdTempOpts.map(opts => {
const allOpts = {...reqdClickOpts, ...opts.opts};
const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
- return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script,allOpts));
+ return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
});
const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, system: true};
@@ -633,6 +633,7 @@ export class CurrentUserUtils {
{ title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} },
{ title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} },
{ title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}},
+ { title: "Dictate",toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}},
];
}
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 13f036838..0a61f3478 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -152,7 +152,6 @@ export namespace DictationManager {
return new Promise<string>((resolve, reject) => {
recognizer.onerror = (e: any) => {
- console.log('SPEECH error:', encodeURIComponent);
// e is SpeechRecognitionError but where is that defined?
if (!(indefinite && e.error === 'no-speech')) {
recognizer.stop();
@@ -162,7 +161,6 @@ export namespace DictationManager {
};
recognizer.onresult = (e: SpeechRecognitionEvent) => {
- console.log('RESULT: ', e);
current = synthesize(e, intra);
let matchedTerminator: string | undefined;
if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
@@ -175,7 +173,6 @@ export namespace DictationManager {
};
recognizer.onend = (e: Event) => {
- console.log('END: ', e);
if (!indefinite || isManuallyStopped) {
return complete();
}
@@ -188,7 +185,6 @@ export namespace DictationManager {
};
const complete = () => {
- console.log('COMPLETE:');
if (indefinite) {
current && sessionResults.push(current);
sessionResults.length && resolve(sessionResults.join(inter || interSession));
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1d4056759..265df3abc 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { RichTextField } from '../../fields/RichTextField';
import { Cast, NumCast } from '../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
import { Docs } from '../documents/Documents';
@@ -334,6 +334,27 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
</Tooltip>
);
}
+
+ @observable _isRecording = false;
+ @computed
+ get recordButton() {
+ const targetDoc = this.view0?.props.Document;
+ return !targetDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">{'Click to record 5 second annotation'}</div>}>
+ <div
+ className="documentButtonBar-icon"
+ style={{ backgroundColor: this._isRecording ? 'red' : targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+ onClick={undoBatch(
+ action(e => {
+ this._isRecording = true;
+ this.props.views().map(view => view?.docView?.recordAudioAnnotation(action(() => (this._isRecording = false))));
+ })
+ )}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="microphone" />
+ </div>
+ </Tooltip>
+ );
+ }
@observable _aliasDown = false;
onTemplateButton = action((e: React.PointerEvent): void => {
this._tooltipOpen = false;
@@ -411,6 +432,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
</div>
) : null}
+ <div className="documentButtonBar-button">{this.recordButton}</div>
{
Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
/*<div className="documentButtonBar-button">
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 39e7b89c1..80c2c7705 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -68,14 +68,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => 'thumbtack'
);
}
- @computed get dictationButton() {
- return this.propertyToggleBtn(
- 'Dictate',
- '_showAudio',
- on => `${on ? 'Hide' : 'Show'} dictation/recording controls`,
- on => 'microphone'
- );
- }
@computed get maskButton() {
return this.propertyToggleBtn(
'Mask',
@@ -365,7 +357,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{toggle(this.titleButton)}
{toggle(this.captionButton)}
{toggle(this.lockButton)}
- {toggle(this.dictationButton, { display: isNovice ? 'none' : '' })}
{toggle(this.onClickButton)}
{toggle(this.fitWidthButton)}
{toggle(this.freezeThumb)}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c02692bfb..6ea697a2f 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -65,11 +65,13 @@
width: 25px;
height: 25;
position: absolute;
- top: 10px;
- left: 10px;
+ top: 0;
+ left: 50%;
border-radius: 25px;
background: white;
opacity: 0.3;
+ pointer-events: all;
+ cursor: default;
svg {
width: 90% !important;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2524d66dd..4b2bd07ef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,7 +10,7 @@ import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
@@ -52,6 +52,8 @@ import { RadialMenu } from './RadialMenu';
import { ScriptingBox } from './ScriptingBox';
import { PresBox } from './trails/PresBox';
import React = require('react');
+import { DictationManager } from '../../util/DictationManager';
+import { Tooltip } from '@material-ui/core';
const { Howl } = require('howler');
interface Window {
@@ -1005,16 +1007,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
@computed get contents() {
TraceMobx();
- const audioView = !this.layoutDoc._showAudio ? null : (
- <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter}>
- <FontAwesomeIcon
- className="documentView-audioFont"
- style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }}
- icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'microphone' : 'file-audio'}
- size="sm"
- />
- </div>
- );
+ const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length;
+ const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+ const audioView =
+ (!this.props.isSelected() && !this._isHovering) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this._mediaState) ? null : (
+ <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
+ <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
+ <FontAwesomeIcon className="documentView-audioFont" style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }} icon={!audioAnnosCount ? 'microphone' : 'file-audio'} size="sm" />
+ </div>
+ </Tooltip>
+ );
return (
<div
@@ -1148,26 +1150,29 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
@action
- onPointerEnter = () => {
+ playAnnotation = () => {
const self = this;
- const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']);
- if (audioAnnos && audioAnnos.length && this._mediaState === 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._mediaState = 0));
- },
- });
+ const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
+ const anno = audioAnnos.lastElement();
+ if (anno instanceof AudioField && this._mediaState === 0) {
+ new Howl({
+ src: [anno.url.href],
+ format: ['mp3'],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: function () {
+ runInAction(() => {
+ console.log('PLAYED');
+ self._mediaState = 0;
+ });
+ },
+ });
this._mediaState = 1;
}
};
- recordAudioAnnotation = () => {
+
+ recordAudioAnnotation = (onEnd?: () => void) => {
let gumStream: any;
let recorder: any;
const self = this;
@@ -1176,18 +1181,30 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
audio: true,
})
.then(function (stream) {
+ let audioTextAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+ if (audioTextAnnos) audioTextAnnos.push('');
+ else audioTextAnnos = self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'] = new List<string>(['']);
+ DictationManager.Controls.listen({
+ interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+ continuous: { indefinite: false },
+ }).then(results => {
+ if (results && [DictationManager.Controls.Infringed].includes(results)) {
+ DictationManager.Controls.stop();
+ }
+ onEnd?.();
+ });
+
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(result.accessPaths.agnostic.client, { title: 'audio test', _width: 200, _height: 32 });
- audioDoc.treeViewExpandedView = 'layout';
- const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(Doc));
+ const audioField = new AudioField(result.accessPaths.agnostic.client);
+ const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
if (audioAnnos === undefined) {
- self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioDoc]);
+ self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioField]);
} else {
- audioAnnos.push(audioDoc);
+ audioAnnos.push(audioField);
}
}
};
@@ -1195,6 +1212,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
recorder.start();
setTimeout(() => {
recorder.stop();
+ DictationManager.Controls.stop(false);
runInAction(() => (self._mediaState = 0));
gumStream.getAudioTracks()[0].stop();
}, 5000);
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 78ef85ec2..d3b95e25a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
@@ -710,6 +710,13 @@ ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
}
if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor();
});
+ScriptingGlobals.add(function toggleDictation(checkResult?: boolean) {
+ const textView = RichTextMenu.Instance?.TextView;
+ if (checkResult) {
+ return textView?._recording ? Colors.MEDIUM_BLUE : 'transparent';
+ }
+ if (textView) runInAction(() => (textView._recording = !textView._recording));
+});
ScriptingGlobals.add(function toggleBold(checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 849deb04e..929cca1ea 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -771,7 +771,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const uicontrols: ContextMenuProps[] = [];
!Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
- uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' });
uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
uicontrols.push({
@@ -839,7 +838,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
};
recordDictation = () => {
- console.log('RECORD DICTATIN:');
DictationManager.Controls.listen({
interimHandler: this.setDictationContent,
continuous: { indefinite: false },
@@ -852,11 +850,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
setDictationContent = (value: string) => {
- console.log('DICTATION CONETNT: ' + value);
if (this._editorView && this._recordingStart) {
- console.log('STEP 1');
if (this._break) {
- console.log('BREAK');
const textanchorFunc = () => {
const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
return this.addDocument(tanch) ? tanch : undefined;
@@ -880,7 +875,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
}
- console.log('FINALIziNG');
const from = this._editorView.state.selection.from;
this._break = false;
const tr = this._editorView.state.tr.insertText(value);
@@ -1732,7 +1726,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
.scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1));
@computed get audioHandle() {
- return (
+ return !this._recording ? null : (
<div
className="formattedTextBox-dictation"
onPointerDown={e =>
@@ -1744,7 +1738,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
action(e => (this._recording = !this._recording))
)
}>
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
</div>
);
}
@@ -1898,7 +1892,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
</div>
{this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
{this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
- {!this.layoutDoc._showAudio ? null : this.audioHandle}
+ {this.audioHandle}
</div>
</div>
);