diff --git a/app/assets/scss/app.scss b/app/assets/scss/app.scss index 0e608bf..51aad31 100644 --- a/app/assets/scss/app.scss +++ b/app/assets/scss/app.scss @@ -350,6 +350,7 @@ a.nav-link.active { position:absolute; bottom:0; // make transcription start from the bottom max-height:100%; + min-width:5px; } .final:focus { diff --git a/app/components/Navbar.vue b/app/components/Navbar.vue index b4a639b..f63db09 100644 --- a/app/components/Navbar.vue +++ b/app/components/Navbar.vue @@ -25,6 +25,12 @@ + + + + + Typing Mode + @@ -131,6 +137,9 @@ export default { captioningOn: function() { return this.$store.state.captioner.shouldBeOn; }, + typingModeOn: function() { + return this.$store.state.captioner.typingModeOn; + }, microphoneName: function() { return this.$store.state.captioner.microphoneName; }, @@ -186,11 +195,11 @@ export default { stopCaptioning: function() { this.$store.dispatch('captioner/stopManual'); }, - startTyping: function() { - this.$store.dispatch('captioner/startTyping'); + startTypingMode: function() { + this.$store.dispatch('captioner/startTypingMode'); }, - stopTyping: function() { - this.$store.dispatch('captioner/stopTyping'); + stopTypingMode: function() { + this.$store.dispatch('captioner/stopTypingMode'); }, startSaveToFileModal: function() { this.$router.push('/captioner/save-to-file'); diff --git a/app/components/Transcript.vue b/app/components/Transcript.vue index 294e3b2..f3c6687 100644 --- a/app/components/Transcript.vue +++ b/app/components/Transcript.vue @@ -2,13 +2,14 @@
+ v-bind:style="{height, color, backgroundColor, fontFamily, fontSize, lineHeight, letterSpacing, textTransform, padding, textShadow, cursor}" + @click="focusIfInTypingMode()"> - {{finalTranscript}} {{interimTranscript}} + {{finalTranscript}} {{interimTranscript}} Hello world
@@ -27,6 +28,7 @@ export default { data: function() { return { height: '100vh', + transcriptTypedForDisplay: '', } }, methods: { @@ -37,6 +39,14 @@ export default { } }); }, + typedTranscriptDidChange: function() { + this.$store.commit('captioner/SET_TRANSCRIPT_TYPED', {transcriptTyped: this.$refs.typedTranscript.innerText}) + }, + focusIfInTypingMode: function() { + if (this.typingModeOn) { + this.$refs.typedTranscript.focus(); + } + }, }, mounted: function() { this.scrollToBottom(); @@ -54,6 +64,37 @@ export default { this.height = this.adjustAppHeight(); }); }, + watch: { + typingModeOn: function (on) { + if (on) { + // Turned on. Copy the transcript over once. + this.transcriptTypedForDisplay = this.typedTranscript; + this.$nextTick(() => { + this.$refs.typedTranscript.focus(); + + // Put caret at end + if (typeof window.getSelection != "undefined" + && typeof document.createRange != "undefined") { + var range = document.createRange(); + range.selectNodeContents(this.$refs.typedTranscript); + range.collapse(false); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } else if (typeof document.body.createTextRange != "undefined") { + var textRange = document.body.createTextRange(); + textRange.moveToElementText(this.$refs.typedTranscript); + textRange.collapse(false); + textRange.select(); + } + }); + } + else { + // Turned off. + this.transcriptTypedForDisplay = ''; + } + }, + }, computed: { // Appearance color () { @@ -107,6 +148,12 @@ export default { return (this.$store.state.captioner.transcript.interim && this.$store.state.captioner.transcript.interim.length ? ' ' : '') + this.$store.state.captioner.transcript.interim; }, + typingModeOn () { + return this.$store.state.captioner.typingModeOn; + }, + typedTranscript () { + return this.$store.state.captioner.transcript.typed; + }, textPositionClass: function () { return { @@ -130,6 +177,9 @@ export default { 'align-items-end': ['bottom','lowerThird'].includes(this.$store.state.settings.appearance.text.alignment.vertical), } }, + cursor: function() { + return this.typingModeOn ? 'text' : 'default'; + }, largerLayout: function() { return this.$store.state.settings.controls.layout.larger; }, @@ -138,4 +188,10 @@ export default { diff --git a/app/nuxt.config.js b/app/nuxt.config.js index a63b1eb..378f6ab 100644 --- a/app/nuxt.config.js +++ b/app/nuxt.config.js @@ -32,7 +32,7 @@ module.exports = { imports: [ { set: '@fortawesome/free-solid-svg-icons', - icons: ['faFileAlt', 'faFileWord', 'faExclamationTriangle', 'faTimes', 'faMicrophone', 'faDesktop', 'faExternalLinkAlt', 'faSave', 'faTrashAlt', 'faCog', 'faCheckCircle', 'faSpinner', 'faChevronRight', 'faMinusCircle', 'faPlusCircle', 'faArrowLeft', 'faFlask', 'faCaretRight', 'faCaretDown', ], + icons: ['faFileAlt', 'faFileWord', 'faExclamationTriangle', 'faTimes', 'faMicrophone', 'faDesktop', 'faExternalLinkAlt', 'faSave', 'faTrashAlt', 'faCog', 'faCheckCircle', 'faSpinner', 'faChevronRight', 'faMinusCircle', 'faPlusCircle', 'faArrowLeft', 'faFlask', 'faCaretRight', 'faCaretDown', 'faKeyboard', ], }, { set: '@fortawesome/free-regular-svg-icons', @@ -45,6 +45,9 @@ module.exports = { ] }], ], + plugins: [ + '~/node_modules/vue-contenteditable-directive', + ], css: [ '@/assets/scss/app.scss', ], diff --git a/app/package-lock.json b/app/package-lock.json index 3450c60..b7afa55 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -11107,6 +11107,12 @@ "integrity": "sha512-pRy3/QYWvNwCESrkVEyz9NjVfzUUkp9OLcK4RQlx6L1UY54CdFeVMs/+KdzxBppt38HSpXh+IX/p2tB4pDIgFQ==", "dev": true }, + "vue-contenteditable-directive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vue-contenteditable-directive/-/vue-contenteditable-directive-1.2.0.tgz", + "integrity": "sha512-9RuW1cboQBOUhURXiQpBD8XldyK2BYWhkWTnRw4Qmv8ZeQy+tGnnPs4XfemoPNf4KQW31Mx6UqEszlZYgoPeYw==", + "dev": true + }, "vue-eslint-parser": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", diff --git a/app/package.json b/app/package.json index b6706c9..eaf8b63 100644 --- a/app/package.json +++ b/app/package.json @@ -54,6 +54,7 @@ "sass-loader": "^7.1.0", "screenfull": "^3.3.2", "semver-compare": "^1.0.0", + "vue-contenteditable-directive": "^1.2.0", "whatwg-fetch": "^2.0.4" } } diff --git a/app/pages/captioner.vue b/app/pages/captioner.vue index 0e681b2..a244203 100755 --- a/app/pages/captioner.vue +++ b/app/pages/captioner.vue @@ -217,7 +217,6 @@ export default { mode: 'cors', headers: { 'Accept': 'application/json', - 'Content-Type': 'application/json', }, body, }) diff --git a/app/store/modules/captioner/index.js b/app/store/modules/captioner/index.js index bc4e0bb..bc78128 100644 --- a/app/store/modules/captioner/index.js +++ b/app/store/modules/captioner/index.js @@ -13,6 +13,7 @@ let speechRecognizer, const state = { on: false, shouldBeOn: false, + typingModeOn: false, microphonePermission: { needed: false, denied: false, @@ -22,6 +23,7 @@ const state = { transcript: { interim: '', final: '', + typed: '', lastStart: null, lastUpdate: null, waitingForInitial: false, @@ -198,6 +200,21 @@ const actions = { }); } }, + + startTypingMode: ({state, commit, dispatch}) => { + dispatch('stopManual'); + setTimeout(() => { + commit('SET_TRANSCRIPT_TYPED', {transcriptTyped: state.transcript.final}); + commit('CLEAR_TRANSCRIPT_FINAL'); + commit('SET_TYPING_MODE_ON'); + },500); + }, + + stopTypingMode: ({state, commit, dispatch}) => { + commit('APPEND_TRANSCRIPT_FINAL', {transcriptFinal: state.transcript.typed }); + commit('CLEAR_TRANSCRIPT_TYPED'); + commit('SET_TYPING_MODE_OFF'); + }, } const mutations = { @@ -227,13 +244,23 @@ const mutations = { state.transcript.interim = transcriptInterim; state.transcript.lastUpdate = Date.now(); }, + SET_TRANSCRIPT_TYPED (state, { transcriptTyped }) { + state.transcript.typed = transcriptTyped; + }, CLEAR_TRANSCRIPT (state) { state.transcript.interim = ''; state.transcript.final = ''; + state.transcript.typed = ''; }, CLEAR_TRANSCRIPT_INTERIM (state) { state.transcript.interim = ''; }, + CLEAR_TRANSCRIPT_FINAL (state) { + state.transcript.final = ''; + }, + CLEAR_TRANSCRIPT_TYPED (state) { + state.transcript.typed = ''; + }, APPEND_TRANSCRIPT_FINAL (state, { transcriptFinal }) { if (state.transcript.final.length && state.transcript.final.charAt(state.transcript.final.length - 1) != ' ') { // Current final string is not empty and doesn't end in a @@ -258,6 +285,14 @@ const mutations = { SET_WAITING_FOR_INITIAL_TRANSCRIPT (state, { waitingForInitial }) { state.transcript.waitingForInitial = waitingForInitial; }, + + SET_TYPING_MODE_ON (state) { + state.typingModeOn = true; + }, + + SET_TYPING_MODE_OFF (state) { + state.typingModeOn = false; + }, } const getters = { diff --git a/app/store/mutations.js b/app/store/mutations.js index 68246e8..51acf57 100755 --- a/app/store/mutations.js +++ b/app/store/mutations.js @@ -136,6 +136,10 @@ export default { state.detached = false; }, + SET_DETACHED_MODE_OFF: (state) => { + state.detached = false; + }, + SET_LAST_WHATS_NEW_VERSION_SEEN: (state, { version }) => { state.settings.lastWhatsNewVersionSeen = version; },