Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
69a9dacb90
34 changed files with 874 additions and 799 deletions
|
|
@ -1,5 +1,9 @@
|
||||||
|
"use strict";
|
||||||
|
/* eslint @typescript-eslint/no-var-requires: "off" */
|
||||||
|
import { sendAudio } from './session'
|
||||||
const portAudio = require('naudiodon');
|
const portAudio = require('naudiodon');
|
||||||
|
|
||||||
|
let ia: typeof portAudio.AudioIO;
|
||||||
let audioInput: string[] = [];
|
let audioInput: string[] = [];
|
||||||
|
|
||||||
function processAudio(audioInput: Array<string>): Buffer {
|
function processAudio(audioInput: Array<string>): Buffer {
|
||||||
|
|
@ -8,42 +12,39 @@ function processAudio(audioInput: Array<string>): Buffer {
|
||||||
return Buffer.from(audio, "base64");
|
return Buffer.from(audio, "base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updateRecorder = (_event, record: boolean): void => {
|
||||||
const updateRecorder = async (Event: any, record: boolean): void => {
|
|
||||||
if (record) {
|
if (record) {
|
||||||
ia.resume();
|
ia.resume();
|
||||||
} else {
|
} else {
|
||||||
ia.pause();
|
ia.pause();
|
||||||
const audio = processAudio(audioInput);
|
const audio = processAudio(audioInput);
|
||||||
// emmit ws event with audio
|
sendAudio(audio);
|
||||||
// sendAudio(audio);
|
|
||||||
audioInput = [];
|
audioInput = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const initRecorder = () => {
|
||||||
|
|
||||||
const ia = new portAudio.AudioIO({
|
if (!ia) {
|
||||||
inOptions: {
|
ia = new portAudio.AudioIO({
|
||||||
channelCount: 1,
|
inOptions: {
|
||||||
sampleFormat: 16,
|
channelCount: 1,
|
||||||
sampleRate: 16000,
|
sampleFormat: 16,
|
||||||
deviceId: -1,
|
sampleRate: 16000,
|
||||||
closeOnError: false,
|
deviceId: -1,
|
||||||
}
|
closeOnError: false,
|
||||||
});
|
}
|
||||||
ia.setEncoding('base64');
|
});
|
||||||
ia.start();
|
ia.setEncoding('base64');
|
||||||
ia.pause();
|
ia.start();
|
||||||
ia.on('error', (e: Error) => {
|
ia.pause();
|
||||||
console.log('Error recording audio', + e);
|
ia.on('error', (e: Error) => {
|
||||||
});
|
console.log('Error recording audio', + e);
|
||||||
ia.on('data', (chunk: string) => {
|
});
|
||||||
audioInput.push(chunk);
|
ia.on('data', (chunk: string) => {
|
||||||
});
|
audioInput.push(chunk);
|
||||||
|
});
|
||||||
export function useIA() {
|
|
||||||
return {
|
|
||||||
ia,
|
|
||||||
processAudio
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { createWindow } from './window';
|
import { createWindow } from './window';
|
||||||
import { ipcMain, BrowserWindow } from "electron";
|
|
||||||
import { initSession } from './session';
|
import { initSession } from './session';
|
||||||
import { backgroundMitt } from './emitter';
|
import { initRecorder } from './audio';
|
||||||
// const portAudio = require('naudiodon');
|
|
||||||
|
|
||||||
interface RenderMessage {
|
let socket = false;
|
||||||
content: string;
|
|
||||||
context: string;
|
|
||||||
subContext: string;
|
|
||||||
modifiers: string;
|
|
||||||
time: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IpcRendererPayload {
|
|
||||||
endpoint: string;
|
|
||||||
message: RenderMessage | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let win: BrowserWindow | null;
|
|
||||||
let activeSession = false;
|
|
||||||
|
|
||||||
backgroundMitt.on('ipc-renderer', (payload: IpcRendererPayload) => {
|
|
||||||
if (win)
|
|
||||||
win.webContents.send(payload.endpoint, {
|
|
||||||
message: payload.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const windowDismount = (): void => {
|
|
||||||
win = null;
|
|
||||||
backgroundMitt.emit('window-active', false);
|
|
||||||
ipcMain.removeAllListeners('send-message');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The main function will be run after electron app is ready.
|
* The main function will be run after electron app is ready.
|
||||||
|
|
@ -42,55 +12,17 @@ const windowDismount = (): void => {
|
||||||
export async function main() {
|
export async function main() {
|
||||||
|
|
||||||
// create main window.
|
// create main window.
|
||||||
if (!win) win = await createWindow({
|
await createWindow({
|
||||||
width: 350,
|
width: 350,
|
||||||
height: 525,
|
height: 525,
|
||||||
resizable: true
|
resizable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Instantiate socket session with crimata-platorm.
|
// Instantiate socket session with crimata-platorm.
|
||||||
if (!activeSession) initSession();
|
if (!socket) initSession();
|
||||||
|
socket = true;
|
||||||
|
|
||||||
activeSession = true;
|
// Begin audio stream.
|
||||||
|
initRecorder();
|
||||||
|
|
||||||
// Handle window close.
|
|
||||||
win.on("closed", windowDismount);
|
|
||||||
|
|
||||||
// TODO: FIX recorder restart bug
|
|
||||||
// let audioInput: string[] = [];
|
|
||||||
// const updateRecorder = async (Event: any, record: boolean): void => {
|
|
||||||
// if (record) {
|
|
||||||
// ia.resume();
|
|
||||||
// } else {
|
|
||||||
// ia.pause();
|
|
||||||
// const audio = processAudio(audioInput);
|
|
||||||
// sendAudio(audio);
|
|
||||||
// audioInput = [];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function processAudio(audioInput: Array<string>): Buffer {
|
|
||||||
// let audio = '';
|
|
||||||
// audioInput.forEach(chunk => audio += chunk);
|
|
||||||
// return Buffer.from(audio, "base64");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const ia = new portAudio.AudioIO({
|
|
||||||
// inOptions: {
|
|
||||||
// channelCount: 1,
|
|
||||||
// sampleFormat: 16,
|
|
||||||
// sampleRate: 16000,
|
|
||||||
// deviceId: -1,
|
|
||||||
// closeOnError: false,
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// ia.setEncoding('base64');
|
|
||||||
// ia.start();
|
|
||||||
// ia.pause();
|
|
||||||
// ia.on('error', (e: Error) => {
|
|
||||||
// console.log('Error recording audio', + e);
|
|
||||||
// });
|
|
||||||
// ia.on('data', (chunk: string) => {
|
|
||||||
// audioInput.push(chunk);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ interface RenderMessage {
|
||||||
content: string;
|
content: string;
|
||||||
context: string;
|
context: string;
|
||||||
subContext: string;
|
subContext: string;
|
||||||
modifiers: string;
|
modifier: string;
|
||||||
time: string;
|
time: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
@ -32,22 +32,29 @@ let auth = false;
|
||||||
|
|
||||||
const authSession = async (_event, payload: string | UserCreds | null): Promise<string> => {
|
const authSession = async (_event, payload: string | UserCreds | null): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
console.log('authenticating...');
|
console.log('authenticating...');
|
||||||
backgroundMitt.on('auth-res', (res: string) => {
|
|
||||||
|
backgroundMitt.once('auth-res', (res: string) => {
|
||||||
|
|
||||||
if (res === 'locked') {
|
if (res === 'locked') {
|
||||||
reject(res);
|
reject(res);
|
||||||
} else {
|
} else {
|
||||||
|
console.log('Success!');
|
||||||
auth = true;
|
auth = true;
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
socket.send('no-token');
|
socket.send('no-token');
|
||||||
} else if (typeof payload === 'string' || payload instanceof String) {
|
} else if (payload instanceof String || typeof payload === 'string') {
|
||||||
socket.send(payload);
|
socket.send(payload);
|
||||||
} else {
|
} else {
|
||||||
socket.send(JSON.stringify(payload));
|
socket.send(JSON.stringify(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -59,22 +66,52 @@ const receiveMessage = (message: string): void => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed: RenderMessage = JSON.parse(message);
|
const parsed: RenderMessage = JSON.parse(message);
|
||||||
|
if (parsed.modifier === "sf") {
|
||||||
|
backgroundMitt.emit('message-res', parsed);
|
||||||
|
} else if (parsed.modifier === "ai") {
|
||||||
|
backgroundMitt.emit('ipc-renderer', {
|
||||||
|
endpoint: 'render-message',
|
||||||
|
message: parsed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const asyncSend = (_event?, payload: TextMessage | Buffer): Promise<RenderMessage> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
backgroundMitt.once('message-res', (res: RenderMessage) => {
|
||||||
|
if (res.content) {
|
||||||
|
resolve(res);
|
||||||
|
} else {
|
||||||
|
reject('ERROR: NO MESSAGE');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (payload instanceof Buffer) {
|
||||||
|
socket.send(payload);
|
||||||
|
} else if (payload) {
|
||||||
|
socket.send(JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendAudio = async (content: Buffer) => {
|
||||||
|
|
||||||
|
const res = await asyncSend(null, content);
|
||||||
|
|
||||||
backgroundMitt.emit('ipc-renderer', {
|
backgroundMitt.emit('ipc-renderer', {
|
||||||
endpoint: 'render-message',
|
endpoint: 'render-message',
|
||||||
message: parsed
|
message: res
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const sendMessage = (content: TextMessage | Buffer) => {
|
|
||||||
if (content instanceof Buffer) {
|
|
||||||
socket.send(content);
|
|
||||||
} else if (content) {
|
|
||||||
socket.send(JSON.stringify(content));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run every time we want to connect to backend.
|
||||||
export const initSession = () => {
|
export const initSession = () => {
|
||||||
|
|
||||||
|
// Close existing sockets.
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.removeAllListeners();
|
socket.removeAllListeners();
|
||||||
socket.terminate();
|
socket.terminate();
|
||||||
|
|
@ -82,25 +119,33 @@ export const initSession = () => {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init new socket.
|
||||||
socket = new WebSocket(`${ip}:${port}`);
|
socket = new WebSocket(`${ip}:${port}`);
|
||||||
socket.binaryType = 'arraybuffer';
|
socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
// handle renderer auth-token event
|
// handle renderer auth-token event
|
||||||
ipcMain.removeHandler('auth-user'); // avoid setting duplicate handlers
|
ipcMain.removeHandler('auth-session'); // avoid setting duplicate handlers
|
||||||
ipcMain.handle('auth-user', authSession);
|
ipcMain.handle('auth-session', authSession);
|
||||||
|
|
||||||
|
// handle renderer send-message event
|
||||||
|
ipcMain.removeHandler('message'); // avoid setting duplicate handlers
|
||||||
|
ipcMain.handle('message', asyncSend);
|
||||||
|
|
||||||
|
// Connect to the backend.
|
||||||
socket.on('open', () => {
|
socket.on('open', () => {
|
||||||
console.log('Success! Connected to Crimata Servers.');
|
console.log('Connected to Crimata Servers.');
|
||||||
|
|
||||||
// fetch token from renderer
|
// call auth renderer event on connection
|
||||||
backgroundMitt.emit('ipc-renderer', {
|
backgroundMitt.emit('ipc-renderer', {
|
||||||
endpoint: 'fetch-token',
|
endpoint: 'auth',
|
||||||
message: null
|
message: null
|
||||||
});
|
});
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle for failed connect, try again.
|
||||||
socket.on('error', (_e) => {
|
socket.on('error', (_e) => {
|
||||||
console.log('ERROR: Failed to connect.');
|
console.log('ERROR: Failed to connect.');
|
||||||
socket.removeAllListeners();
|
socket.removeAllListeners();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { BrowserWindow, ipcMain } from "electron";
|
import { BrowserWindow, ipcMain } from "electron";
|
||||||
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
||||||
import { backgroundMitt } from './emitter';
|
import { backgroundMitt } from './emitter';
|
||||||
import { sendMessage } from './session';
|
import { updateRecorder } from './audio';
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
interface WindowSettings {
|
interface WindowSettings {
|
||||||
|
|
@ -12,23 +12,46 @@ interface WindowSettings {
|
||||||
resizable: boolean;
|
resizable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextMessage {
|
interface RenderMessage {
|
||||||
audio: 0;
|
content: string;
|
||||||
content: string;
|
context: string;
|
||||||
|
subContext: string;
|
||||||
|
modifiers: string;
|
||||||
|
time: string;
|
||||||
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMessage = (_event, arg: TextMessage | Buffer ) => {
|
interface IpcRendererPayload {
|
||||||
sendMessage(arg);
|
endpoint: string;
|
||||||
|
message: RenderMessage | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let win: BrowserWindow | null;
|
||||||
|
|
||||||
const windowMount = (): void => {
|
const windowMount = (): void => {
|
||||||
backgroundMitt.emit('window-active', true);
|
backgroundMitt.emit('window-active', true);
|
||||||
ipcMain.on('send-message', handleMessage);
|
ipcMain.on('update-recorder', updateRecorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createWindow = async (options: WindowSettings): Promise<BrowserWindow> => {
|
const windowDismount = (): void => {
|
||||||
|
win = null;
|
||||||
|
backgroundMitt.emit('window-active', false);
|
||||||
|
ipcMain.removeAllListeners('update-recorder');
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundMitt.on('ipc-renderer', (payload: IpcRendererPayload) => {
|
||||||
|
if (win)
|
||||||
|
win.webContents.send(payload.endpoint, {
|
||||||
|
message: payload.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createWindow = async (options: WindowSettings): Promise<void> => {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
const win: BrowserWindow = new BrowserWindow({
|
|
||||||
|
if (win) resolve();
|
||||||
|
|
||||||
|
win = new BrowserWindow({
|
||||||
width: options.width,
|
width: options.width,
|
||||||
height: options.height,
|
height: options.height,
|
||||||
resizable: options.resizable,
|
resizable: options.resizable,
|
||||||
|
|
@ -50,9 +73,12 @@ export const createWindow = async (options: WindowSettings): Promise<BrowserWind
|
||||||
win.loadURL("app://./index.html");
|
win.loadURL("app://./index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle window close.
|
||||||
|
win.on("closed", windowDismount);
|
||||||
|
|
||||||
win.webContents.on('did-finish-load', () => {
|
win.webContents.on('did-finish-load', () => {
|
||||||
windowMount();
|
windowMount();
|
||||||
resolve(win);
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
id="messenger"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
@click="emittMessage"
|
|
||||||
>
|
|
||||||
<MessageVisualizer />
|
|
||||||
<TextAudioInput />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
// child components
|
|
||||||
import MessageVisualizer from "./messageVisualizer/index.vue";
|
|
||||||
import TextAudioInput from "@/components/taInput/index.vue";
|
|
||||||
|
|
||||||
// event emitter
|
|
||||||
import { Mitt } from "@/types/mitt/index";
|
|
||||||
|
|
||||||
import { defineComponent, ref, inject } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
|
|
||||||
name: "Messenger",
|
|
||||||
components: { MessageVisualizer, TextAudioInput },
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const count = ref(0);
|
|
||||||
|
|
||||||
// imports mitt Emitter safely
|
|
||||||
let emitter: Mitt;
|
|
||||||
const emitterInject: Mitt | undefined = inject("mitt");
|
|
||||||
|
|
||||||
if (emitterInject) {
|
|
||||||
emitter = emitterInject;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render message when render-message event called.
|
|
||||||
window.ipcRenderer.on("render-message", (event, payload) => {
|
|
||||||
console.log(payload.message);
|
|
||||||
emitter.emit("renderMessage", payload.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
#messenger {
|
|
||||||
width: 350px;
|
|
||||||
height: 500px;
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
|
||||||
// border-radius: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
:viewBox="scrollView"
|
|
||||||
>
|
|
||||||
<g id="messageVisualizer" transform="translate(0, 5)">
|
|
||||||
<g
|
|
||||||
v-for="(item, index) in renderArr"
|
|
||||||
:key="index"
|
|
||||||
class="messageList"
|
|
||||||
transform="translate(0, 25)"
|
|
||||||
preserverAspectRatio="none"
|
|
||||||
>
|
|
||||||
<transition
|
|
||||||
appear
|
|
||||||
v-on:before-appear="beforeEnter"
|
|
||||||
v-on:appear="enter"
|
|
||||||
v-on:after-enter="afterEnter"
|
|
||||||
>
|
|
||||||
<MessageItem :item="item" :key="index" />
|
|
||||||
</transition>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import useMessageVisualizer from "./viewDetails/visualizer";
|
|
||||||
import useScrollView from "@/modules/scrollView";
|
|
||||||
import MessageItem from "../messageBubble/index.vue";
|
|
||||||
import { Message } from "@/types/message/index";
|
|
||||||
import { Mitt } from "@/types/mitt/index";
|
|
||||||
import { defineComponent, reactive, inject } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "MessageVisualizer",
|
|
||||||
components: { MessageItem },
|
|
||||||
setup() {
|
|
||||||
const renderArr: Message[] = reactive([]);
|
|
||||||
|
|
||||||
// imports mitt Emitter safely
|
|
||||||
let emitter: Mitt;
|
|
||||||
const emitterInject: Mitt | undefined = inject("mitt");
|
|
||||||
if (emitterInject) {
|
|
||||||
emitter = emitterInject;
|
|
||||||
emitter.on("renderMessage", (payload: Message | undefined) => {
|
|
||||||
if (payload) renderArr.push(payload);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { beforeEnter, enter, afterEnter } = useMessageVisualizer();
|
|
||||||
const { scrollView } = useScrollView({
|
|
||||||
targetId: "messageVisualizer",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
renderArr,
|
|
||||||
beforeEnter,
|
|
||||||
enter,
|
|
||||||
afterEnter,
|
|
||||||
scrollView,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
svg {
|
|
||||||
height: 1%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#messageRenderer {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
import { inject } from "vue";
|
|
||||||
import AnimeFunc from "@/types/animejs/index";
|
|
||||||
import { MessageAnimeFunc } from '@/types/message/index';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* anime js animations used by message renderer
|
|
||||||
*/
|
|
||||||
export default function useMessageViewAnims(): {
|
|
||||||
shiftAnimate: MessageAnimeFunc;
|
|
||||||
stackAnimate: MessageAnimeFunc;
|
|
||||||
} {
|
|
||||||
// imports animejs safely
|
|
||||||
let anime: AnimeFunc;
|
|
||||||
const animeInject: AnimeFunc | undefined = inject("animejs");
|
|
||||||
if (animeInject) anime = animeInject;
|
|
||||||
|
|
||||||
// shifts message list by a given value
|
|
||||||
function shiftList({ targetList, sv }: { targetList: NodeList | undefined; sv: number | undefined }) {
|
|
||||||
if (targetList) {
|
|
||||||
for (const element of targetList) {
|
|
||||||
const el = element.parentNode as HTMLElement;
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
transform: `translate(0 ${sv})`,
|
|
||||||
easing: "easeInOutQuad",
|
|
||||||
duration: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// adds new message at the bottom of the list and shifts existing message list
|
|
||||||
const shiftAnimate: MessageAnimeFunc = (el, transform, shiftValue, targetList) => {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
transform: transform.initialTranslate,
|
|
||||||
easing: "easeInOutQuad",
|
|
||||||
duration: 500,
|
|
||||||
begin: function() {
|
|
||||||
shiftList({
|
|
||||||
targetList,
|
|
||||||
sv: shiftValue
|
|
||||||
});
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
transform: transform.finalTranslate,
|
|
||||||
easing: "easeInOutQuad",
|
|
||||||
duration: 500,
|
|
||||||
offset: 1000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// stacks message from top to bottom
|
|
||||||
const stackAnimate: MessageAnimeFunc = (el, transform) => {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
transform: transform.initialTranslate,
|
|
||||||
easing: "easeInOutQuad",
|
|
||||||
duration: 1000,
|
|
||||||
complete: function() {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
transform: transform.finalTranslate,
|
|
||||||
easing: "easeInOutQuad",
|
|
||||||
duration: 500,
|
|
||||||
offset: 1000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
shiftAnimate,
|
|
||||||
stackAnimate
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
|
|
||||||
import useMessageViewAnims from "./anime";
|
|
||||||
import useOffsetCalculator from './offsetCalculator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handles visualization of view messages through
|
|
||||||
* vue transition callbacks and anime-js animations
|
|
||||||
*/
|
|
||||||
export default function useMessageVisualizer(): {
|
|
||||||
beforeEnter: VueTransitionCallback;
|
|
||||||
enter: VueTransitionCallback;
|
|
||||||
afterEnter: VueTransitionCallback;
|
|
||||||
} {
|
|
||||||
// uses offsetCalculator logic and renderer animations
|
|
||||||
const { calculateMessageOffsets } = useOffsetCalculator();
|
|
||||||
const { shiftAnimate, stackAnimate } = useMessageViewAnims();
|
|
||||||
|
|
||||||
const messagePadding = 15;
|
|
||||||
let messageList: NodeList;
|
|
||||||
let message: HTMLElement | null;
|
|
||||||
let messageOffset = { x: 0, y: 0 };
|
|
||||||
let shift = false;
|
|
||||||
let shiftOffset = 0;
|
|
||||||
|
|
||||||
// animates message item to 'enter' renderView
|
|
||||||
const animateMessage = (el: SVGGElement): void => {
|
|
||||||
|
|
||||||
const transform = {
|
|
||||||
initialTranslate: `translate(${messageOffset.x} ${messageOffset.y})`,
|
|
||||||
finalTranslate: `translate(${messageOffset.x} ${messageOffset.y})`
|
|
||||||
}
|
|
||||||
shift ? shiftAnimate(el, transform, shiftOffset, messageList) : stackAnimate(el, transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeEnter: VueTransitionCallback = () => {
|
|
||||||
const queryResult = document.querySelectorAll(".messageList");
|
|
||||||
if (queryResult.length) messageList = queryResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enter: VueTransitionCallback = (el) => {
|
|
||||||
// query DOM for latest message as soon as it enters
|
|
||||||
if (el) {
|
|
||||||
message = document.getElementById(el.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const afterEnter: VueTransitionCallback = (el) => {
|
|
||||||
// get message positioning & shift state before animating
|
|
||||||
if (message) {
|
|
||||||
messageOffset = calculateMessageOffsets(message);
|
|
||||||
shift = messageOffset.y > 400 ? true : false;
|
|
||||||
}
|
|
||||||
if (el) {
|
|
||||||
if (shift) {
|
|
||||||
|
|
||||||
console.log(messageOffset.y)
|
|
||||||
if (messageOffset.y < 445) {
|
|
||||||
shiftOffset -= 15;
|
|
||||||
} else {
|
|
||||||
shiftOffset -= el.getBBox().height + messagePadding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animateMessage(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
beforeEnter,
|
|
||||||
enter,
|
|
||||||
afterEnter
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="login">
|
|
||||||
|
|
||||||
<!-- login title -->
|
|
||||||
<div id="loginTitle">Login</div>
|
|
||||||
|
|
||||||
<!-- username and password forms -->
|
|
||||||
<div id="loginBox">
|
|
||||||
|
|
||||||
<!-- username -->
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
v-model="login.username"
|
|
||||||
placeholder="username"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- password -->
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
type="password"
|
|
||||||
v-model="login.password"
|
|
||||||
placeholder="Password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- submit button -->
|
|
||||||
<button class="button" v-on:click="submitCreds">Submit</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
|
|
||||||
name: "Login",
|
|
||||||
|
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
login: {
|
|
||||||
username: "",
|
|
||||||
password: ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
#login {
|
|
||||||
width: 350px;
|
|
||||||
height: 500px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loginBox {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
background-color: #B9B9B9;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
padding: 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
margin: 4px 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
background-color: #58C4FD;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 20px;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
92
src/components/messageView/index.vue
Normal file
92
src/components/messageView/index.vue
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
:viewBox="scrollView"
|
||||||
|
>
|
||||||
|
<g id="messageView" transform="translate(0, 20)">
|
||||||
|
<g v-for="(item, index) in renderArr" :key="index" preserverAspectRatio="none">
|
||||||
|
<transition
|
||||||
|
appear
|
||||||
|
v-on:before-appear="beforeEnter"
|
||||||
|
v-on:appear="enter"
|
||||||
|
v-on:after-enter="afterEnter"
|
||||||
|
>
|
||||||
|
<TextBubble :item="item" :key="index" />
|
||||||
|
</transition>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import TextBubble from "@/components/textBubble/index.vue";
|
||||||
|
import { Message } from "@/types/message/index";
|
||||||
|
import useTransitions from "./viewDetails/transitions";
|
||||||
|
import useScrollView from "@/modules/scrollView";
|
||||||
|
import useMitt from "@/modules/mitt";
|
||||||
|
import { useIpc } from "@/modules/ipc";
|
||||||
|
import { Queue } from '@/modules/queue';
|
||||||
|
import { defineComponent, reactive, onMounted, onUnmounted, watch } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "MessageView",
|
||||||
|
components: { TextBubble },
|
||||||
|
setup() {
|
||||||
|
const { emitter } = useMitt();
|
||||||
|
const renderArr: Message[] = reactive([]);
|
||||||
|
const renderQ = new Queue();
|
||||||
|
|
||||||
|
const { invoke } = useIpc("message");
|
||||||
|
const { beforeEnter, enter, afterEnter, rendering } = useTransitions();
|
||||||
|
const { scrollView } = useScrollView({
|
||||||
|
targetId: "messageView",
|
||||||
|
});
|
||||||
|
|
||||||
|
const render = (m: Message) => {
|
||||||
|
if (rendering.value) {
|
||||||
|
renderQ.enqueue(m);
|
||||||
|
} else {
|
||||||
|
renderArr.push(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(rendering, (rendering, _prevRendering) => {
|
||||||
|
if (!rendering) {
|
||||||
|
const m = renderQ.dequeue() as Message | null;
|
||||||
|
if (m) renderArr.push(m);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.ipcRenderer.on("render-message", (event, payload) => {
|
||||||
|
const m = payload.message;
|
||||||
|
if (m.content) render(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
emitter.on("user-message", async (message) => {
|
||||||
|
try {
|
||||||
|
const res = await invoke(message);
|
||||||
|
render(res)
|
||||||
|
} catch (e) {
|
||||||
|
console.log("No response. Do not render.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.ipcRenderer.removeAllListeners("render-message");
|
||||||
|
emitter.all.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderArr,
|
||||||
|
beforeEnter,
|
||||||
|
enter,
|
||||||
|
afterEnter,
|
||||||
|
scrollView,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
60
src/components/messageView/viewDetails/anime.ts
Normal file
60
src/components/messageView/viewDetails/anime.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { inject } from "vue";
|
||||||
|
import AnimeFunc from "@/types/animejs/index";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* anime js animations used by message view transitions
|
||||||
|
*/
|
||||||
|
export default function useMessageViewAnims() {
|
||||||
|
// imports animejs safely
|
||||||
|
let anime: AnimeFunc;
|
||||||
|
const animeInject: AnimeFunc | undefined = inject("animejs");
|
||||||
|
if (animeInject) anime = animeInject;
|
||||||
|
|
||||||
|
// update message View bottom bound
|
||||||
|
const shiftView = (el: SVGGElement, transform: string) => {
|
||||||
|
anime({
|
||||||
|
targets: el,
|
||||||
|
transform: transform,
|
||||||
|
easing: "easeInOutQuad",
|
||||||
|
duration: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// stacks message from top to bottom
|
||||||
|
const stackAnimate = (el: Element, transform: string) => {
|
||||||
|
return new Promise((resolve, _reject) => {
|
||||||
|
anime({
|
||||||
|
targets: el,
|
||||||
|
transform: transform,
|
||||||
|
easing: "easeInOutQuad",
|
||||||
|
duration: 1000,
|
||||||
|
complete: function() {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds new message at the bottom of the list and shifts existing message list
|
||||||
|
const shiftAnimate = (el: Element, transform: string, view: SVGGElement, viewTransform: string) => {
|
||||||
|
return new Promise((resolve, _reject) => {
|
||||||
|
anime({
|
||||||
|
targets: el,
|
||||||
|
transform: transform,
|
||||||
|
easing: "easeInOutQuad",
|
||||||
|
duration: 800,
|
||||||
|
begin: function() {
|
||||||
|
shiftView(view, viewTransform);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shiftAnimate,
|
||||||
|
stackAnimate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ export default function useOffsetCalculator() {
|
||||||
let previousMessageHeight = 0;
|
let previousMessageHeight = 0;
|
||||||
const messagePadding = 15;
|
const messagePadding = 15;
|
||||||
|
|
||||||
function calculateX(message: HTMLElement): void {
|
function calculateX(message: HTMLElement| SVGGElement): void {
|
||||||
const computedQuery = window.getComputedStyle(message);
|
const computedQuery = window.getComputedStyle(message);
|
||||||
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
|
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
|
||||||
xOffset.value = matrix.m41;
|
xOffset.value = matrix.m41;
|
||||||
85
src/components/messageView/viewDetails/transitions.ts
Normal file
85
src/components/messageView/viewDetails/transitions.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* Do stuff before, on, and after message enters the view.
|
||||||
|
* Rendering Process consists of the execution of the above
|
||||||
|
* mentioned callbacks followed by the shift or stack animation.
|
||||||
|
* The animation called depends on the height of the message view
|
||||||
|
* relative to the window height. Additionally, the animation will
|
||||||
|
* always be called on the after enter callback, after all the neccesary
|
||||||
|
* calculations have been completed. While all of this is hapenning,
|
||||||
|
* the rendering state is updated throughout the process.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
|
||||||
|
import useMessageViewAnims from "./anime";
|
||||||
|
import useOffsetCalculator from './offsetCalculator';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const wH = 500; // window Height
|
||||||
|
const vP = 10; // view padding
|
||||||
|
const mP = 15; // message padding
|
||||||
|
let mH: number; // message Height
|
||||||
|
let vH: number; // view Height
|
||||||
|
let vB: number; // view Bottom Bound
|
||||||
|
let message: HTMLElement | null;
|
||||||
|
let messageOffset = { x: 0, y: 0 };
|
||||||
|
let shift = false;
|
||||||
|
let beforeEnterVH: number;
|
||||||
|
let messageView: SVGGElement;
|
||||||
|
|
||||||
|
export default function useTransitions() {
|
||||||
|
const { calculateMessageOffsets } = useOffsetCalculator();
|
||||||
|
const { shiftAnimate, stackAnimate } = useMessageViewAnims();
|
||||||
|
const rendering = ref(false);
|
||||||
|
|
||||||
|
const animateMessage = async (el: SVGGElement) => {
|
||||||
|
|
||||||
|
const elTransform = `translate(${messageOffset.x} ${messageOffset.y})`;
|
||||||
|
const viewTransform = `translate(0 ${-vB})`;
|
||||||
|
|
||||||
|
if (shift) {
|
||||||
|
await shiftAnimate(el, elTransform, messageView, viewTransform);
|
||||||
|
} else {
|
||||||
|
await stackAnimate(el, elTransform)
|
||||||
|
}
|
||||||
|
rendering.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeEnter: VueTransitionCallback = () => {
|
||||||
|
rendering.value = true;
|
||||||
|
// query DOM for messageView element
|
||||||
|
const messageViewQuery = document.querySelectorAll("#messageView");
|
||||||
|
messageView = messageViewQuery[0] as SVGGElement;
|
||||||
|
if (messageViewQuery) {
|
||||||
|
beforeEnterVH = messageView.getBBox().height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const enter: VueTransitionCallback = (el) => {
|
||||||
|
// query DOM for latest message
|
||||||
|
if (el) {
|
||||||
|
message = document.getElementById(el.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const afterEnter: VueTransitionCallback = (el) => {
|
||||||
|
|
||||||
|
if (message) messageOffset = calculateMessageOffsets(message);
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
// calculate message & view heights, as well as view bottom bound
|
||||||
|
mH = el.getBoundingClientRect().height;
|
||||||
|
vH = mH + beforeEnterVH + mP;
|
||||||
|
vB = Math.abs(vH - wH) + vP;
|
||||||
|
|
||||||
|
shift = vH > wH ? true : false;
|
||||||
|
animateMessage(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
beforeEnter,
|
||||||
|
enter,
|
||||||
|
afterEnter,
|
||||||
|
rendering
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="splash">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="57.046"
|
|
||||||
height="52.759"
|
|
||||||
viewBox="0 0 57.046 52.759"
|
|
||||||
>
|
|
||||||
<g transform="translate(-4127 -507)">
|
|
||||||
<g transform="translate(3949 332.206)">
|
|
||||||
<g transform="translate(178 174.794)">
|
|
||||||
<g transform="translate(0 27.405)">
|
|
||||||
<circle
|
|
||||||
cx="12.677"
|
|
||||||
cy="12.677"
|
|
||||||
r="12.677"
|
|
||||||
transform="translate(0 0)"
|
|
||||||
fill="#383838"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="12.677"
|
|
||||||
cy="12.677"
|
|
||||||
r="12.677"
|
|
||||||
transform="translate(31.692 0)"
|
|
||||||
fill="#383838"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<circle
|
|
||||||
cx="12.677"
|
|
||||||
cy="12.677"
|
|
||||||
r="12.677"
|
|
||||||
transform="translate(15.822 0)"
|
|
||||||
fill="#383838"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M36.27,83.67"
|
|
||||||
transform="translate(-23.569 -43.588)"
|
|
||||||
fill="#ff0"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
|
|
||||||
name: "Splash",
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
#splash {
|
|
||||||
width: 350px;
|
|
||||||
height: 500px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #EBEBEB;
|
|
||||||
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
|
||||||
// border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
@ -1,37 +1,46 @@
|
||||||
import { ref, watch, onUnmounted, onMounted } from "vue";
|
|
||||||
import useKeyDownHandler from "@/modules/keyDownHandler/useKeyDownHandler";
|
|
||||||
import useMediaStream from "@/modules/mediaStream";
|
|
||||||
import useAudioVisualizer from './audioDetails/audioVisualizer';
|
|
||||||
import useTextVisualizer from './textDetails/textVisualizer';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Controls which input to visualize
|
* Controls which input to visualize
|
||||||
* Will visualize text input by default as user types or
|
* Will visualize text input by default as user types or
|
||||||
* if user holds space bar down, input audio stream
|
* if user holds space bar down, input audio stream
|
||||||
* will be visualized & recorded
|
* will be visualized & recorded
|
||||||
*/
|
*/
|
||||||
// NOTE: input params should probably be input and MediaStream
|
|
||||||
// TODO: visualize audio stream from nodeJS background process
|
import { ref, watch, onUnmounted, onMounted } from "vue";
|
||||||
|
import useKeyDownHandler from "@/modules/keyDownHandler/useKeyDownHandler";
|
||||||
|
import useMediaStream from "@/modules/mediaStream";
|
||||||
|
import useAudioVisualizer from './audioDetails/audioVisualizer';
|
||||||
|
import useTextVisualizer from './textDetails/textVisualizer';
|
||||||
|
import useMitt from "@/modules/mitt";
|
||||||
|
import { useIpc } from '@/modules/ipc';
|
||||||
|
|
||||||
|
let stream: MediaStream;
|
||||||
|
|
||||||
export default function useInputController() {
|
export default function useInputController() {
|
||||||
const visualizeStream = ref(false);
|
const visualizeStream = ref(false);
|
||||||
const paths = ref([]);
|
const paths = ref([]);
|
||||||
const audioBubbleWidth = ref(10);
|
const audioBubbleWidth = ref(10);
|
||||||
let stream: MediaStream;
|
const { emitter } = useMitt();
|
||||||
|
const { post } = useIpc('update-recorder');
|
||||||
|
|
||||||
|
// send message on ENTER
|
||||||
|
const enterCallback = (input: string) => {
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
audio: 0,
|
||||||
|
text: input
|
||||||
|
};
|
||||||
|
emitter.emit("user-message", message);
|
||||||
|
}
|
||||||
|
|
||||||
const { keyDownHandler, input } = useKeyDownHandler();
|
|
||||||
const { textInputXoffset, renderTextInput } = useTextVisualizer(input);
|
|
||||||
const { visualizeStreamAsPaths, expandBubble } = useAudioVisualizer(visualizeStream);
|
const { visualizeStreamAsPaths, expandBubble } = useAudioVisualizer(visualizeStream);
|
||||||
|
const { keyDownHandler, input } = useKeyDownHandler(enterCallback);
|
||||||
|
const { textInputXoffset, renderTextInput } = useTextVisualizer(input);
|
||||||
|
|
||||||
// stops stream recording on space key up
|
// stops stream recording on space key up
|
||||||
function keyupHandler(e: any) {
|
function keyupHandler(e: any) {
|
||||||
if (e.key === " " && visualizeStream.value) {
|
if (e.key === " " && visualizeStream.value) {
|
||||||
console.log("stop recording");
|
|
||||||
|
|
||||||
window.postMessage({
|
|
||||||
myTypeField: 'update-recorder',
|
|
||||||
record: false
|
|
||||||
}, '*')
|
|
||||||
|
|
||||||
|
post(false);
|
||||||
visualizeStream.value = false;
|
visualizeStream.value = false;
|
||||||
paths.value = []
|
paths.value = []
|
||||||
audioBubbleWidth.value = 10;
|
audioBubbleWidth.value = 10;
|
||||||
|
|
@ -39,15 +48,18 @@ export default function useInputController() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add window event keyboard listeners
|
|
||||||
window.addEventListener("keydown", keyDownHandler);
|
|
||||||
window.addEventListener('keyup', keyupHandler);
|
|
||||||
|
|
||||||
// get stream & initialize recorder object on mount
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
stream = await useMediaStream({ audio: true });
|
stream = await useMediaStream({ audio: true });
|
||||||
|
// add window event keyboard listeners
|
||||||
|
window.addEventListener("keydown", keyDownHandler);
|
||||||
|
window.addEventListener('keyup', keyupHandler);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("keydown", keyDownHandler);
|
||||||
|
window.removeEventListener("keyup", keyupHandler);
|
||||||
|
});
|
||||||
|
|
||||||
// determine whether to render text or audio based one the keyboard input
|
// determine whether to render text or audio based one the keyboard input
|
||||||
watch(input, (input, prevInput) => {
|
watch(input, (input, prevInput) => {
|
||||||
if (prevInput !== "" || prevInput.length > 0) {
|
if (prevInput !== "" || prevInput.length > 0) {
|
||||||
|
|
@ -57,14 +69,8 @@ export default function useInputController() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (input === " " && visualizeStream.value === false) {
|
if (input === " " && visualizeStream.value === false) {
|
||||||
console.log("record MediaStream");
|
|
||||||
visualizeStream.value = true;
|
visualizeStream.value = true;
|
||||||
|
post(true);
|
||||||
window.postMessage({
|
|
||||||
myTypeField: 'update-recorder',
|
|
||||||
record: true
|
|
||||||
}, '*')
|
|
||||||
|
|
||||||
expandBubble(audioBubbleWidth);
|
expandBubble(audioBubbleWidth);
|
||||||
visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
|
visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
|
||||||
return;
|
return;
|
||||||
|
|
@ -72,11 +78,6 @@ export default function useInputController() {
|
||||||
renderTextInput();
|
renderTextInput();
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove Event Listeners on component unMount
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener("keydown", keyDownHandler);
|
|
||||||
window.removeEventListener("keyup", keyupHandler);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
input,
|
input,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { ref, computed, inject } from 'vue';
|
|
||||||
import useTextInputAnims from "./textAnime";
|
|
||||||
import { Ref } from "@/types/vueRef/index";
|
|
||||||
/*
|
/*
|
||||||
* text rendering logic used by inputController
|
* text rendering logic used by inputController
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
|
import useTextInputAnims from "./textAnime";
|
||||||
|
import { Ref } from "@/types/vueRef/index";
|
||||||
|
import useMitt from "@/modules/mitt";
|
||||||
|
|
||||||
export default function useTextVisualizer(input: Ref<string>) {
|
export default function useTextVisualizer(input: Ref<string>) {
|
||||||
const inputHeight = ref(42);
|
const inputHeight = ref(42);
|
||||||
const inputWidth = ref(0);
|
const inputWidth = ref(0);
|
||||||
|
const { emitter } = useMitt();
|
||||||
const emitter: any = inject("mitt");
|
|
||||||
const { animateSend, verticalShiftInput, inputAppear } = useTextInputAnims();
|
const { animateSend, verticalShiftInput, inputAppear } = useTextInputAnims();
|
||||||
|
|
||||||
async function renderTextInput() {
|
async function renderTextInput() {
|
||||||
|
|
@ -32,10 +34,16 @@ export default function useTextVisualizer(input: Ref<string>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.on("newSelfMessage", async (payload: any) => {
|
onMounted(() => {
|
||||||
const inputEl = document.getElementsByClassName("inputContainer");
|
emitter.on("user-message", () => {
|
||||||
const foreignEl = inputEl[0] as HTMLElement;
|
const inputEl = document.getElementsByClassName("inputContainer");
|
||||||
animateSend(foreignEl, inputHeight.value, input);
|
const foreignEl = inputEl[0] as HTMLElement;
|
||||||
|
animateSend(foreignEl, inputHeight.value, input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
emitter.all.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
const textInputXoffset = computed(() => {
|
const textInputXoffset = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export default function useComputedBubble(m: string, id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const startingOffset = computed(() => {
|
const startingOffset = computed(() => {
|
||||||
const parentView = document.getElementById('messageVisualizer');
|
const parentView = document.getElementById('messageView');
|
||||||
if (parentView) {
|
if (parentView) {
|
||||||
const rect = parentView.getBoundingClientRect();
|
const rect = parentView.getBoundingClientRect();
|
||||||
let Y = 600;
|
let Y = 600;
|
||||||
|
|
@ -29,13 +29,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import useComputedBubble from "./bubbleDetails/computed";
|
import useComputedBubble from "./bubbleDetails/computed";
|
||||||
import useBubbleDrawer from "./bubbleDetails/drawer";
|
import useBubbleDrawer from "./bubbleDetails/drawer";
|
||||||
|
|
||||||
// text wrap composition
|
|
||||||
import useTextWrap from "@/modules/textWrap";
|
import useTextWrap from "@/modules/textWrap";
|
||||||
|
|
||||||
import { defineComponent, onMounted, ref, reactive } from "vue";
|
import { defineComponent, onMounted, ref, reactive } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "MessageItem",
|
name: "TextBubble",
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -2,12 +2,12 @@ import { reactive, toRefs } from 'vue';
|
||||||
import { useIpc } from './ipc';
|
import { useIpc } from './ipc';
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
accessToken?: string | null;
|
accessToken: string | null;
|
||||||
error?: Error;
|
error?: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = reactive<AuthState>({
|
const state = reactive<AuthState>({
|
||||||
accessToken: undefined,
|
accessToken: null,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ const AUTH_KEY = 'crimata_token';
|
||||||
const token = window.localStorage.getItem(AUTH_KEY);
|
const token = window.localStorage.getItem(AUTH_KEY);
|
||||||
|
|
||||||
const authToken = async () => {
|
const authToken = async () => {
|
||||||
const { data, invoke } = useIpc('auth-user');
|
const { data, invoke } = useIpc('auth-session');
|
||||||
try {
|
try {
|
||||||
await invoke(token);
|
await invoke(token);
|
||||||
state.accessToken = data.value;
|
state.accessToken = data.value;
|
||||||
|
|
@ -28,29 +28,26 @@ const authToken = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticate on socket connect
|
// authenticate on auth event
|
||||||
window.ipcRenderer.on("fetch-token", async (_event, _arg) => {
|
window.ipcRenderer.on("auth", async (_event, _arg) => {
|
||||||
authToken();
|
await authToken();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
authToken();
|
state.accessToken = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const setToken = (token: string, remember: boolean) => {
|
|
||||||
if (remember) {
|
|
||||||
// Save
|
|
||||||
window.localStorage.setItem(AUTH_KEY, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const setToken = (token: string) => {
|
||||||
|
window.localStorage.setItem(AUTH_KEY, token);
|
||||||
state.accessToken = token;
|
state.accessToken = token;
|
||||||
state.error = undefined;
|
state.error = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = (): Promise<void> => {
|
const logout = (): Promise<null> => {
|
||||||
window.localStorage.removeItem(AUTH_KEY);
|
window.localStorage.removeItem(AUTH_KEY);
|
||||||
return Promise.resolve(state.accessToken = undefined);
|
return Promise.resolve(state.accessToken = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ import { ref, computed } from 'vue'
|
||||||
|
|
||||||
export const useIpc = (endpoint: string) => {
|
export const useIpc = (endpoint: string) => {
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const loading = ref(false);
|
|
||||||
const error = ref();
|
const error = ref();
|
||||||
const listener = ref(false);
|
|
||||||
|
|
||||||
const errorMessage = computed(() => {
|
const errorMessage = computed(() => {
|
||||||
console.log('ERROR', error.value);
|
console.log('ERROR', error.value);
|
||||||
|
|
@ -13,54 +11,29 @@ export const useIpc = (endpoint: string) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const invoke = (payload: any) => {
|
const invoke = async (payload: any) => {
|
||||||
loading.value = true;
|
|
||||||
error.value = undefined;
|
error.value = undefined;
|
||||||
|
try {
|
||||||
return window.ipcRenderer.invoke(endpoint, payload).then(res => {
|
const res = await window.ipcRenderer.invoke(endpoint, payload);
|
||||||
data.value = res;
|
return data.value = res;
|
||||||
}).catch(e => {
|
} catch (e) {
|
||||||
error.value = e;
|
error.value = e;
|
||||||
throw e;
|
throw e;
|
||||||
}).finally(() => {
|
}
|
||||||
loading.value = false;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const send = (payload?: Record<string, any>, callback?: (arg: any) => void) => {
|
const post = (payload: any) => {
|
||||||
loading.value = true;
|
|
||||||
error.value = undefined;
|
|
||||||
|
|
||||||
window.ipcRenderer.on(endpoint + '-reply', (event: any, arg: any | Error) => {
|
|
||||||
if (arg instanceof Error) {
|
|
||||||
error.value = arg;
|
|
||||||
throw arg;
|
|
||||||
} else {
|
|
||||||
data.value = arg;
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.ipcRenderer.send(endpoint, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEvent = (callback: (event: any, arg: any) => void) => {
|
|
||||||
listener.value = true;
|
|
||||||
window.ipcRenderer.on(endpoint, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
const post = (payload?: Record<string, any>) => {
|
|
||||||
window.postMessage({
|
window.postMessage({
|
||||||
myTypeField: endpoint,
|
endpoint: endpoint,
|
||||||
message: payload
|
content: payload
|
||||||
}, '*')
|
}, '*')
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
|
||||||
data,
|
data,
|
||||||
error,
|
error,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
send,
|
invoke,
|
||||||
invoke
|
post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,18 @@
|
||||||
import { ref, inject } from "vue";
|
import { ref } from "vue";
|
||||||
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
|
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
|
||||||
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
|
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
|
||||||
// This catches keys from a physical keyboard, a soft keyboard on a tablet, or a
|
|
||||||
// scanner attached via USB
|
interface KeyDownEvent {
|
||||||
export default function useKeyDownHandler() {
|
|
||||||
interface KeyDownEvent {
|
|
||||||
keyCode: number;
|
keyCode: number;
|
||||||
shiftKey: boolean;
|
shiftKey: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This catches keys from a physical keyboard, a soft keyboard on a tablet, or a
|
||||||
|
// scanner attached via USB
|
||||||
|
export default function useKeyDownHandler(enterCallback: (input: string) => void | null) {
|
||||||
|
|
||||||
const emitter: any = inject("mitt");
|
|
||||||
const input = ref("");
|
const input = ref("");
|
||||||
|
|
||||||
// submits newSelfMessage event
|
|
||||||
function handleEnterKey() {
|
|
||||||
// does nothing if input value is empty
|
|
||||||
if (input.value === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = {
|
|
||||||
text: input.value,
|
|
||||||
audio: 0
|
|
||||||
};
|
|
||||||
// fire event with message payload
|
|
||||||
emitter.emit("newSelfMessage", message);
|
|
||||||
|
|
||||||
window.postMessage({
|
|
||||||
myTypeField: 'send-message',
|
|
||||||
message: message
|
|
||||||
}, '*')
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyDownHandler(e: KeyDownEvent) {
|
function keyDownHandler(e: KeyDownEvent) {
|
||||||
// keyboardCharMap is an array of arrays, with each inner
|
// keyboardCharMap is an array of arrays, with each inner
|
||||||
// array having 2 columns - un-shifted, and shifted values.
|
// array having 2 columns - un-shifted, and shifted values.
|
||||||
|
|
@ -49,7 +31,7 @@ export default function useKeyDownHandler() {
|
||||||
const cmd = keyboardNameMap[e.keyCode];
|
const cmd = keyboardNameMap[e.keyCode];
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "ENTER":
|
case "ENTER":
|
||||||
handleEnterKey();
|
enterCallback(input.value);
|
||||||
break;
|
break;
|
||||||
case "BACK_SPACE":
|
case "BACK_SPACE":
|
||||||
input.value = input.value.slice(0, -1);
|
input.value = input.value.slice(0, -1);
|
||||||
|
|
@ -66,4 +48,4 @@ export default function useKeyDownHandler() {
|
||||||
keyDownHandler,
|
keyDownHandler,
|
||||||
input
|
input
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/modules/mitt.ts
Normal file
17
src/modules/mitt.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { inject } from "vue";
|
||||||
|
import { Mitt } from "@/types/mitt/index";
|
||||||
|
|
||||||
|
let emitter: Mitt;
|
||||||
|
|
||||||
|
export default function useMitt() {
|
||||||
|
|
||||||
|
const emitterInject: Mitt | undefined = inject("mitt");
|
||||||
|
if (emitterInject) {
|
||||||
|
emitter = emitterInject;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
emitter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
78
src/modules/queue.ts
Normal file
78
src/modules/queue.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
export interface DataNode<T> {
|
||||||
|
data: T;
|
||||||
|
next: DataNode<T> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueueI<T> {
|
||||||
|
enqueue(data: T): void;
|
||||||
|
dequeue(): T | null;
|
||||||
|
peek(): T | null;
|
||||||
|
size(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Queue<T> implements QueueI<T> {
|
||||||
|
private _size: number;
|
||||||
|
private head: DataNode<T> | null;
|
||||||
|
private tail: DataNode<T> | null;
|
||||||
|
constructor() {
|
||||||
|
this._size = 0;
|
||||||
|
this.head = this.tail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueue(data: T) {
|
||||||
|
|
||||||
|
const node: DataNode<T> = {
|
||||||
|
data,
|
||||||
|
next: null
|
||||||
|
}
|
||||||
|
if (this.head === null) {
|
||||||
|
this.head = this.tail = node;
|
||||||
|
} else {
|
||||||
|
if (this.tail) {
|
||||||
|
this.tail.next = node;
|
||||||
|
this.tail = this.tail.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
++this._size;
|
||||||
|
}
|
||||||
|
|
||||||
|
dequeue(): T | null {
|
||||||
|
|
||||||
|
let item: T | null = null;
|
||||||
|
|
||||||
|
if (this._size === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.head) {
|
||||||
|
item = this.head.data;
|
||||||
|
this.head = this.head.next;
|
||||||
|
--this._size;
|
||||||
|
|
||||||
|
if (!this._size) {
|
||||||
|
this.tail = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
peek(): T | null {
|
||||||
|
|
||||||
|
let item: T | null = null;
|
||||||
|
|
||||||
|
if (this._size === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.head) {
|
||||||
|
item = this.head.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
size(): number {
|
||||||
|
return this._size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handles visualization of view messages through
|
* handles visualization of view messages through
|
||||||
* vue transition callbacks and anime-js animations
|
* vue transition callbacks and anime-js animations
|
||||||
*/
|
*/
|
||||||
export default function useScrollView({ targetId }: { targetId: string }) {
|
export default function useScrollView({ targetId }: { targetId: string }) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
interface SocketState {
|
|
||||||
connection: WebSocket | null;
|
|
||||||
auth: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create WebSocket connection.
|
|
||||||
const socket = new WebSocket('ws://localhost:8080');
|
|
||||||
|
|
||||||
// Connection opened
|
|
||||||
socket.onopen = function (event) {
|
|
||||||
socket.send('Hello Server!');
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onmessage = function(event) {
|
|
||||||
console.debug("WebSocket message received:", event);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -17,22 +17,14 @@ process.once("loaded", () => {
|
||||||
// do something with custom event
|
// do something with custom event
|
||||||
const message = event.data;
|
const message = event.data;
|
||||||
|
|
||||||
if (message.myTypeField === "send-message") {
|
if (message.endpoint === "update-menu-bar") {
|
||||||
ipcRenderer.send("send-message", message.message);
|
ipcRenderer.send("update-menu-bar", message.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.myTypeField === "update-menu-bar") {
|
if (message.endpoint === "update-recorder") {
|
||||||
ipcRenderer.send("update-menu-bar", message);
|
ipcRenderer.send("update-recorder", message.content);
|
||||||
}
|
|
||||||
|
|
||||||
if (message.myTypeField === "update-recorder") {
|
|
||||||
ipcRenderer.send("update-recorder", message.record);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.myTypeField === "submit-creds") {
|
|
||||||
ipcRenderer.send("submit-creds", message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
RouteRecordRaw
|
RouteRecordRaw
|
||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
import Home from "@/views/home.vue";
|
import Home from "@/views/home.vue";
|
||||||
|
import Splash from "@/views/splash.vue";
|
||||||
import { useAuth } from '@/modules/auth';
|
import { useAuth } from '@/modules/auth';
|
||||||
|
|
||||||
// Define the routes (/*) for the app here.
|
// Define the routes (/*) for the app here.
|
||||||
|
|
@ -15,6 +16,14 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
component: Home,
|
component: Home,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/splash",
|
||||||
|
name: "splash",
|
||||||
|
component: Splash,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
name: "login",
|
name: "login",
|
||||||
|
|
@ -34,14 +43,19 @@ const router = createRouter({
|
||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// route auth check
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const { accessToken } = useAuth();
|
const { accessToken } = useAuth();
|
||||||
|
|
||||||
// Not logged into a guarded route?
|
// Not logged into a guarded route?
|
||||||
if (to.meta.requiresAuth && !accessToken?.value) next({ name: 'login' });
|
if (to.meta.requiresAuth && !accessToken.value) {
|
||||||
|
next({ name: 'login' })
|
||||||
|
}
|
||||||
|
|
||||||
// Logged in for an auth route
|
// Logged in for an auth route
|
||||||
else if ((to.name == 'login' || to.name == 'register') && accessToken!.value) next({ name: 'home' });
|
else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
|
||||||
|
next({ name: 'home' });
|
||||||
|
}
|
||||||
|
|
||||||
// Carry On...
|
// Carry On...
|
||||||
else next();
|
else next();
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<HomeComponent />
|
<svg
|
||||||
|
id="messenger"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
>
|
||||||
|
<MessageView />
|
||||||
|
<TextAudioInput />
|
||||||
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import MessageView from "@/components/messageView/index.vue";
|
||||||
|
import TextAudioInput from "@/components/taInput/index.vue";
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import HomeComponent from "@/components/home/index.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
name: "Messenger",
|
||||||
name: "Home",
|
components: { MessageView, TextAudioInput },
|
||||||
components: { HomeComponent },
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
#messenger {
|
||||||
|
width: 350px;
|
||||||
|
height: 500px;
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
||||||
|
// border-radius: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,9 @@
|
||||||
<label for="checkbox">Remember Me</label>
|
<label for="checkbox">Remember Me</label>
|
||||||
|
|
||||||
<!-- submit button; position: fixed -->
|
<!-- submit button; position: fixed -->
|
||||||
<button class="button" @click="submit">Submit</button>
|
<button class="button" @click.prevent="submit">Submit</button>
|
||||||
|
|
||||||
<!-- back to login button: position: fixed -->
|
<!-- back to login button: position: fixed -->
|
||||||
<button class="button2" @click="switchView">Create an account</button>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -45,9 +44,9 @@ export default defineComponent({
|
||||||
|
|
||||||
const email = ref("");
|
const email = ref("");
|
||||||
const password = ref("");
|
const password = ref("");
|
||||||
const rememberMe = ref(false);
|
const rememberMe = ref(true);
|
||||||
|
|
||||||
const { data, invoke } = useIpc('auth-user');
|
const { data, invoke } = useIpc('auth-session');
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
@ -57,8 +56,7 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await invoke(payload);
|
await invoke(payload);
|
||||||
console.log('testing incoke', data.value)
|
setToken(data.value);
|
||||||
setToken(data.value, rememberMe.value);
|
|
||||||
router.push({ name: "home" });
|
router.push({ name: "home" });
|
||||||
} catch(e){
|
} catch(e){
|
||||||
console.log('Error loging in.')
|
console.log('Error loging in.')
|
||||||
|
|
|
||||||
|
|
@ -6,42 +6,40 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- username and password forms -->
|
<!-- username and password forms -->
|
||||||
<div id="registerBox">
|
<form id="registerBox" @submit.prevent="submit">
|
||||||
<!-- username -->
|
<!-- username -->
|
||||||
<input class="input" type="text" v-model="email" placeholder="email" />
|
<input class="input" type="text" v-model="email" placeholder="email" required/>
|
||||||
|
|
||||||
<!-- password -->
|
<!-- password -->
|
||||||
<input class="input" type="password" v-model="password" placeholder="Password" />
|
<input class="input" type="password" v-model="password" placeholder="Password" required/>
|
||||||
|
|
||||||
<!-- firstName -->
|
<!-- re-enter passoword -->
|
||||||
<input class="input" type="text" v-model="password2" placeholder="Re-enter password" />
|
<input class="input" type="password" v-model="password2" placeholder="Re-enter password" required/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- submit button; position: fixed -->
|
<!-- submit button; position: fixed -->
|
||||||
<button class="button" @click="submit">Submit</button>
|
<button class="button" type="submit">Submit</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
<!-- back to login button: position: fixed -->
|
<!-- back to login button: position: fixed -->
|
||||||
<button class="button2" @click="switchView">Back to login</button>
|
<button class="button2" @click.prevent="switchView">Back to login</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
computed,
|
|
||||||
defineComponent,
|
defineComponent,
|
||||||
onErrorCaptured,
|
|
||||||
reactive,
|
|
||||||
ref,
|
ref,
|
||||||
toRefs,
|
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useIpc } from "@/modules/ipc";
|
import { useIpc } from "@/modules/ipc";
|
||||||
import { useAuth } from "@/modules/auth";
|
import { useAuth } from "@/modules/auth";
|
||||||
|
|
||||||
interface RegisterPayload {
|
interface RegisterPayload {
|
||||||
request?: string;
|
request: string;
|
||||||
email?: string;
|
email: string;
|
||||||
password?: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
@ -49,25 +47,46 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const email = ref("");
|
const email = ref("");
|
||||||
const password = ref("");
|
const password = ref("");
|
||||||
const rememberMe = ref("");
|
const password2 = ref("");
|
||||||
|
|
||||||
const { invoke, data,} = useIpc("auth-user");
|
const { invoke, data,} = useIpc("auth-session");
|
||||||
const { setToken } = useAuth();
|
const { setToken } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const payload: RegisterPayload = {
|
const re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
|
||||||
request: "register",
|
|
||||||
email: email.value,
|
const passwordMatch = () => {
|
||||||
password: password.value,
|
let match = false;
|
||||||
};
|
if (password.value == password2.value) {
|
||||||
|
match = true;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
let valid = false;
|
||||||
|
if (re.test(email.value) && passwordMatch()) {
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
|
||||||
await invoke(payload);
|
const payload: RegisterPayload = {
|
||||||
setToken(data.value, true);
|
request: "register",
|
||||||
router.push({ name: "home" });
|
email: email.value,
|
||||||
} catch(e) {
|
password: password.value,
|
||||||
console.log('Failed to register.')
|
};
|
||||||
|
|
||||||
|
if (validate()) {
|
||||||
|
try {
|
||||||
|
await invoke(payload);
|
||||||
|
setToken(data.value);
|
||||||
|
router.push({ name: "home" });
|
||||||
|
} catch(e) {
|
||||||
|
console.log('Failed to register.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -78,6 +97,7 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
password2,
|
||||||
switchView,
|
switchView,
|
||||||
submit,
|
submit,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,72 @@
|
||||||
<template>
|
<template>
|
||||||
<SplashComponent />
|
<div id="splash">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="57.046"
|
||||||
|
height="52.759"
|
||||||
|
viewBox="0 0 57.046 52.759"
|
||||||
|
>
|
||||||
|
<g transform="translate(-4127 -507)">
|
||||||
|
<g transform="translate(3949 332.206)">
|
||||||
|
<g transform="translate(178 174.794)">
|
||||||
|
<g transform="translate(0 27.405)">
|
||||||
|
<circle
|
||||||
|
cx="12.677"
|
||||||
|
cy="12.677"
|
||||||
|
r="12.677"
|
||||||
|
transform="translate(0 0)"
|
||||||
|
fill="#383838"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="12.677"
|
||||||
|
cy="12.677"
|
||||||
|
r="12.677"
|
||||||
|
transform="translate(31.692 0)"
|
||||||
|
fill="#383838"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<circle
|
||||||
|
cx="12.677"
|
||||||
|
cy="12.677"
|
||||||
|
r="12.677"
|
||||||
|
transform="translate(15.822 0)"
|
||||||
|
fill="#383838"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M36.27,83.67"
|
||||||
|
transform="translate(-23.569 -43.588)"
|
||||||
|
fill="#ff0"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import SplashComponent from "@/components/splash/index.vue"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
||||||
name: "Splash",
|
name: "Splash",
|
||||||
components: { SplashComponent },
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
#splash {
|
||||||
|
width: 350px;
|
||||||
|
height: 500px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #EBEBEB;
|
||||||
|
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
||||||
|
// border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
160
tests/mockMessages.ts
Normal file
160
tests/mockMessages.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import { Message } from "@/types/message/index";
|
||||||
|
|
||||||
|
export const Messages: Message[] = [
|
||||||
|
{
|
||||||
|
content: "Good morning Dave",
|
||||||
|
context: "launch",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "2",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content:
|
||||||
|
"add contact",
|
||||||
|
context: "addcontact",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "1",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "I'm looking for name, crimata_idto addcontact. Feel free toenter only a subset of thisinformation, but then let meknow when you are done",
|
||||||
|
context: "addcontact",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "5",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "andrew gundersen",
|
||||||
|
context: "addcontact",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "6",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
content: "gundersena@xavier.edu",
|
||||||
|
context: "addcontact",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "3",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "I already have a contact forandrew gundersen. Are you sureyou want this?",
|
||||||
|
context: "addcontact",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "7",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
content: "Yes posting it online soon.",
|
||||||
|
context: "You to Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "4",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Hey do you know Enrique Hernandez?",
|
||||||
|
context: "Message from Crimata",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "8",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Hey Anna, did you get the paper done?",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "9",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Hey Anna, did you get the paper done?",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "10",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Yes posting it online soon.",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "11",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content:
|
||||||
|
"I can be there at 5 pm tomorrow to greet you at a Chick file with my almond and chive salad.",
|
||||||
|
context: "You to Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "12",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Hey Anna, did you get the paper done?",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "13",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Hey Anna, did you get the paper done?",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "14",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Yes posting it online soon.",
|
||||||
|
context: "You to Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "15",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Sweet, thanks!",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "16",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Hey do you know Enrique Hernandez?",
|
||||||
|
context: "Message from Crimata AI",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "17",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Yes posting it online soon.",
|
||||||
|
context: "You to Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "sf",
|
||||||
|
id: "18",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Sweet, thanks!",
|
||||||
|
context: "Message from Tommy McConville",
|
||||||
|
subContext: "",
|
||||||
|
modifier: "ai",
|
||||||
|
id: "19",
|
||||||
|
time: ""
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -8,10 +8,10 @@ wss.on("connection", function connection(ws, req) {
|
||||||
ws.on("message", function incoming(message) {
|
ws.on("message", function incoming(message) {
|
||||||
|
|
||||||
console.log(message)
|
console.log(message)
|
||||||
if (message === 'no-token' || message === 'undefined') {
|
if (message === 'no-token' || message === 'undefined') {
|
||||||
console.log('no token to authenticate');
|
console.log('no token to authenticate');
|
||||||
ws.send('locked');
|
ws.send('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue