Merge remote-tracking branch 'origin/main' into main

This commit is contained in:
Enrique Hernandez 2021-02-17 13:25:37 -06:00
commit 69a9dacb90
34 changed files with 874 additions and 799 deletions

View file

@ -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
} }
} }

View file

@ -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);
// });
} }

View file

@ -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();

View file

@ -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();
}); });
}); });

View file

@ -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>

View file

@ -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>

View file

@ -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
};
}

View file

@ -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
};
}

View file

@ -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>

View 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>

View 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,
};
}

View file

@ -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;

View 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
};
}

View file

@ -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>

View file

@ -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,

View file

@ -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(() => {

View file

@ -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;

View file

@ -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,

View file

@ -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 {

View file

@ -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
} }
} }

View file

@ -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
View 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
View 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;
}
}

View file

@ -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 }) {

View file

@ -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);
}

View file

@ -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)
} }
}); });
}); });

View file

@ -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();

View file

@ -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>

View file

@ -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.')

View file

@ -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,
}; };

View file

@ -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
View 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: ""
},
];

View file

@ -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');
} }
}); });