improvments

This commit is contained in:
Andrew Gundersen 2021-03-17 09:32:32 -05:00
commit a6ebab0dea
8 changed files with 303 additions and 332 deletions

View file

@ -0,0 +1,78 @@
import { onMounted } from "vue";
import useMitt from "@/modules/mitt";
import { useIpc } from '@/modules/ipc';
import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
import { renderMessage } from '@/modules/message';
export default function useAudioInputController () {
// For sending messages.
const { post } = useIpc();
const { emitter } = useMitt();
// Keepp track of when we are recording.
let recording = false;
// Utility function to render message.
const render = (text: string, audio: boolean | string, context: string, modifier: string): string => {
const m = renderMessage({ text, audio, context, modifier });
emitter.emit("self-message", m);
return m.uid;
}
//---Callbacks-----------------------------------------------
const onKeyDown = (e: KeyboardEvent) => {
const cmd = keyboardNameMap[e.keyCode];
// Start recording on space bar.
if (cmd == "SPACE" && !recording) {
post('update-recorder', {
isRecording: true,
uid: null
});
recording = true;
}
}
const onKeyUp = (e: KeyboardEvent) => {
const cmd = keyboardNameMap[e.keyCode];
// Stop recording on space up.
if (cmd == "SPACE" && recording) {
// Render audio immediately.
const uid = render("", "", "", "sf");
post('update-recorder', {
isRecording: false,
uid: uid
});
recording = false;
}
}
//-----------------------------------------------------------
onMounted(() => {
window.addEventListener("keydown", onKeyDown);
window.addEventListener("keyup", onKeyUp);
})
return {
recording
}
}

View file

@ -0,0 +1,173 @@
import anime from "animejs";
import useMitt from "@/modules/mitt";
import { useIpc } from '@/modules/ipc';
import { Ref, watch, onMounted } from "vue";
import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
import { clientMessage, renderMessage } from '@/modules/message';
// Post a message to the backend.
const { post } = useIpc();
//---Animations-----------------------------------------------
let side = "right"; // Side of parent we are on.
let visible = false; // Whether textInput is visible.
function showTextInput () {
let t1 = (side === "right") ? 50 : -70;
let t2 = (side === "right") ? 110 : -130;
anime({
targets: '#textInput',
opacity: [0, 1],
translateX: [t1, t2],
scale: [0.3, 1],
duration: 500,
easing: 'easeOutExpo',
})
visible = true
}
function hideTextInput() {
let t = (side === "right") ? 50 : -80;
anime({
targets: '#textInput',
opacity: 0,
translateX: t,
scale: 0.3,
duration: 500,
easing: 'easeOutExpo',
})
visible = false
}
function switchSide(currentSide: string) {
let t = (currentSide === "right") ? -130 : 110;
anime({
targets: '#textInput',
translateX: t,
duration: 500,
easing: 'easeOutExpo',
})
}
//------------------------------------------------------------
export default function useTextInputController(elementX: Ref) {
let textInput: HTMLInputElement | null;
let length: number;
// For sending messages.
const { post } = useIpc();
const { emitter } = useMitt();
// Keep track of first key press.
let firstKey = true;
// Clear text input.
const clearInput = () => {
if (textInput) textInput.value = "";
hideTextInput()
firstKey = true;
}
// Utility function to render message.
const render = (text: string, audio: boolean | string, context: string, modifier: string): string => {
const m = renderMessage({ text, audio, context, modifier });
emitter.emit("self-message", m);
return m.uid;
}
// Send a message and clean up after.
const sendMessage = () => {
if (textInput) {
// Render message
const uid = render(textInput.value, false, "", "sf");
// Send it to the backend for processing.
const clientM = clientMessage(textInput.value, false, uid);
post('client-message', clientM);
}
}
//---Callbacks-----------------------------------------------
const onKeyDown = (e: KeyboardEvent) => {
if (textInput) {
const key = keyboardNameMap[e.keyCode]
// Reveal textInput on first key press.
if (firstKey) {
// Handle for space bar
if (key == "SPACE") {
return
}
showTextInput()
firstKey = false
}
textInput.focus();
if (textInput.value == "" && !firstKey) {
clearInput()
}
if (key == "ENTER") {
if (textInput.value) {
sendMessage()
clearInput()
}
}
if (key == "ESCAPE") {
clearInput()
}
}
}
//-----------------------------------------------------------
// Watch parent position and update side.
watch(elementX, (elementX, previous) => {
const winW = window.innerWidth
// Logic depends on the side we are on.
if (side === "right") {
if (winW - elementX < 230) {
switchSide(side)
side = "left"
}
}
else {
if (winW - elementX > 230) {
switchSide(side)
side = "right"
}
}
});
onMounted(() => {
textInput = document.getElementById("textInput") as HTMLInputElement;
window.addEventListener("keydown", onKeyDown);
})
return {
}
}

View file

@ -1,189 +0,0 @@
/*
* Main logic used by inputItem component.
* Will visualize text input by default as user types.
* If user holds space bar down, input audio stream will
* be recorded and sent to the backend for analysis.
*/
import { ref, watch, onUnmounted, onMounted } from "vue";
import useMitt from "@/modules/mitt";
import { useIpc } from '@/modules/ipc';
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
import anime from "animejs";
import { clientMessage, renderMessage } from '@/modules/message';
let textInput: HTMLInputElement | null;
// Animation functions for text input element.
function expandInput() {
anime({
targets: textInput,
width: ['0px', '150px'],
duration: 500,
easing: 'easeOutExpo',
})
}
function retractInput() {
anime({
targets: textInput,
width: ['150px', '0px'],
duration: 500,
easing: 'easeOutExpo',
begin: function() {
if (textInput) textInput.blur();
}
})
}
export default function useInputController() {
const { emitter } = useMitt();
const { post } = useIpc();
const isRecording = ref(false);
const isTyping = ref(false);
const input = ref("");
const capsLock = ref(false);
// Utility function to render message.
const render = (text: string, audio: boolean | string, context: string, modifier: string): string => {
const m = renderMessage({ text, audio, context, modifier });
emitter.emit("self-message", m);
console.log(m.time)
return m.uid;
}
// send message on ENTER.
const onEnter = () => {
if (input.value) {
// Render the message immediately and get its unique id.
const uid = render(input.value, false, "", "sf");
// Then send it to the backend for processing.
const clientM = clientMessage(input.value, false, uid);
post('client-message', clientM);
// Reset the text input.
if (textInput) textInput.value = '';
input.value = " ";
}
}
// update input on keydown event & listen for special commands.
const onKeyDown = (e: KeyboardEvent) => {
if (textInput) {
textInput.focus();
textInput.value = input.value;
}
// keyboardCharMap is an array of arrays, with each inner
// array having 2 columns - un-shifted, and shifted values.
// iCol = 0 is the un-shifted value, while iCol=1 is the shifted value.
// See the "keyboardCharMap" module for possible keys.
// MODIFY keyboardCharMap to suit your needs if you want
// more/less/different characters to be considered "printable".
let iCol = 0;
if (e.shiftKey || capsLock.value) {
iCol = 1;
}
const ch = keyboardCharMap[e.keyCode][iCol];
// Optionally do things with non-printables,
// like CR, ESC, Backspace, LF, Tab, Arrows, F1-F24, etc.
// See the arrary "keyboardNameMap" module for possible keys.
const cmd = keyboardNameMap[e.keyCode];
switch (cmd) {
case "ENTER":
onEnter();
break;
case "BACK_SPACE":
input.value = input.value.slice(0, -1);
break;
case "CAPS_LOCK":
capsLock.value = true;
break;
case "ESCAPE":
if (textInput) textInput.value = ''
input.value = "";
break;
default:
input.value += ch;
input.value.trim();
break;
}
}
// stop stream recording and render audio message on space key up event.
const onKeyUp = (e: KeyboardEvent) => {
if (e.key === " " && isRecording.value) {
isRecording.value = false;
// // Render the message immediately and get its unique id.
const uid = render("", "", "", "sf");
// Notify main process to stop recording and send send audio to backend for analysis.
post('update-recorder', {
isRecording: isRecording.value,
uid
});
// Reset input.
input.value = ''
}
else if (e.code === "CapsLock") {
capsLock.value = false;
}
}
onMounted(() => {
textInput = document.getElementById('textInput') as HTMLInputElement;
// add window event keyboard listeners
window.addEventListener("keydown", onKeyDown);
window.addEventListener('keyup', onKeyUp);
})
onUnmounted(() => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("keyup", onKeyUp);
});
// watch keyboard input & update inputItem state.
watch(input, (input, prevInput) => {
if (isTyping.value && input.length === 0) {
retractInput();
}
// Do nothing if user is typing.
if (prevInput !== "" || prevInput.length > 0) {
return;
}
// Begin stream recording on space bar.
if (input === " " && isRecording.value === false) {
isTyping.value = false;
isRecording.value = true;
// tell main process to begin stream recording.
post('update-recorder', {
isRecording: isRecording.value,
uid: null
});
return;
}
isTyping.value = true;
expandInput()
});
return {
isRecording,
isTyping
}
}

View file

@ -2,22 +2,25 @@
<div
id="inputItem"
class="input-item"
:class="{ playing: isRecording }"
:class="{ playing: recording }"
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
>
AG
<!-- animations for recording and on-standby -->
<span v-if="isRecording" class="play"></span>
<span v-if="isRecording" class="pause">
<!-- Show audio input on spacebar -->
<AudioInput />
<span v-if="recording" class="play"></span>
<span v-if="recording" class="pause">
<div class="rec-icon"></div>
</span>
<!-- Show text input on key-down -->
<TextInput :position="elementX"/>
<TextInput />
<!-- Show suggestions menu on click -->
<!-- <SuggestionsMenu /> -->
<!-- <Menu /> -->
</div>
</template>
@ -26,8 +29,12 @@
import { defineComponent } from "vue";
import draggify from "@/modules/draggify";
import useInputController from "./inputController";
import TextInput from "@/components/inputItem/textInput.vue"
import useTextInputController from
"@/components/inputItem/controllers/textCtrl"
import useAudioInputController from
"@/components/inputItem/controllers/audioCtrl"
export default defineComponent({
name: "InputItem",
@ -39,14 +46,14 @@ export default defineComponent({
// Calculate position of inputItem on drag.
const { elementX, elementY } = draggify({ elementId: "inputItem" });
// determine whether the user is typing or recording.
const { isRecording, isTyping } = useInputController();
// Controllers for text and audio.
useTextInputController(elementX)
const { recording } = useAudioInputController()
return {
elementX,
elementY,
isRecording,
isTyping,
recording
};
},
});
@ -57,6 +64,8 @@ export default defineComponent({
.input-item {
position: absolute;
opacity: 1.0;
display: flex;
align-items: center;
justify-content: center;
@ -66,12 +75,12 @@ export default defineComponent({
width: 40px;
height: 40px;
border: 4px solid #C6C6C6;
border-radius: 24px;
z-index: 3;
background-color: white;
// border: 4px solid #C6C6C6;
border-radius: 20px;
background-color: #EBEBEB;
cursor: pointer;

View file

@ -1,70 +0,0 @@
<template>
<div id="menu">
<ul id="menu-list">
<li
v-for="suggestion in menuItems.values()"
@click="submitSuggestion(suggestion)"
>
{{ suggestion }}
</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import useMenu from "./menuController";
export default defineComponent({
name: "SuggestionsMenu",
setup() {
const menuItems = ref(new Map())
// menuItems.value.set(0, 'add contact')
// menuItems.value.set(1, 'delete contact')
// menuItems.value.set(2, 'show contacts')
// menuItems.value.set(3, 'send a message')
const { submitSuggestion } = useMenu();
return {
menuItems,
submitSuggestion
}
}
})
</script>
<style lang="scss" scoped>
#menu {
margin-top: 20px;
min-width: 200px;
height: 200px;
background-color: white;
border-radius: 18px;
margin: 0;
padding: 0;
animation-name: menu-appear;
animation-duration: 1s;
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
}
#menu-list {
margin: 0;
padding: 0;
}
@keyframes menu-appear {
from {
height: 0px;
}
to {
height: 200px;
}
}
</style>

View file

@ -1,22 +0,0 @@
export default function useMenu() {
const submitSuggestion = (suggestion: string) => {
console.log(`Submit...${suggestion}`)
// // Render the message immediately and get its unique id.
// const uid = render(suggestion, false, "", "sf");
// // Then send it to the backend for processing.
// const clientM = clientMessage(suggestion, false, uid);
// post('client-message', clientM);
}
return {
submitSuggestion
}
}

View file

@ -1,11 +1,9 @@
<template>
<div
class="text-input"
:class="`text-input-${updateSide}`"
>
<input id="textInput" type="text" />
</div>
<input
id="textInput"
type="text"
/>
</template>
@ -15,54 +13,48 @@ import { defineComponent, ref, watch } from "vue";
export default defineComponent({
name: "TextInput",
data: {
// Where is avatar?
position: Number
},
setup(data) {
const side = ref("")
side.value = "left"
// Watch the position of parent.
// watch(data.position, (position, prevPosition) => {
// console.log("position moved")
// })
return {
side
}
}
})
</script>
<style lang="scss" scoped>
.text-input {
#textInput {
position: absolute;
opacity: 0;
min-width: 150px;
height: 36px;
height: 16px;
border-radius: 18px;
padding: 10px;
margin-right: 10px;
margin-left: 10px;
outline: none;
border: none;
pointer-events: none;
background-color: white;
z-index: -1;
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
}
// Activate if we need
.text-input-left {
left: -200px;
background-color: red;
transform: translateX(50px) scale(0.3);
}
@keyframes appear {
from {
opacity: 0;
transform: translateX(50px) scale(0.3);
}
to {
opacity: 1;
transform: translateX(110px) scale(1.0);
}
}
</style>

View file

@ -113,8 +113,8 @@ export default function draggify(input: { elementId: string }) {
});
// Initialize the coordinants.
elementX.value = 20;
elementY.value = 200 - 38 - 20;
elementX.value = 15;
elementY.value = 300;
// remove event listeners on component dismount.
onUnmounted(() => {