]> Repos - mime-chat/commitdiff
beginnings of refactor
authorAndrew Gundersen <gundersena@xavier.edu>
Wed, 9 Jun 2021 02:21:11 +0000 (21:21 -0500)
committerAndrew Gundersen <gundersena@xavier.edu>
Wed, 9 Jun 2021 02:21:11 +0000 (21:21 -0500)
63 files changed:
src/App.vue [deleted file]
src/api/account.ts
src/assets/crimata.svg [deleted file]
src/audio.ts [new file with mode: 0644]
src/background.ts [deleted file]
src/background/audio.ts [deleted file]
src/background/helpers.ts [deleted file]
src/background/init.ts [deleted file]
src/background/ipc/audio.ts [deleted file]
src/background/ipc/session.ts [deleted file]
src/background/session.ts [deleted file]
src/background/websockets.ts [deleted file]
src/components/controllers/audioCtrl.ts [deleted file]
src/components/keyBoardMaps/keyboardCharMap.ts [deleted file]
src/components/keyBoardMaps/keyboardNameMap.ts [deleted file]
src/components/message.vue [deleted file]
src/components/messenger.vue [deleted file]
src/components/textInput.vue [deleted file]
src/composables/audio.ts [new file with mode: 0644]
src/composables/autoUpdate.ts [new file with mode: 0644]
src/composables/emitter.ts [moved from src/modules/emitter.ts with 51% similarity]
src/composables/http.ts [moved from src/modules/http.ts with 100% similarity]
src/composables/json.ts [new file with mode: 0644]
src/composables/store.ts [moved from src/background/store.ts with 100% similarity]
src/composables/websockets.ts [new file with mode: 0644]
src/init.ts [new file with mode: 0644]
src/ipc/account.ts [moved from src/background/ipc/account.ts with 84% similarity]
src/ipc/audio.ts [new file with mode: 0644]
src/ipc/index.ts [moved from src/background/ipc/index.ts with 74% similarity]
src/ipc/session.ts [new file with mode: 0644]
src/ipcRend/account.ts [deleted file]
src/ipcRend/audio.ts [deleted file]
src/ipcRend/session.ts [deleted file]
src/main.ts
src/modules/message.ts [deleted file]
src/modules/messages.ts [deleted file]
src/modules/mitt.ts [deleted file]
src/modules/scroll.ts [deleted file]
src/render/App.vue [new file with mode: 0644]
src/render/components/bubble.vue [new file with mode: 0644]
src/render/components/controllers/bubble.control.ts [new file with mode: 0644]
src/render/components/controllers/helpers.ts [new file with mode: 0644]
src/render/components/controllers/inputItem.control.audio.ts [new file with mode: 0644]
src/render/components/controllers/inputItem.control.text.ts [moved from src/components/controllers/textCtrl.ts with 53% similarity]
src/render/components/controllers/messenger.control.ts [new file with mode: 0644]
src/render/components/header.vue [new file with mode: 0644]
src/render/components/inputItem.vue [moved from src/components/inputItem.vue with 87% similarity]
src/render/components/login.vue [moved from src/components/login.vue with 89% similarity]
src/render/components/messenger.vue [new file with mode: 0644]
src/render/components/settings.vue [moved from src/components/settings.vue with 100% similarity]
src/render/components/splash.vue [moved from src/components/splash.vue with 100% similarity]
src/render/composables/auth.ts [moved from src/modules/auth.ts with 90% similarity]
src/render/composables/draggify.ts [moved from src/modules/draggify.ts with 100% similarity]
src/render/composables/ipc.ts [moved from src/modules/ipc.ts with 91% similarity]
src/render/composables/scroll.ts [new file with mode: 0644]
src/render/ipc.ts [new file with mode: 0644]
src/render/preload.ts [moved from src/preload.ts with 100% similarity]
src/render/shims-vue.d.ts [moved from src/shims-vue.d.ts with 100% similarity]
src/session.ts [new file with mode: 0644]
src/state.ts [new file with mode: 0644]
src/types.ts
src/window.ts [moved from src/background/window.ts with 86% similarity]
tsconfig.json

diff --git a/src/App.vue b/src/App.vue
deleted file mode 100644 (file)
index 52e44e6..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-<template>
-  <!-- Only render when profile has been set -->
-  <div id="app" v-if="(ready)">
-
-    <button id="titlebar" />
-
-    <!-- Minimize and exit buttons. -->
-    <span class="menu">
-      <button
-        class="menuButton exitButton"
-        @click.prevent="post('nav-bar', 'close')"
-      />
-      <button
-        class="menuButton minimizeButton"
-        @click.prevent="post('nav-bar', 'min')"
-      />
-    </span>
-
-    <!-- Main Pages -->
-    <Messenger
-      v-if="profile"
-      :profile="profile"
-      :newMessages="newMessages"
-    />
-    <Login v-else />
-
-  </div>
-
-  <!-- Show spash screen if ready is false -->
-  <Splash v-else />
-
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, onUnmounted, ref } from "vue";
-import { IpcRendererEvent } from "electron";
-import { useIpc } from "@/modules/ipc";
-import { useProfile } from "@/modules/auth"
-import { invokeProfile } from "@/ipcRend/account";
-import { postInitSession, postMount } from "@/ipcRend/session";
-
-import Splash from "@/components/splash.vue";
-import Messenger from "@/components/messenger.vue";
-import Login from "@/components/login.vue";
-
-import { Profile } from "@/types";
-
-
-export default defineComponent({
-
-  components: {
-    Splash,
-    Messenger,
-    Login
-  },
-
-  setup() {
-
-    const { post, invoke } = useIpc();
-
-    const { profile, setProfile, clearProfile } = useProfile();
-
-    // Whether browser has received user info yet.
-    const ready = ref(false);
-
-    // New messages that browser missed while closed.
-    const newMessages = ref([]);
-
-    // Receive updated information about the session.
-    const updateState = (_event: IpcRendererEvent, payload: any) => {
-      console.log('YAYAYAYAYA');
-      console.log("APP:Received updated profile and new messages: \n" +
-                  `    profile: ${payload.message.profile}\n` +
-                  `    new: ${payload.message.newMessages}`);
-
-      if (payload.message.profile) {
-        console.log(`APP:Logged-in, showing Messenger View.`);
-      } else {
-        console.log(`APP:Logged-out, showing Login View.`);
-      }
-
-      newMessages.value = payload.message.newMessages;
-
-      ready.value = true;
-
-    }
-
-    const onSessionAuthFail = (_event: IpcRendererEvent, _payload: null) => {
-        clearProfile();
-    }
-
-    onMounted(async () => {
-      console.log("APP:mounted.");
-
-      window.ipcRenderer.on("session-auth-fail", onSessionAuthFail)
-      window.ipcRenderer.on("update-state", updateState)
-      window.ipcRenderer.on("update_available", () => {
-          console.log('testing auto update');
-      })
-
-      window.ipcRenderer.on("update_downloaded", () => {
-          console.log('testing update download');
-      })
-
-      postMount();
-
-      try {
-
-        const profile = await invokeProfile() as Profile;
-        setProfile(profile);
-      } catch(e) {
-          console.log('[AUTH]', e);
-          clearProfile();
-      } finally {
-          ready.value = true;
-          if (profile.value.crimataId) {
-              // start session
-              postInitSession(profile.value.crimataId);
-          }
-      }
-
-    });
-
-    onUnmounted(() => {
-      window.ipcRenderer.removeAllListeners("update-state");
-      window.ipcRenderer.removeAllListeners("session-auth-fail");
-    })
-
-    return {
-        ready,
-        post,
-        newMessages,
-        profile
-    }
-  }
-})
-
-</script>
-
-<style lang="scss">
-
-html, body {
-  margin: 0;
-  padding: 0;
-  // Background color set in window.ts
-}
-
-#app {
-  font-family: "SF Pro Text";
-
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-
-  height: 100vh;
-  width: 100vw;
-
-  border-radius: 15px;
- }
-
-#titlebar {
-  position: fixed;
-
-  width: 100vw;
-  height: 54px;
-
-  opacity: 0.75;
-
-  background-color: #EBEBEB;
-
-  -webkit-app-region: drag;
-
-  border: none;
-  outline: none;
-
-  z-index: 1;
-
-}
-
-.menu {
-  position: fixed;
-  margin-left: 20px;
-  margin-top: 20px;
-
-  z-index: 2;
-
-}
-
-.menuButton {
-  border: none;
-  outline:none;
-  min-width: 14px;
-  min-height: 14px;
-  border-radius: 7px;
-}
-
-.exitButton {
-  background-color: #FF6157;
-}
-
-.exitButton:active {
-  background: #c14645;
-}
-
-.minimizeButton {
-  background-color: #FFC12F;
-  margin-left: 8px;
-}
-
-.minimizeButton:active {
-  background-color: #c08e38;
-}
-
-</style>
index 1d728225cd18c70c657c674db6f1a71d5b447c67..de1335b8c21d1bb621a2246a678864fa0e3f1bb7 100644 (file)
@@ -1,27 +1,14 @@
 
-import { useHttp } from "@/modules/http";
+import { useHttp } from "@/composables/http";
 import axios from "axios";
 
 const { post } = useHttp();
 
-export const submit =
-    async (email: string, password: string) => (
-
-        await post('/account/login', {
-            email,
-            password
-        })
-
+export const submit = async (email: string, password: string) => (
+    await post('/account/login', { email, password })
 )
 
-
-export const logout =
-    async (): Promise<null | Error> => (await post('/account/logout'));
-
-
-
-export const fetchProfile = async(email: string, token: string) => (
-
+export const fetchAccount = async (email: string, token: string) => (
     await axios({
       url: "http://127.0.0.1:3000/api/account/profile",
       headers: {
diff --git a/src/assets/crimata.svg b/src/assets/crimata.svg
deleted file mode 100644 (file)
index 8bb28dc..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="13.795" height="12.759" viewBox="0 0 13.795 12.759">
-  <g id="Group_544" data-name="Group 544" transform="translate(-4127 -507)">
-    <g id="Group_33" data-name="Group 33" transform="translate(4127 507)">
-      <g id="Group_32" data-name="Group 32" transform="translate(0 0)">
-        <g id="Group_31" data-name="Group 31" transform="translate(0 6.627)">
-          <circle id="Ellipse_50" data-name="Ellipse 50" cx="3.066" cy="3.066" r="3.066" transform="translate(0 0)" fill="#383838"/>
-          <circle id="Ellipse_51" data-name="Ellipse 51" cx="3.066" cy="3.066" r="3.066" transform="translate(7.664 0)" fill="#383838"/>
-        </g>
-        <circle id="Ellipse_52" data-name="Ellipse 52" cx="3.066" cy="3.066" r="3.066" transform="translate(3.826 0)" fill="#383838"/>
-        <path id="Path_150" data-name="Path 150" d="M36.27,83.67" transform="translate(-33.199 -73.977)" fill="#ff0"/>
-      </g>
-    </g>
-  </g>
-</svg>
diff --git a/src/audio.ts b/src/audio.ts
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/background.ts b/src/background.ts
deleted file mode 100644 (file)
index b67e829..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Entry point for Crimata electron app.
- * "Look on my Works, ye Mighty, and despair!"
- */
-
-"use strict";
-
-import { initApp } from './background/init';
-import { protocol } from "electron";
-require('dotenv').config()
-
-// Scheme must be registered before the app is ready
-protocol.registerSchemesAsPrivileged([
-    { scheme: "app", privileges: { secure: true, standard: true } }
-]);
-
-// Load environment variable
-const isDev = require('electron-is-dev');
-
-// NOTE Program Begins Here
-(() => {
-
-    console.log('Starting Crimata electron app.');
-    initApp(isDev);
-
-})();
diff --git a/src/background/audio.ts b/src/background/audio.ts
deleted file mode 100644 (file)
index fa6efb2..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-/* eslint @typescript-eslint/no-var-requires: "off" */
-
-"use strict";
-
-import { backgroundMitt } from '@/modules/emitter';
-const portAudio = require('naudiodon');
-
-// Audio in and out stream objects.
-let ai: typeof portAudio.AudioIO | boolean = false;
-let ao: typeof portAudio.AudioIO | boolean = false;
-
-// Whether activly recording.
-let record = false;
-
-const audioContainer = {
-    input: '',
-}
-
-const audioOptions = {
-    channelCount: 1,
-    sampleFormat: 16,
-    sampleRate: 16000,
-    deviceId: -1,
-    closeOnError: false,
-}
-
-export const toggleRecord = (): void => { record = !record };
-
-
-export const fetchAudioInput = (): Promise<Error | string> => (
-
-    new Promise((resolve, reject) => {
-
-        try {
-            resolve(audioContainer.input);
-            toggleRecord();
-        } catch (e) {
-            reject(new Error('Failed to fetch the audio.'))
-        }
-
-    })
-)
-
-
-// Main audio function run by run.ts module.
-export function initAudioIO(): void {
-    console.log("AUDIO:Starting io streams.")
-
-    if (!ai) {
-
-        // Initialize and start input stream.
-        ai = new portAudio.AudioIO({ inOptions: audioOptions });
-        ai.setEncoding("hex");
-        ai.start();
-
-        // On each data chunk...
-        ai.on('data', (chunk: string) => {
-
-            // If recording, we capture the data.
-            if (record) {
-                console.log('AUDIO:Recording...')
-                audioContainer.input += chunk;
-            }
-
-            // Else, we don't capture and also clear audioContainer.
-            else {
-                if (audioContainer.input.length) {
-                    audioContainer.input = "";
-                }
-            }
-
-        });
-    }
-
-    if (!ao) {
-
-        // Initialize and start input stream.
-        ao = new portAudio.AudioIO({ outOptions: audioOptions });
-        ao.start();
-
-    }
-}
-
-
-// ---Audio playback--------------------------------------------
-
-// Split Buffer into an array of len-sized Buffers.
-function bufSplit(buf: Buffer, len: number): Array<Buffer> {
-    const chunks = [];
-    let i = 0;
-    let L = len;
-
-    while(i < buf.byteLength) {
-      chunks.push(buf.slice(i, L));
-      i = L;
-      L += len;
-    }
-
-    return chunks;
-}
-
-// Audio playback.
-export function play(input: string): void {
-
-    // Format the audio.
-    const audio = bufSplit(
-        Buffer.from(input as string, 'hex'),
-        8192
-    );
-
-    // Called on end of write.
-    const callback = () => {
-
-        // We stop audio playback anim.
-        backgroundMitt.emit('ipc-renderer', {
-            endpoint: 'stop-playback-anim'
-        });
-
-    }
-
-    write();
-
-    // Iterate through audio array and write buffers to portAudio writable.
-    function write() {
-        let chunk: Buffer;
-        let ok = true;
-        let i = 0;
-
-        do {
-            chunk = audio[i];
-            if (i === audio.length - 1) {
-                // write last chunk.
-                ao.write(chunk, null, callback);
-            } else {
-                // check for backpreassure.
-                ok = ao.write(chunk, null);
-            }
-            i++;
-        } while (i < audio.length && ok);
-
-        if (i < audio.length) {
-            // Had to stop early!
-            // Write some more once it drains.
-            ao.once('drain', write);
-        }
-    }
-}
-
-// -------------------------------------------------------------
-
-// Get's called on window close.
-export async function stopStream() {
-    console.log("AUDIO:Stopping audio stream.")
-    if (ai) {
-        try {
-            await ai.quit()
-        } catch(e){
-            console.log('AUDIO: Failed to shutdown audio input.');
-            throw e;
-        }
-    }
-    if (ao) {
-        try {
-            await ao.quit()
-        } catch(e){
-            console.log('AUDIO: Failed to shutdown audio output.');
-            throw e;
-        }
-    }
-}
-
-
diff --git a/src/background/helpers.ts b/src/background/helpers.ts
deleted file mode 100644 (file)
index f0074fb..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-import fs from 'fs';
-import { backgroundMitt } from "@/modules/emitter";
-import { SessionState, WindowState } from "@/types"; 
-import { app } from "electron";
-
-const configPath = app.getPath("userData");
-
-export const ipcEmit = (channel: string, payload: any) => {
-    backgroundMitt.emit('ipc-renderer', {
-        endpoint: channel,
-        message: payload
-    });
-}
-
-export const loadState = (fileName: string): SessionState => {
-    let state: SessionState;
-
-    try {
-        state = JSON.parse(fs.readFileSync(configPath + fileName).toString());
-    } 
-
-    catch (error) {
-        state = {
-            key: false,
-            newMessages: []
-        }
-    }
-
-    return state
-    
-} 
-
-export const loadWinState = (fileName: string): WindowState => {
-    let state: WindowState;
-
-    try {
-        state = JSON.parse(fs.readFileSync(configPath + fileName).toString());
-    } 
-
-    catch (error) {
-        state = {
-            width: 600,
-            height: 500,
-            x: null,
-            y: null,
-        }
-    }
-
-    return state
-    
-} 
-
-// Save session or window state.
-export const saveToJson = (fileName: string, data: any) => {
-
-    fs.writeFile(configPath + fileName, JSON.stringify(data), (err) => {
-        if (err) {
-            console.log("Error when saving to json.")
-        }      
-    })
-
-}
\ No newline at end of file
diff --git a/src/background/init.ts b/src/background/init.ts
deleted file mode 100644 (file)
index 1f47214..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-
-"use strict";
-
-import { app, dialog } from "electron";
-import { createWindow } from './window';
-import { stopStream } from './audio';
-import { backgroundMitt } from '@/modules/emitter';
-import useIpc from "@/background/ipc/index";
-const { autoUpdater } = require('electron-updater');
-
-let win: boolean;
-
-// Listen for window creation.
-backgroundMitt.on('window-active', (state: boolean) => {
-    win = state;
-});
-
-// Auto updating.
-autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' }
-
-autoUpdater.on('update-available', (info: any) => {
-    console.log(`Update available: ${info.version}`)
-})
-
-autoUpdater.on('update-downloaded', (info: any) => {
-
-    const updateDialog = {
-        type: 'info',
-        buttons: ['Restart', 'Later'],
-        title: 'Application Update',
-        message: info.version,
-        detail: 'A new version has been downloaded. Restart the application to apply the updates.'
-    }
-
-    dialog.showMessageBox(updateDialog).then((returnValue) => {
-        if (returnValue.response === 0) autoUpdater.quitAndInstall()
-    })
-
-})
-
-
-
-// Run when electron app is initialized.
-async function main(): Promise<void> {
-
-    console.log("MAIN:Initializing Electron App.");
-
-    useIpc();
-
-    // Must wait til window is created.
-    await createWindow();
-
-}
-
-// Root function of app.
-export function initApp(dev: boolean): void {
-
-    // On initial startup.
-    app.on("ready", () => {
-        // autoUpdater.checkForUpdates()
-        main();
-    });
-
-    // Must keep to ensure app doesn't quit on close.
-    app.on("before-quit", async () => {
-        await stopStream();
-    });
-
-    // Must keep to ensure app doesn't quit on close.
-    app.on("window-all-closed", () => {
-    });
-
-    // When user clicks app icon (re-open)
-    app.on("activate", () => {
-
-        if (!win) {
-            createWindow();
-        }
-
-    });
-
-    // Exit cleanly on request from parent process in development mode.
-    if (dev) {
-        process.on("SIGTERM", () => {
-            app.quit();
-        });
-    }
-}
-
diff --git a/src/background/ipc/audio.ts b/src/background/ipc/audio.ts
deleted file mode 100644 (file)
index 1419273..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-
-"use strict";
-
-import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
-import { fetchAudioInput, toggleRecord } from "@/background/audio";
-
-
-// Toggles record to true to begin capturing chunks.
-const onRecordingStart = (
-    _event: IpcMainEvent,
-    _payload: null
-): void => {
-
-    console.log('[IPC]: start-recording');
-
-    toggleRecord();
-};
-
-
-// Returns recorded audio to frontend and sets record to false.
-const onRecordingStop = async (
-    _event: IpcMainInvokeEvent,
-    _payload: null
-): Promise<Error | string> => {
-
-    console.log('[IPC]: stop-recording');
-
-    return await fetchAudioInput()
-};
-
-
-export default function useAudioListeners(): void {
-
-    ipcMain.removeAllListeners("start-recording");
-    ipcMain.on("start-recording", onRecordingStart);
-
-    ipcMain.removeHandler("stop-recording");
-    ipcMain.handle("stop-recording", onRecordingStop);
-
-}
diff --git a/src/background/ipc/session.ts b/src/background/ipc/session.ts
deleted file mode 100644 (file)
index cc6edeb..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-
-"use strict";
-
-import { initSession, emitNewMessages, sendMessage } from '@/background/session';
-import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
-import { initAudioIO } from "@/background/audio";
-import { ClientMessage } from "@/types";
-import { store } from "@/background/store";
-
-
-// Instantiate socket session with crimata-platorm.
-const onSessionInit = (
-    _event: IpcMainInvokeEvent,
-    cid: string
-): void => {
-
-    console.log('[IPC]: init-session');
-
-    const token = store.get('key');
-    const crimataId = store.get('crimataId');
-
-
-    initSession({
-        token,
-        crimataId
-    });
-
-    initAudioIO();
-}
-
-
-const onAppMounted = (
-    _event: IpcMainInvokeEvent,
-    _payload: null
-): void => {
-
-    console.log('[IPC]: app-mounted');
-
-    emitNewMessages()
-};
-
-
-// Handle messages from window/client.
-const onClientMessage = (
-    _event: IpcMainEvent,
-    payload: ClientMessage
-): void => {
-
-    console.log('[IPC]: client-message');
-
-    sendMessage(payload);
-}
-
-
-export default function useSessionListeners(): void {
-
-    console.log('[IPC]: Init session listeners.');
-
-    // Attach listeners for frontend.
-    ipcMain.removeAllListeners("client-message");
-    ipcMain.on("client-message", onClientMessage);
-
-    // Attack browser window init listener.
-    ipcMain.removeAllListeners("app-mounted");
-    ipcMain.on("app-mounted", onAppMounted);
-
-    ipcMain.removeAllListeners("init-session");
-    ipcMain.on("init-session", onSessionInit);
-
-}
diff --git a/src/background/session.ts b/src/background/session.ts
deleted file mode 100644 (file)
index ebfbc26..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Creates a websocket session with Crimata Servers.
- *
- * Connects to Servers and attempts key authentication. Server will respond
- * with key and user profile. We send the profile to the browser. We also
- * resend this information on new broser window. We then serve as a
- * communication interface between the window and the servers. It will
- * automatically try to reconnect on websocket disconnect.
- */
-
-import { backgroundMitt } from "@/modules/emitter";
-import { ipcEmit, loadState } from './helpers';
-import WebSocket from 'ws';
-
-import useWebSockets from "./websockets";
-
-import { play } from "./audio";
-import { renderMessage } from "@/modules/message";
-import { SessionState } from "@/types";
-
-let win = true;
-
-// Info saved to json on quit (key, newMessages).
-let state: SessionState;
-
-let socket: WebSocket | null = null;
-
-
-// Calls appropriate endpoint for a server message.
-const onMessage = (data: string): void => {
-    let message = JSON.parse(data);
-    if (message === "CLOSE_AUTH_FAIL") {
-        ipcEmit("session-auth-fail", null)
-        return;
-    }
-
-    // Standard message.
-    if (message.content) {
-
-        // Convert to render message
-        message = renderMessage(
-            message.content.text,
-            message.content.audio,
-            message.context,
-            message.modifier
-        );
-
-        if (win) {
-            console.log("SESS:Emitting standard message.")
-            if (message.audio) {
-                play(message.audio)
-            }
-            ipcEmit("render-message", message)
-        }
-
-        else {
-            console.log("SESS:No window: saving message.")
-            state.newMessages.push(message);
-        }
-    }
-
-    else {
-        if (win) {
-            ipcEmit("render-message", message)
-        }
-    }
-
-};
-
-
-// Websockets module.
-const { createSocket, send } = useWebSockets(onMessage);
-
-
-export const emitNewMessages = (): void => {
-    if (state) {
-        ipcEmit("update-state", {
-            newMessages: state.newMessages,
-        });
-    }
-
-}
-
-
-export const sendMessage = (payload: Record<string, any>): void => {
-    try {
-        send(payload)
-    } catch(e) {
-        console.log("Unable to send message: ", payload);
-    }
-
-}
-
-export const endSession = (): void => {
-    if (socket) {
-        socket.close();
-        socket = null;
-    }
-}
-
-
-// Call this to initialize session with Crimata servers.
-export const initSession = (authPayload: {
-    token: string;
-    crimataId: string;
-}): void => {
-    console.log("SESS:Creating new session.")
-
-    // Load Json or createState.
-    state = loadState("session.json");
-
-    // Open socket connection.
-    if (!socket)
-    socket = createSocket(authPayload);
-
-    // Keep win up-to-date.
-    backgroundMitt.on('window-active', (state: boolean) => {
-        win = state;
-    });
-
-}
diff --git a/src/background/websockets.ts b/src/background/websockets.ts
deleted file mode 100644 (file)
index 36eae6f..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-
-"use strict";
-
-import WebSocket from 'ws';
-
-let socket: WebSocket;
-
-const socketUrl = "ws://127.0.0.1:8760"
-
-// Run every time we want to connect to backend.
-export default function useWebSockets(
-    receiveCallback: (s: string) => void,
-    openCallback?: () => void
-) {
-
-    // Returns bool (sucess or fail).
-    const sendMessage = (data: any) => {
-        console.log("WS:Sending message: ", data)
-
-        if (socket.readyState !== 1) {
-            return false
-        }
-
-        else {
-            socket.send(JSON.stringify(data))
-            return true
-        }
-
-    }
-
-    const send = async (data: Record<string, any>): Promise<boolean> => (
-        new Promise((resolve, reject) => {
-            if (socket.readyState !== 1) {
-                reject(false);
-            }
-            socket.send(JSON.stringify(data))
-            resolve(true);
-
-        }))
-
-
-    const onOpen = (_event: WebSocket.OpenEvent) => {
-
-        console.log("WS:Connected to WS Server!");
-        if (openCallback) openCallback();
-
-    }
-
-    const onServerMessage = (event: WebSocket.MessageEvent) => {
-
-        console.log("WS:Message received: ", event.data);
-
-        receiveCallback(event.data.toString())
-
-    }
-
-    const onClose = (event: WebSocket.CloseEvent) => {
-        console.log("WS:Socket closed normally.")
-    }
-
-    // Reconnect automatically on error.
-    const onError = (event: WebSocket.ErrorEvent) => {
-        console.log("WS:WebSocket error: ", event.message);
-
-        console.log("Attempting reconnect in 1s.")
-        setTimeout(createSocket, 1000)
-    }
-
-    const createSocket = (authPayload: {
-        token: string;
-        crimataId: string;
-    }):WebSocket => {
-        socket = new WebSocket(socketUrl)
-
-        // Add listeners.
-        socket.addEventListener("open", (_event: WebSocket.OpenEvent) => {
-            socket.send(JSON.stringify({
-                key: authPayload.token,
-                crimata_id: authPayload.crimataId
-            }));
-        })
-        socket.addEventListener("message", onServerMessage)
-        socket.addEventListener("close", onClose)
-        socket.addEventListener("error", onError)
-
-        return socket;
-
-    }
-
-    return {
-        createSocket,
-        sendMessage,
-        send
-    }
-
-}
diff --git a/src/components/controllers/audioCtrl.ts b/src/components/controllers/audioCtrl.ts
deleted file mode 100644 (file)
index f635c0b..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-import anime from "animejs";
-import useMitt from "@/modules/mitt";
-import { useIpc } from '@/modules/ipc';
-import { onMounted, onUnmounted, ref, Ref } from "vue";
-import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
-import { renderMessage, clientMessage } from '@/modules/message';
-import { postMessage } from "@/ipcRend/session";
-import { invokeStopRecord } from "@/ipcRend/audio";
-
-
-function showRecIcon () {
-
-    anime({
-        targets: '#recIcon',
-        opacity: [0, 0.75],
-        scale: [0.0, 1],
-        duration: 250,
-        easing: 'linear',
-    })
-
-}
-
-function hideRecIcon () {
-
-    anime({
-        targets: '#recIcon',
-        opacity: [0.75, 0],
-        scale: [1, 0],
-        duration: 250,
-        easing: 'linear',
-    })
-
-}
-
-
-export default function useAudioInputController (typing: Ref) {
-
-    // For sending messages.
-    const { post, invoke } = useIpc();
-    const { emitter } = useMitt();
-
-    // Keepp track of when we are recording.
-    const recording = ref(false);
-
-    //---Callbacks-----------------------------------------------
-
-    const onKeyDown = (e: KeyboardEvent) => {
-        const cmd = keyboardNameMap[e.keyCode];
-        // console.log(cmd)
-
-        // Start recording on space bar.
-        if (cmd == "SPACE" && !recording.value && !typing.value) {
-
-            console.log("INPT:Starting record.")
-            post("start-recording", null);
-
-            showRecIcon()
-            recording.value = true;
-
-        }
-
-    }
-
-    const onKeyUp = async (e: KeyboardEvent) => {
-        const cmd = keyboardNameMap[e.keyCode];
-
-        // Stop recording on space up.
-        if (cmd == "SPACE" && recording.value) {
-
-            // Create a message.
-            const message = renderMessage(
-                "",
-                "",
-                "",
-                "sf"
-            )
-
-            // Render it immediately.
-            emitter.emit("self-message", message);
-
-            // Stop recording and get audio from recorder.
-            console.log("INPT:Stopping record.")
-            try {
-                const audio = await invokeStopRecord() as string;
-                // Send message to the backend for processing.
-                const clientM = clientMessage("", audio, message.uid);
-                postMessage(clientM);
-            } catch(e) {
-                console.log('Failed to fetch audio.')
-            } finally {
-                hideRecIcon();
-                recording.value = false;
-            }
-
-
-
-        }
-
-    }
-
-    //-----------------------------------------------------------
-
-    onMounted(() => {
-        window.addEventListener("keydown", onKeyDown);
-        window.addEventListener("keyup", onKeyUp);
-    })
-
-    onUnmounted(() => {
-        window.removeEventListener("keydown", onKeyDown);
-        window.removeEventListener("keyup", onKeyUp);
-    });
-
-
-    return {
-        recording
-    }
-
-}
diff --git a/src/components/keyBoardMaps/keyboardCharMap.ts b/src/components/keyBoardMaps/keyboardCharMap.ts
deleted file mode 100644 (file)
index 102e9c3..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-// This has the UnShifted and Shifted characters that each key maps to
-// Ones that are to be ignored for character input are empty.
-const keyboardCharMap = [
-  ["", ""], // [0]
-  ["", ""], // [1]
-  ["", ""], // [2]
-  ["", ""], // [3]
-  ["", ""], // [4]
-  ["", ""], // [5]
-  ["", ""], // [6]
-  ["", ""], // [7]
-  ["", ""], // [8]
-  ["", ""], // [9]
-  ["", ""], // [10]
-  ["", ""], // [11]
-  ["", ""], // [12]
-  ["\r", "\r"], // [13]        - MOST control characters are ignored.  This one (Carriage Return, or "Enter") is significant!
-  ["", ""], // [14]
-  ["", ""], // [15]
-  ["", ""], // [16]
-  ["", ""], // [17]
-  ["", ""], // [18]
-  ["", ""], // [19]
-  ["", ""], // [20]
-  ["", ""], // [21]
-  ["", ""], // [22]
-  ["", ""], // [23]
-  ["", ""], // [24]
-  ["", ""], // [25]
-  ["", ""], // [26]
-  ["", ""], // [27]
-  ["", ""], // [28]
-  ["", ""], // [29]
-  ["", ""], // [30]
-  ["", ""], // [31]
-  [" ", " "], // [32]  // SPACE!  Don't "clean it up" and remove the space!
-  ["", ""], // [33]
-  ["", ""], // [34]
-  ["", ""], // [35]
-  ["", ""], // [36]
-  ["", ""], // [37]
-  ["", ""], // [38]
-  ["", ""], // [39]
-  ["", ""], // [40]
-  ["", ""], // [41]
-  ["", ""], // [42]
-  ["", ""], // [43]
-  ["", ""], // [44]
-  ["", ""], // [45]
-  ["", ""], // [46]
-  ["", ""], // [47]
-  ["0", ")"], // [48]
-  ["1", "!"], // [49]
-  ["2", "@"], // [50]
-  ["3", "#"], // [51]
-  ["4", "$"], // [52]
-  ["5", "%"], // [53]
-  ["6", "^"], // [54]
-  ["7", "&"], // [55]
-  ["8", "*"], // [56]
-  ["9", "("], // [57]
-  ["", ""], // [58]
-  [";", ":"], // [59]
-  ["<", ""], // [60]
-  ["=", ""], // [61]
-  [">", ""], // [62]
-  ["?", ""], // [63]   shifted;  else "/"
-  ["", ""], // [64]
-  ["a", "A"], // [65]
-  ["b", "B"], // [66]
-  ["c", "C"], // [67]
-  ["d", "D"], // [68]
-  ["e", "E"], // [69]
-  ["f", "F"], // [70]
-  ["g", "G"], // [71]
-  ["h", "H"], // [72]
-  ["i", "I"], // [73]
-  ["j", "J"], // [74]
-  ["k", "K"], // [75]
-  ["l", "L"], // [76]
-  ["m", "M"], // [77]
-  ["n", "N"], // [78]
-  ["o", "O"], // [79]
-  ["p", "P"], // [80]
-  ["q", "Q"], // [81]
-  ["r", "R"], // [82]
-  ["s", "S"], // [83]
-  ["t", "T"], // [84]
-  ["u", "U"], // [85]
-  ["v", "V"], // [86]
-  ["w", "W"], // [87]
-  ["x", "X"], // [88]
-  ["y", "Y"], // [89]
-  ["z", "Z"], // [90]
-  ["", ""], // [91] Windows Key (Windows) or Command Key (Mac)
-  ["", ""], // [92]
-  ["", ""], // [93]
-  ["", ""], // [94]
-  ["", ""], // [95]
-  // Number Keypad Entries...
-  ["0", ""], // [96]
-  ["1", ""], // [97]
-  ["2", ""], // [98]
-  ["3", ""], // [99]
-  ["4", ""], // [100]
-  ["5", ""], // [101]
-  ["6", ""], // [102]
-  ["7", ""], // [103]
-  ["8", ""], // [104]
-  ["9", ""], // [105]
-  ["*", ""], // [106]
-  ["+", ""], // [107]
-  ["", ""], // [108]
-  ["-", ""], // [109]
-  [".", ""], // [110]
-  ["/", ""], // [111]
-
-  ["", ""], // [112]
-  ["", ""], // [113]
-  ["", ""], // [114]
-  ["", ""], // [115]
-  ["", ""], // [116]
-  ["", ""], // [117]
-  ["", ""], // [118]
-  ["", ""], // [119]
-  ["", ""], // [120]
-  ["", ""], // [121]
-  ["", ""], // [122]
-  ["", ""], // [123]
-  ["", ""], // [124]
-  ["", ""], // [125]
-  ["", ""], // [126]
-  ["", ""], // [127]
-  ["", ""], // [128]
-  ["", ""], // [129]
-  ["", ""], // [130]
-  ["", ""], // [131]
-  ["", ""], // [132]
-  ["", ""], // [133]
-  ["", ""], // [134]
-  ["", ""], // [135]
-  ["", ""], // [136]
-  ["", ""], // [137]
-  ["", ""], // [138]
-  ["", ""], // [139]
-  ["", ""], // [140]
-  ["", ""], // [141]
-  ["", ""], // [142]
-  ["", ""], // [143]
-  ["", ""], // [144]
-  ["", ""], // [145]
-  ["", ""], // [146]
-  ["", ""], // [147]
-  ["", ""], // [148]
-  ["", ""], // [149]
-  ["", ""], // [150]
-  ["", ""], // [151]
-  ["", ""], // [152]
-  ["", ""], // [153]
-  ["", ""], // [154]
-  ["", ""], // [155]
-  ["", ""], // [156]
-  ["", ""], // [157]
-  ["", ""], // [158]
-  ["", ""], // [159]
-  ["", ""], // [160]
-  ["", ""], // [161]
-  ["", ""], // [162]
-  ["", ""], // [163]
-  ["", ""], // [164]
-  ["", ""], // [165]
-  ["", ""], // [166]
-  ["", ""], // [167]
-  ["", ""], // [168]
-  ["", ""], // [169]
-  ["", ""], // [170]
-  ["", ""], // [171]
-  ["", ""], // [172]
-  ["", ""], // [173]
-  ["", ""], // [174]
-  ["", ""], // [175]
-  ["", ""], // [176]
-  ["", ""], // [177]
-  ["", ""], // [178]
-  ["", ""], // [179]
-  ["", ""], // [180]
-  ["", ""], // [181]
-  ["", ""], // [182]
-  ["", ""], // [183]
-  ["", ""], // [184]
-  ["", ""], // [185]
-  [";", ":"], // [186]
-  ["=", "+"], // [187]
-  [",", "<"], // [188]
-  ["-", "_"], // [189]
-  [".", ">"], // [190]
-  ["/", "?"], // [191]
-  ["`", "~"], // [192]
-  ["", ""], // [193]
-  ["", ""], // [194]
-  ["", ""], // [195]
-  ["", ""], // [196]
-  ["", ""], // [197]
-  ["", ""], // [198]
-  ["", ""], // [199]
-  ["", ""], // [200]
-  ["", ""], // [201]
-  ["", ""], // [202]
-  ["", ""], // [203]
-  ["", ""], // [204]
-  ["", ""], // [205]
-  ["", ""], // [206]
-  ["", ""], // [207]
-  ["", ""], // [208]
-  ["", ""], // [209]
-  ["", ""], // [210]
-  ["", ""], // [211]
-  ["", ""], // [212]
-  ["", ""], // [213]
-  ["", ""], // [214]
-  ["", ""], // [215]
-  ["", ""], // [216]
-  ["", ""], // [217]
-  ["", ""], // [218]
-  ["[", "{"], // [219]
-  ["\\", "|"], // [220]
-  ["]", "}"], // [221]
-  ["'", '"'], // [222]
-  ["", ""], // [223]
-  ["", ""], // [224]
-  ["", ""], // [225]
-  ["", ""], // [226]
-  ["", ""], // [227]
-  ["", ""], // [228]
-  ["", ""], // [229]
-  ["", ""], // [230]
-  ["", ""], // [231]
-  ["", ""], // [232]
-  ["", ""], // [233]
-  ["", ""], // [234]
-  ["", ""], // [235]
-  ["", ""], // [236]
-  ["", ""], // [237]
-  ["", ""], // [238]
-  ["", ""], // [239]
-  ["", ""], // [240]
-  ["", ""], // [241]
-  ["", ""], // [242]
-  ["", ""], // [243]
-  ["", ""], // [244]
-  ["", ""], // [245]
-  ["", ""], // [246]
-  ["", ""], // [247]
-  ["", ""], // [248]
-  ["", ""], // [249]
-  ["", ""], // [250]
-  ["", ""], // [251]
-  ["", ""], // [252]
-  ["", ""], // [253]
-  ["", ""], // [254]
-  ["", ""] // [255]
-];
-export default keyboardCharMap;
diff --git a/src/components/keyBoardMaps/keyboardNameMap.ts b/src/components/keyBoardMaps/keyboardNameMap.ts
deleted file mode 100644 (file)
index 8f385ae..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-// names of known key codes (0-255)
-const keyboardNameMap = [
-  "", // [0]
-  "", // [1]
-  "", // [2]
-  "CANCEL", // [3]
-  "", // [4]
-  "", // [5]
-  "HELP", // [6]
-  "", // [7]
-  "BACK_SPACE", // [8]
-  "TAB", // [9]
-  "", // [10]
-  "", // [11]
-  "CLEAR", // [12]
-  "ENTER", // [13]
-  "ENTER_SPECIAL", // [14]
-  "", // [15]
-  "SHIFT", // [16]
-  "CONTROL", // [17]
-  "ALT", // [18]
-  "PAUSE", // [19]
-  "CAPS_LOCK", // [20]
-  "KANA", // [21]
-  "EISU", // [22]
-  "JUNJA", // [23]
-  "FINAL", // [24]
-  "HANJA", // [25]
-  "", // [26]
-  "ESCAPE", // [27]
-  "CONVERT", // [28]
-  "NONCONVERT", // [29]
-  "ACCEPT", // [30]
-  "MODECHANGE", // [31]
-  "SPACE", // [32]
-  "PAGE_UP", // [33]
-  "PAGE_DOWN", // [34]
-  "END", // [35]
-  "HOME", // [36]
-  "LEFT", // [37]
-  "UP", // [38]
-  "RIGHT", // [39]
-  "DOWN", // [40]
-  "SELECT", // [41]
-  "PRINT", // [42]
-  "EXECUTE", // [43]
-  "PRINTSCREEN", // [44]
-  "INSERT", // [45]
-  "DELETE", // [46]
-  "", // [47]
-  "0", // [48]
-  "1", // [49]
-  "2", // [50]
-  "3", // [51]
-  "4", // [52]
-  "5", // [53]
-  "6", // [54]
-  "7", // [55]
-  "8", // [56]
-  "9", // [57]
-  "COLON", // [58]
-  "SEMICOLON", // [59]
-  "LESS_THAN", // [60]
-  "EQUALS", // [61]
-  "GREATER_THAN", // [62]
-  "QUESTION_MARK", // [63]
-  "AT", // [64]
-  "A", // [65]
-  "B", // [66]
-  "C", // [67]
-  "D", // [68]
-  "E", // [69]
-  "F", // [70]
-  "G", // [71]
-  "H", // [72]
-  "I", // [73]
-  "J", // [74]
-  "K", // [75]
-  "L", // [76]
-  "M", // [77]
-  "N", // [78]
-  "O", // [79]
-  "P", // [80]
-  "Q", // [81]
-  "R", // [82]
-  "S", // [83]
-  "T", // [84]
-  "U", // [85]
-  "V", // [86]
-  "W", // [87]
-  "X", // [88]
-  "Y", // [89]
-  "Z", // [90]
-  "OS_KEY", // [91] Windows Key (Windows) or Command Key (Mac)
-  "", // [92]
-  "CONTEXT_MENU", // [93]
-  "", // [94]
-  "SLEEP", // [95]
-  "NUMPAD0", // [96]
-  "NUMPAD1", // [97]
-  "NUMPAD2", // [98]
-  "NUMPAD3", // [99]
-  "NUMPAD4", // [100]
-  "NUMPAD5", // [101]
-  "NUMPAD6", // [102]
-  "NUMPAD7", // [103]
-  "NUMPAD8", // [104]
-  "NUMPAD9", // [105]
-  "MULTIPLY", // [106]
-  "ADD", // [107]
-  "SEPARATOR", // [108]
-  "SUBTRACT", // [109]
-  "DECIMAL", // [110]
-  "DIVIDE", // [111]
-  "F1", // [112]
-  "F2", // [113]
-  "F3", // [114]
-  "F4", // [115]
-  "F5", // [116]
-  "F6", // [117]
-  "F7", // [118]
-  "F8", // [119]
-  "F9", // [120]
-  "F10", // [121]
-  "F11", // [122]
-  "F12", // [123]
-  "F13", // [124]
-  "F14", // [125]
-  "F15", // [126]
-  "F16", // [127]
-  "F17", // [128]
-  "F18", // [129]
-  "F19", // [130]
-  "F20", // [131]
-  "F21", // [132]
-  "F22", // [133]
-  "F23", // [134]
-  "F24", // [135]
-  "", // [136]
-  "", // [137]
-  "", // [138]
-  "", // [139]
-  "", // [140]
-  "", // [141]
-  "", // [142]
-  "", // [143]
-  "NUM_LOCK", // [144]
-  "SCROLL_LOCK", // [145]
-  "WIN_OEM_FJ_JISHO", // [146]
-  "WIN_OEM_FJ_MASSHOU", // [147]
-  "WIN_OEM_FJ_TOUROKU", // [148]
-  "WIN_OEM_FJ_LOYA", // [149]
-  "WIN_OEM_FJ_ROYA", // [150]
-  "", // [151]
-  "", // [152]
-  "", // [153]
-  "", // [154]
-  "", // [155]
-  "", // [156]
-  "", // [157]
-  "", // [158]
-  "", // [159]
-  "CIRCUMFLEX", // [160]
-  "EXCLAMATION", // [161]
-  "DOUBLE_QUOTE", // [162]
-  "HASH", // [163]
-  "DOLLAR", // [164]
-  "PERCENT", // [165]
-  "AMPERSAND", // [166]
-  "UNDERSCORE", // [167]
-  "OPEN_PAREN", // [168]
-  "CLOSE_PAREN", // [169]
-  "ASTERISK", // [170]
-  "PLUS", // [171]
-  "PIPE", // [172]
-  "HYPHEN_MINUS", // [173]
-  "OPEN_CURLY_BRACKET", // [174]
-  "CLOSE_CURLY_BRACKET", // [175]
-  "TILDE", // [176]
-  "", // [177]
-  "", // [178]
-  "", // [179]
-  "", // [180]
-  "VOLUME_MUTE", // [181]
-  "VOLUME_DOWN", // [182]
-  "VOLUME_UP", // [183]
-  "", // [184]
-  "", // [185]
-  "SEMICOLON", // [186]
-  "EQUALS", // [187]
-  "COMMA", // [188]
-  "MINUS", // [189]
-  "PERIOD", // [190]
-  "SLASH", // [191]
-  "BACK_QUOTE", // [192]
-  "", // [193]
-  "", // [194]
-  "", // [195]
-  "", // [196]
-  "", // [197]
-  "", // [198]
-  "", // [199]
-  "", // [200]
-  "", // [201]
-  "", // [202]
-  "", // [203]
-  "", // [204]
-  "", // [205]
-  "", // [206]
-  "", // [207]
-  "", // [208]
-  "", // [209]
-  "", // [210]
-  "", // [211]
-  "", // [212]
-  "", // [213]
-  "", // [214]
-  "", // [215]
-  "", // [216]
-  "", // [217]
-  "", // [218]
-  "OPEN_BRACKET", // [219]
-  "BACK_SLASH", // [220]
-  "CLOSE_BRACKET", // [221]
-  "QUOTE", // [222]
-  "", // [223]
-  "META", // [224]
-  "ALTGR", // [225]
-  "", // [226]
-  "WIN_ICO_HELP", // [227]
-  "WIN_ICO_00", // [228]
-  "", // [229]
-  "WIN_ICO_CLEAR", // [230]
-  "", // [231]
-  "", // [232]
-  "WIN_OEM_RESET", // [233]
-  "WIN_OEM_JUMP", // [234]
-  "WIN_OEM_PA1", // [235]
-  "WIN_OEM_PA2", // [236]
-  "WIN_OEM_PA3", // [237]
-  "WIN_OEM_WSCTRL", // [238]
-  "WIN_OEM_CUSEL", // [239]
-  "WIN_OEM_ATTN", // [240]
-  "WIN_OEM_FINISH", // [241]
-  "WIN_OEM_COPY", // [242]
-  "WIN_OEM_AUTO", // [243]
-  "WIN_OEM_ENLW", // [244]
-  "WIN_OEM_BACKTAB", // [245]
-  "ATTN", // [246]
-  "CRSEL", // [247]
-  "EXSEL", // [248]
-  "EREOF", // [249]
-  "PLAY", // [250]
-  "ZOOM", // [251]
-  "", // [252]
-  "PA1", // [253]
-  "WIN_OEM_CLEAR", // [254]
-  "" // [255]
-];
-
-export default keyboardNameMap;
diff --git a/src/components/message.vue b/src/components/message.vue
deleted file mode 100644 (file)
index e69f29c..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-<template>  
-  <div
-    class="message"
-    :class="`${message.isChild}ChildMessage`"
-    :id="`${message.modifier}Message`"
-    :key="message.id"
-  >
-
-    <!-- Three types of messages: self (sf), friend (fr), and crimata (ai) -->
-    <!-- !denoted by the modifier attribute -->
-
-    <!-- Shows when a new session is created with Crimata. -->
-    <div 
-      v-if="message.context === 'Crimata'"
-      class="divider"
-    >
-      <div>new session</div>
-    </div>
-
-    <!-- Includes bubble and context. -->
-    <div 
-      :id="`${message.modifier}MessageBox`"
-      class="messageBox"
-    >
-
-      <!-- message bubble -->
-      <div
-        class="bubble"
-        :class="`${message.modifier}-${message.isChild}ChildBubble`"
-        :id="`${message.modifier}Bubble`"
-      >
-
-        <!-- Notification dot for new messages. -->
-        <div v-if="notify === true"
-          class="notify"
-        />
-
-        <!-- Show loader when audio is being transcribed. -->
-        <div 
-          v-if="message.content.text === ''" 
-          class="contentLoader"
-        >
-          <div class="contentLoaderDot"></div>
-          <div class="contentLoaderDot"></div>
-          <div class="contentLoaderDot"></div>
-        </div>
-
-        <!-- Show question mark if transcription comes back as unknown. -->
-        <div
-          v-else-if="message.content.text === false"
-          class="questionMark"
-        >
-        ?
-        </div>
-
-        <!-- Else, show text. -->          
-        <div v-else>
-          {{ message.content.text }}
-        </div>
-
-      </div>
-
-      <!-- message context -->
-      <span 
-        v-if="message.isChild === 'last' || message.isChild === 'none'"
-        class="context"
-      >
-
-        <!-- Render Crimata icon or friend's initials. -->
-        <div v-if="message.modifier == 'ai'" class="photo">
-          <img src="@/assets/crimata.svg"/>
-        </div>
-
-        <!-- Render Crimata icon or friend's initials. -->
-        <div v-if="message.modifier == 'fr'" class="photo"
-          :class="{ playing: isPlaying }"
-        >
-          {{ message.context[0] }}
-        </div>
-
-        {{ message.context }}
-
-      </span>
-
-    </div>
-
-  </div>
-</template>
-
-<script lang='ts'>
-
- import { defineComponent, ref, onMounted } from 'vue';
-
- export default defineComponent({
-  name: "MessageItem",
-
-  props: ["message"],
-
-  setup(props) {
-
-    let addListener = false;
-
-    const notify = ref(false)
-    const isPlaying = ref(false);
-
-    if (!props.message.seen) {
-      console.log("MSG:Checking...")
-
-      // newMessages always render with blue dot.
-      if (props.message.newMessage) {
-        console.log("MSG:New message, notifying.")
-        props.message.seen = true;
-        notify.value = true;
-      }
-
-      else {
-
-        // Do nothing if window is visible on setup.
-        if (document.visibilityState === "visible") {
-          props.message.seen = true;
-        } 
-
-        // We must listen for window visible event.
-        else {
-          console.log("MSG:Unseen, adding listener.")
-          addListener = true;
-        }
-
-      }
-
-    }
-
-    // Callback for window visibilityState.
-    const onVisibilityChange = (_event: any) => {
-      if (document.visibilityState === "visible") {
-        console.log("MSG:Window visible, notifying.")
-
-        props.message.seen = true;
-        notify.value = true; // triggers blue dot anim.
-
-        // Once complete we can remove.
-        document.removeEventListener("visibilitychange", onVisibilityChange)
-      }
-    }
-
-    const onStopPlayback = (_event: any) => {
-      window.ipcRenderer.removeAllListeners("stop-playback-anim");
-      isPlaying.value = false
-    }
-
-    onMounted(() => {
-
-      // Listen for window focus event.
-      if (addListener) {
-        document.addEventListener("visibilitychange", onVisibilityChange)
-      }
-      
-      // Turn on audio playback animation if audio.
-      if (props.message.content.audio === true) {
-        window.ipcRenderer.on("stop-playback-anim", onStopPlayback);
-        isPlaying.value = true
-      }
-
-    })
-
-    return {
-      notify,
-      isPlaying
-    }
-  }
-
- })
-
-</script>
-
-<style lang="scss" scoped>
-
-  .message {
-    width: 100vw;
-
-    display: flex;
-    flex-direction: column;
-
-    padding-top: 9px;
-    padding-bottom: 9px;
-
-  }
-
-  .message:first-child {
-    margin-top: 55px;
-  }
-
-  .message:last-child {
-    margin-bottom: 18px;
-  }
-
-  .divider {
-    width: 100vw;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-
-    font-family: "SF Compact Display";
-    font-size: 12px;
-    font-weight: bold;
-    color: #9B9B9B;
-
-    margin-bottom: 18px;
-  }
-
-  .firstChildMessage {
-    padding-bottom: 2px;
-  }
-
-  .middleChildMessage {
-    padding-top: 2px;
-    padding-bottom: 2px;
-  }
-
-  .lastChildMessage {
-    padding-top: 2px;
-  }
-
-  #aiMessage {
-    align-items: flex-start;
-  }
-
-  #sfMessage {
-    align-items: flex-end;
-  }
-
-  #frMessage {
-    align-items: flex-start;
-  }
-
-  .messageBox {
-    position: relative;
-
-    display: flex;
-    flex-direction: column;
-
-    // Animate on render.
-    animation-name: appear;
-    animation-duration: 0.25s;
-  }
-
-  #aiMessageBox {
-    margin-left: 20px;
-    align-items: flex-start;
-  }
-
-  #sfMessageBox {
-    margin-right: 20px;
-    align-items: flex-end;
-  }
-
-  #frMessageBox {
-    margin-left: 20px;
-    align-items: flex-start;
-  }
-
-  .bubble {
-    position: relative;
-
-    max-width: 66vw;
-
-    display: flex;
-    flex-direction: column;
-
-    font-family: "SF Pro Text";
-    font-size: 14px;
-
-    padding: 10px;
-
-    border-radius: 18px;
-  }
-
-  @keyframes appear {
-    10% {
-      transform: scale(0.3);
-    }
-    100% {
-      transform: scale(1);
-    }
-  }
-
-  #aiBubble {
-    background-color: white;
-  }
-
-  #sfBubble {
-    background-color: #58c4fd;
-    color: white;
-  }
-
-  #frBubble {
-    background-color: white;
-  }
-
-  .sf-firstChildBubble {
-    border-bottom-right-radius: 9px;
-  }
-
-  .sf-middleChildBubble {
-    border-top-right-radius: 9px;
-    border-bottom-right-radius: 9px;
-  }
-
-  .sf-lastChildBubble {
-    border-top-right-radius: 9px;
-  }
-
-  .ai-firstChildBubble, .fr-firstChildBubble {
-    border-bottom-left-radius: 9px;
-  }
-
-  .ai-middleChildBubble, .fr-middleChildBubble {
-    border-top-left-radius: 9px;
-    border-bottom-left-radius: 9px;
-  }
-
-  .ai-lastChildBubble, .fr-lastChildBubble {
-    border-top-left-radius: 9px;
-  }
-
-  .notify {
-    position: absolute;
-    width: 12px;
-    height: 12px;
-    border-radius: 50%;
-    background-color: #58D9FF;
-    top: -5px;
-    left: -5px;
-    border: 2px solid #EBEBEB;
-    transform: scale(0);
-
-    animation-name: notify-anim;
-    animation-duration: 5s;
-  }
-
-  @keyframes notify-anim {
-    0%, 90% {
-      transform: scale(1);
-    }
-    100% {
-      transform: scale(0);
-    }
-  }
-
-  .context {
-    position: relative;
-
-    display: flex;
-    align-items: center;
-
-    font-family: "SF Compact Display";
-    font-size: 12px;
-    font-weight: bold;
-    margin-top: 5px;
-  }
-
-  .photo {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-
-    margin-right: 5px;
-
-    width: 30px;
-    height: 30px;
-
-    font-size: 14px;
-
-    background-color: white;
-    border-radius: 15px;
-  }
-
-  // Apply for audio message playback.
-  .playing {
-    animation-name: circle1;
-    animation-duration: 2s;
-    animation-iteration-count: infinite;
-  }
-
-  @keyframes circle1 {
-    0%,
-    100% {
-      box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
-    }
-    25% {
-      box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
-    }
-    50% {
-      box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
-    }
-    75% {
-      box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
-    }
-  }
-
-  .contentLoader {
-    display: flex;
-  }
-
-  .contentLoaderDot {
-    width: 5px;
-    height: 5px;
-
-    margin: 2px;
-    border-radius: 2.5px;
-    background-color: #357CA2;
-  }
-
-  .questionMark {
-    font-weight: 900;
-    color: #357CA2;
-  }
-
-</style>
diff --git a/src/components/messenger.vue b/src/components/messenger.vue
deleted file mode 100644 (file)
index f5512a9..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<template>
-  <InputItem :initials="profile.initials"/>
-  <Settings />
-
-  <div id="recIcon" />
-
-  <!-- List of message bubbles. -->
-  <div id="messenger">
-    <Message
-      v-for="message in messages"
-      :message="message[1]"
-      :key="message[0]"
-    />
-  </div>
-
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, onUnmounted } from "vue";
-import Message from "@/components/message.vue";
-import InputItem from "@/components/inputItem.vue";
-import Settings from "@/components/settings.vue";
-import useMitt from "@/modules/mitt";
-import useMessages from "@/modules/messages";
-
-export default defineComponent({
-  name: "Messenger",
-
-  props: ["profile", "newMessages"],
-
-  components: {
-    Message,
-    InputItem,
-    Settings
-  },
-
-  setup(props) {
-
-    // Listen for self-messages from inputItem.
-    const { emitter } = useMitt();
-
-    // Handle messages in view.
-    const { messages, prepMessageView, updateMessageView } = useMessages();
-
-    onMounted(() => {
-      const crimataId = props.profile.crimataId;
-      console.log(`MSGR:Initializing messenger for ${crimataId}.`)
-
-      // Load the message history.
-      if (crimataId === window.localStorage.getItem("last_usr")) {
-        prepMessageView(props.newMessages);
-      } else {
-        messages.value.clear();
-      }
-
-      // New content listeners.
-      emitter.on('self-message', (message) => updateMessageView(message));
-      window.ipcRenderer.on("render-message", (_e: any, payload: any) => {
-        updateMessageView(payload.message);
-      });
-
-      // Save the usr for next time.
-      window.localStorage.setItem("last_usr", crimataId);
-
-    });
-
-    onUnmounted(() => {
-      window.ipcRenderer.removeAllListeners("render-message");
-      emitter.all.clear();
-    });
-
-    return {
-      messages
-    };
-
-  },
-
-});
-</script>
-
-<style lang="scss" scoped>
-
-#messenger {
-  width: 100vw;
-  height: 100vh;
-  overflow: auto;
-}
-
-#messenger::-webkit-scrollbar {
-  display: none;
-}
-
-#recIcon {
-  position: fixed;
-  right: 0;
-
-  opacity: 0;
-  transform: scale(0.0);
-
-  margin-top: 24px;
-  margin-right: 66px;
-
-
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  background-color: #FF3B3B;
-
-  z-index: 2;
-
-}
-
-
-</style>
diff --git a/src/components/textInput.vue b/src/components/textInput.vue
deleted file mode 100644 (file)
index 45e1af7..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<template>
-
-  <input 
-    id="textInput" 
-    type="text" 
-  />
-
-</template>
-
-<script lang="ts">
-import { defineComponent } from "vue";
-
-export default defineComponent({
-  name: "TextInput",
-
-})
-</script>
-
-<style lang="scss" scoped>
-
-#textInput {
-  position: absolute;
-
-  opacity: 0;
-
-  min-width: 150px;
-  height: 16px;
-
-  border-radius: 18px;
-
-  padding: 10px;
-  margin-right: 10px;
-  margin-left: 10px;
-
-  outline: none;
-  border: none;
-  pointer-events: none;
-
-  background-color: white;
-
-  z-index: -1;
-
-  box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
-
-  transform: translateX(50px) scale(0.3);
-
-}
-
-</style>
\ No newline at end of file
diff --git a/src/composables/audio.ts b/src/composables/audio.ts
new file mode 100644 (file)
index 0000000..844e791
--- /dev/null
@@ -0,0 +1,187 @@
+/* eslint @typescript-eslint/no-var-requires: "off" */
+
+"use strict";
+
+// where the audio goes
+let buffer: ArrayBuffer[] = [];
+
+// place audio data in buffer
+export function collect (chunk: ArrayBuffer) {
+    buffer.push(chunk);
+}
+
+// return audio and clear buffer
+export function flush () {
+    const bufferCopy = buffer;
+    buffer = [];
+    return bufferCopy;
+}
+
+// import { backgroundMitt } from '@/modules/emitter';
+// const portAudio = require('naudiodon');
+
+// // Audio in and out stream objects.
+// let ai: typeof portAudio.AudioIO | boolean = false;
+// let ao: typeof portAudio.AudioIO | boolean = false;
+
+// // Whether activly recording.
+// let record = false;
+
+// const audioContainer = {
+//     input: '',
+// }
+
+// const audioOptions = {
+//     channelCount: 1,
+//     sampleFormat: 16,
+//     sampleRate: 16000,
+//     deviceId: -1,
+//     closeOnError: false,
+// }
+
+// export const toggleRecord = (): void => { record = !record };
+
+
+// export const fetchAudioInput = (): Promise<Error | string> => (
+
+//     new Promise((resolve, reject) => {
+
+//         try {
+//             resolve(audioContainer.input);
+//             toggleRecord();
+//         } catch (e) {
+//             reject(new Error('Failed to fetch the audio.'))
+//         }
+
+//     })
+// )
+
+
+// // Main audio function run by run.ts module.
+// export function initAudioIO(): void {
+//     console.log("AUDIO:Starting io streams.")
+
+//     if (!ai) {
+
+//         // Initialize and start input stream.
+//         ai = new portAudio.AudioIO({ inOptions: audioOptions });
+//         ai.setEncoding("hex");
+//         ai.start();
+
+//         // On each data chunk...
+//         ai.on('data', (chunk: string) => {
+
+//             // If recording, we capture the data.
+//             if (record) {
+//                 console.log('AUDIO:Recording...')
+//                 audioContainer.input += chunk;
+//             }
+
+//             // Else, we don't capture and also clear audioContainer.
+//             else {
+//                 if (audioContainer.input.length) {
+//                     audioContainer.input = "";
+//                 }
+//             }
+
+//         });
+//     }
+
+//     if (!ao) {
+
+//         // Initialize and start input stream.
+//         ao = new portAudio.AudioIO({ outOptions: audioOptions });
+//         ao.start();
+
+//     }
+// }
+
+
+// // ---Audio playback--------------------------------------------
+
+// // Split Buffer into an array of len-sized Buffers.
+// function bufSplit(buf: Buffer, len: number): Array<Buffer> {
+//     const chunks = [];
+//     let i = 0;
+//     let L = len;
+
+//     while(i < buf.byteLength) {
+//       chunks.push(buf.slice(i, L));
+//       i = L;
+//       L += len;
+//     }
+
+//     return chunks;
+// }
+
+// // Audio playback.
+// export function play(input: string): void {
+
+//     // Format the audio.
+//     const audio = bufSplit(
+//         Buffer.from(input as string, 'hex'),
+//         8192
+//     );
+
+//     // Called on end of write.
+//     const callback = () => {
+
+//         // We stop audio playback anim.
+//         backgroundMitt.emit('ipc-renderer', {
+//             endpoint: 'stop-playback-anim'
+//         });
+
+//     }
+
+//     write();
+
+//     // Iterate through audio array and write buffers to portAudio writable.
+//     function write() {
+//         let chunk: Buffer;
+//         let ok = true;
+//         let i = 0;
+
+//         do {
+//             chunk = audio[i];
+//             if (i === audio.length - 1) {
+//                 // write last chunk.
+//                 ao.write(chunk, null, callback);
+//             } else {
+//                 // check for backpreassure.
+//                 ok = ao.write(chunk, null);
+//             }
+//             i++;
+//         } while (i < audio.length && ok);
+
+//         if (i < audio.length) {
+//             // Had to stop early!
+//             // Write some more once it drains.
+//             ao.once('drain', write);
+//         }
+//     }
+// }
+
+// // -------------------------------------------------------------
+
+// // Get's called on window close.
+// export async function stopStream() {
+//     console.log("AUDIO:Stopping audio stream.")
+//     if (ai) {
+//         try {
+//             await ai.quit()
+//         } catch(e){
+//             console.log('AUDIO: Failed to shutdown audio input.');
+//             throw e;
+//         }
+//     }
+//     if (ao) {
+//         try {
+//             await ao.quit()
+//         } catch(e){
+//             console.log('AUDIO: Failed to shutdown audio output.');
+//             throw e;
+//         }
+//     }
+// }
+
+
diff --git a/src/composables/autoUpdate.ts b/src/composables/autoUpdate.ts
new file mode 100644 (file)
index 0000000..1a6b780
--- /dev/null
@@ -0,0 +1,31 @@
+const { autoUpdater } = require('electron-updater');
+
+let win: boolean;
+
+// Listen for window creation.
+backgroundMitt.on('window-active', (state: boolean) => {
+    win = state;
+});
+
+// Auto updating.
+autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' }
+
+autoUpdater.on('update-available', (info: any) => {
+    console.log(`Update available: ${info.version}`)
+})
+
+autoUpdater.on('update-downloaded', (info: any) => {
+
+    const updateDialog = {
+        type: 'info',
+        buttons: ['Restart', 'Later'],
+        title: 'Application Update',
+        message: info.version,
+        detail: 'A new version has been downloaded. Restart the application to apply the updates.'
+    }
+
+    dialog.showMessageBox(updateDialog).then((returnValue) => {
+        if (returnValue.response === 0) autoUpdater.quitAndInstall()
+    })
+
+})
\ No newline at end of file
similarity index 51%
rename from src/modules/emitter.ts
rename to src/composables/emitter.ts
index bc916935d23bb3effd7e0011fd495c9670fb1697..ee6bb5eb3835c3bb9defab2b097a548471a0c501 100644 (file)
@@ -1,13 +1,15 @@
 /* eslint-disable */
 
-import { Emitter } from "mitt";
-
 // Backend emitter
 
-type Mitt = Emitter;
-
 const EventEmitter = require('events');
-
 class BackgroundMitt extends EventEmitter { }
 
 export const backgroundMitt = new BackgroundMitt();
+
+export default function ipcEmit (channel: string, payload: any) {
+    backgroundMitt.emit('ipc-renderer', {
+        endpoint: channel,
+        message: payload
+    });
+}
similarity index 100%
rename from src/modules/http.ts
rename to src/composables/http.ts
diff --git a/src/composables/json.ts b/src/composables/json.ts
new file mode 100644 (file)
index 0000000..43b3804
--- /dev/null
@@ -0,0 +1,9 @@
+export const saveToJson = (fileName: string, data: any) => {
+
+    fs.writeFile(configPath + fileName, JSON.stringify(data), (err) => {
+        if (err) {
+            console.log("Error when saving to json.")
+        }      
+    })
+
+}
\ No newline at end of file
diff --git a/src/composables/websockets.ts b/src/composables/websockets.ts
new file mode 100644 (file)
index 0000000..779f00b
--- /dev/null
@@ -0,0 +1,73 @@
+
+"use strict";
+
+import WebSocket from 'ws';
+
+export default function useWebSockets(receiveCallback: (s: string) => void, openCallback?: () => void) {
+
+    let socket: WebSocket | null = null;
+
+    const send = async (data: Record<string, any>): Promise<boolean> => {
+        return new Promise((resolve, reject) => {
+            if (socket) {
+                if (socket.readyState === WebSocket.OPEN) {
+                    socket.send(JSON.stringify(data));
+                    resolve(true);
+                }
+            }
+            reject(false);
+        });
+    }
+
+    const onOpen = (_event: WebSocket.OpenEvent) => {
+        console.log("WS:Connected to WS Server!");
+        if (openCallback) openCallback();
+    }
+
+    const onServerMessage = (event: WebSocket.MessageEvent) => {
+        console.log("WS:Message received: ", event.data);
+        receiveCallback(event.data.toString())
+    }
+
+    const onClose = (event: WebSocket.CloseEvent) => {
+        console.log("WS:Socket closed normally.")  
+    }
+
+    // Reconnect automatically on error.
+    const onError = (event: WebSocket.ErrorEvent) => {
+        console.log("WS:WebSocket error: ", event.message);
+        console.log("Attempting reconnect in 1s.")
+        setTimeout(createSocket, 1000);
+    }
+
+    const createSocket = (socketUrl: string) => {
+
+        socket = new WebSocket(socketUrl)
+
+        // Add listeners.
+        socket.addEventListener("open", onOpen);
+        socket.addEventListener("message", onServerMessage);
+        socket.addEventListener("close", onClose);
+        socket.addEventListener("error", onError);
+
+    }
+
+    const close = () => {
+        if (socket) {
+            socket.close();
+            socket = null;
+        }
+    }
+
+    const checkConnection = () => {
+        return true;
+    }
+
+    return {
+        createSocket,
+        send,
+        close,
+        checkConnection
+    };
+
+}
diff --git a/src/init.ts b/src/init.ts
new file mode 100644 (file)
index 0000000..3d0b721
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Entry point for Crimata electron app.
+ * "Look on my Works, ye Mighty, and despair!"
+ */
+
+"use strict";
+
+import { app, protocol } from "electron";
+import createWindow from "./window";
+import main from "./main";
+
+require('dotenv').config();
+
+console.log('Starting Crimata electron app.');
+
+// Scheme must be registered before the app is ready
+protocol.registerSchemesAsPrivileged([
+    { scheme: "app", privileges: { secure: true, standard: true } }
+]);
+
+const isDev = require('electron-is-dev');
+
+/* Start main process on ready */
+app.on("ready", async () => {
+    await main();
+});
+
+// Must keep to ensure app doesn't quit on close.
+app.on("before-quit", async () => {
+});
+
+// Must keep to ensure app doesn't quit on close.
+app.on("window-all-closed", () => {
+});
+
+// When user clicks app icon (re-open)
+app.on("activate", () => {
+    if (!win) createWindow();
+});
+
+// Exit cleanly on request from parent process in development mode.
+if (isDev) {
+    process.on("SIGTERM", () => {
+        app.quit();
+    });
+}
\ No newline at end of file
similarity index 84%
rename from src/background/ipc/account.ts
rename to src/ipc/account.ts
index 589850918e77e263388a4a566082eb89286cf389..38600195488b15402097d10b291eabfc95e61b5b 100644 (file)
@@ -1,11 +1,9 @@
 
 "use strict";
 
-import { Profile } from "@/types";
-import { submit, fetchProfile, logout } from "@/api/account";
+import { submit, fetchProfile, logout } from "../api/account";
 import { ipcMain, IpcMainInvokeEvent } from "electron";
-import { store } from "@/background/store";
-import { endSession } from "@/background/session";
+import { store } from "@/composables/store";
 
 
 const parseAuthRes = (authRes: any) => {
@@ -18,7 +16,13 @@ const parseAuthRes = (authRes: any) => {
 };
 
 
-const onProfile = async (
+
+
+
+/**
+ * Get user profile from store and try to login with it.
+ */
+const onTokenLogin = async (
     _event: IpcMainInvokeEvent,
     _payload: null
 ): Promise<Profile | Error> => (
@@ -33,12 +37,11 @@ const onProfile = async (
         // authenticate and fetch profile
         try {
 
-            const res = await fetchProfile(
-                crimataId,
-                token
-            );
-
+            // attempt login with email token
+            const res = await fetchProfile(crimataId, token);
             const parsed = parseAuthRes(res);
+
+            // return profile to renderer
             resolve(parsed.profile);
 
         } catch(e) {
@@ -60,6 +63,8 @@ const onLogin = async (
 
         if ( account.password && account.email ) {
             try {
+
+                // attempt login with email password
                 const res = await submit(account.email, account.password);
                 const parsed = parseAuthRes(res);
 
@@ -67,6 +72,8 @@ const onLogin = async (
                 store.set('key', parsed.token);
                 store.set('crimataId', parsed.profile.crimataId);
 
+                // init session
+
                 // return profile to renderer
                 resolve(parsed.profile);
 
@@ -95,7 +102,7 @@ const onLogout = async (
             store.delete('crimataId');
 
             // TODO: kill crimata platform session
-            endSession();
+            // endSession();
 
             resolve();
         } catch(e) {
diff --git a/src/ipc/audio.ts b/src/ipc/audio.ts
new file mode 100644 (file)
index 0000000..31c16ce
--- /dev/null
@@ -0,0 +1,34 @@
+
+"use strict";
+
+import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
+import { collect, flush } from "@/composbales/audio";
+
+// handle the new audio data
+const onAudioChunk = (
+    _e: IpcMainEvent,
+    payload: ArrayBuffer
+) => {
+    console.log('[IPC]: audio-buffer');
+    collect(payload);
+}
+
+// Returns recorded audio to frontend and sets record to false.
+const onGetAudio = async (
+    _event: IpcMainInvokeEvent,
+    _payload: null
+): Promise<Error | ArrayBuffer[]> => {
+    console.log('[IPC]: stop-recording');
+    return await flush()
+};
+
+
+export default function useAudioListeners(): void {
+
+    ipcMain.removeAllListeners("audio-chunk");
+    ipcMain.on("audio-chunk", onAudioChunk);
+
+    ipcMain.removeHandler("get-audio");
+    ipcMain.handle("get-audio", onGetAudio);
+
+}
similarity index 74%
rename from src/background/ipc/index.ts
rename to src/ipc/index.ts
index 933bda1d7deef014748fad953ff4dd10d699658d..9152ef2cb1611204a222db632c39e77e5ea760db 100644 (file)
@@ -3,7 +3,7 @@
 
 import useAccountListeners from "./account";
 import useSessionListeners from "./session";
-import useAudioListeners from "./audio";
+// import useAudioListeners from "./audio";
 
 
 export default function useIpc(): void {
@@ -12,6 +12,6 @@ export default function useIpc(): void {
 
     useSessionListeners();
 
-    useAudioListeners();
+    // useAudioListeners();
 
 }
diff --git a/src/ipc/session.ts b/src/ipc/session.ts
new file mode 100644 (file)
index 0000000..fa50aa1
--- /dev/null
@@ -0,0 +1,20 @@
+
+"use strict";
+
+import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
+import { sendMessage } from '@/session';
+
+// Handle messages from window/client.
+function onSendMessage(_event: IpcMainEvent, payload: Message): void {
+    sendMessage(payload);
+}
+
+// Login attempt, returns success or not.
+function onLogin(_event: IpcMainEvent, payload: LoginPayload) => {
+    authenticate(payload.email, payload.password);
+}
+
+export default function useSessionListeners(): void {
+    ipcMain.removeAllListeners("client-message");
+    ipcMain.on("client-message", onSendMessage);
+}
\ No newline at end of file
diff --git a/src/ipcRend/account.ts b/src/ipcRend/account.ts
deleted file mode 100644 (file)
index 9300aa6..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-
-import { useIpc } from "@/modules/ipc";
-import { Profile } from "@/types";
-
-const { invoke } = useIpc();
-
-interface LoginPayload {
-    email: string;
-    password: string;
-}
-
-
-export const invokeProfile = async (): Promise<Profile | Error> => (
-    await invoke('user-profile', null)
-);
-
-
-export const invokeLogin = async (
-    payload: LoginPayload
-): Promise<Profile | Error> => (
-    await invoke('user-login', JSON.stringify(payload))
-);
-
-
-export const invokeLogout = async (): Promise<void> => (
-    await invoke("user-logout", null)
-);
diff --git a/src/ipcRend/audio.ts b/src/ipcRend/audio.ts
deleted file mode 100644 (file)
index 6f25d29..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-
-import { useIpc } from "@/modules/ipc";
-
-
-const { post, invoke } = useIpc();
-
-
-export const postStartRecord = (): void => (
-    post("start-recording", null)
-);
-
-
-export const invokeStopRecord = async (): Promise<string | Error> => (
-    await invoke("stop-recording", null)
-);
diff --git a/src/ipcRend/session.ts b/src/ipcRend/session.ts
deleted file mode 100644 (file)
index 2eb4405..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-
-import { useIpc } from "@/modules/ipc";
-
-import { ClientMessage } from "@/types";
-
-const { post } = useIpc();
-
-
-export const postMount = (): void => (
-    post("app-mounted", null)
-);
-
-
-export const postInitSession = (cid: string): void => (
-    post("init-session", cid)
-);
-
-
-export const postMessage = (payload: ClientMessage): void => (
-    post('client-message', payload)
-);
-
-
index 68cc98ee62814c73d39583b35c200f527e0f98dc..744643db11d4cfc46d856a81038b8a305b5f19b0 100644 (file)
@@ -1,16 +1,72 @@
-// src/main.ts
+/**
+ * Where the background logic really begins, gets called by app.onReady().
+ * 
+ * Handles authentication. If profile is set, we launch a session, which consis
+ * of opening a connection with the platform, initializing the audio streams.
+ * 
+ * The session is primarily an interface between the frontend and the platform,
+ * relaying messages from one to the other.
+ * 
+ */
 
-import App from "./App.vue";
+import { fetchAccount, submit } from "@/api/account";
+import { launchSession, endSession } from "@/session";
+import useIpc from "@/ipc/index";
+import store from "@/composables/store";
 
-import mitt from "mitt";
-import { createApp } from "vue";
-require('dotenv').config()
+/* user profile, signals whether user is logged in */
+let auth: Profile | null = null;
 
+/* authenticate the user */
+export async function authenticate(email: string, password: string) {
 
-// Handle events.
-const emitter = mitt();
+    /* attempt normal login */
+    try {
+        auth = await submit(email, password);
+    } catch (e) {
+        console.log(e);
+    }
 
-const app = createApp(App)
+    /* launch if profile */
+    if (auth) {
+        launchSession(auth);
+    }
 
-app.provide("mitt", emitter)
-app.mount("#app");
+}
+
+/* logout the user, end the session */
+export function deauthenticate() {
+
+    /* set profile back to null */
+    auth = null;
+
+    /* terminate the session */
+    endSession();
+
+}
+
+export default async function main() {
+
+    /* launch browser window */
+    // await createWindow();
+
+    /* attempt key-based authentication with business api */
+    const token = store.get('key', null);
+    const crimataId = store.get('crimataId', null);
+
+    try {
+        const res = await fetchAccount(crimataId, token);
+        auth = parseAuthRes(res);
+    } catch (e) {
+        console.log('[MAIN]', e);
+    }
+
+    /* connect to Crimata, or listen for manual login req */
+    if (auth) {
+        launchSession(auth);
+    }
+
+    /* initiate controls for frontend to use when needed */
+    useIpc();
+
+}
\ No newline at end of file
diff --git a/src/modules/message.ts b/src/modules/message.ts
deleted file mode 100644 (file)
index 71beb70..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-import { 
-  RenderMessage, 
-  ClientMessage, 
-  ClientRequest, 
-  AuthRequest, 
-  LogoutRequest 
-} from "@/types";
-
-import { v4 as uuidv4 } from 'uuid';
-
-function getTimeStamp(): number {  
-  const currentdate = new Date();
-  return currentdate.getTime();
-}
-
-// Create a RenderMessage object.
-export const renderMessage = (text: boolean | string, audio: boolean | string, context: string, modifier: string): RenderMessage => (
-  {
-    content: {
-        text: text,
-        audio: audio
-    },
-    context: context,
-    modifier: modifier,
-    time: getTimeStamp(),
-    uid: uuidv4(),
-    isChild: "none",
-    seen: false,
-    newMessage: false
-  }
-)
-
-export const clientMessage = (text: string, audio: string | boolean, uid: string): ClientMessage => (
-  {
-    audio,
-    text,
-    uid
-  }
-)
-
-export const clientRequest = (intent: string, params: object, epic: string | boolean): ClientRequest => (
-  {
-    intent: intent,
-    params: params,
-    epic: epic,
-    confidence: 1.0
-  }
-)
-
-export const authRequest = (key: boolean | string, usr: boolean | string, pwd: boolean | string): AuthRequest => (
-  {
-    key,
-    usr,
-    pwd
-  }
-)
-
-export const logoutRequest = (): LogoutRequest => (
-  {
-    logout: true
-  }
-)
\ No newline at end of file
diff --git a/src/modules/messages.ts b/src/modules/messages.ts
deleted file mode 100644 (file)
index 1d2c4b4..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-import { ref } from 'vue';
-import useScroll from "@/modules/scroll";
-import { RenderMessage, Annotation } from "@/types";
-
-const messages = ref(new Map());
-
-const addMessage = (message: RenderMessage) => {
-  messages.value.set(message.uid, message)
-}
-
-const updateMessage = (annotation: Annotation) => {
-  const message = messages.value.get(annotation.uid)
-  message.context = annotation.context
-  message.content.text = annotation.text
-}
-
-const loadSavedMessages = () => {
-  const rawData = window.localStorage.getItem("crimata_messages");
-  if (rawData) {
-    const messageData = JSON.parse(rawData)
-    messages.value = new Map(Object.entries(messageData));
-  }
-}
-
-const saveMessages = () => {
-  const messageData = Object.fromEntries(messages.value);
-  window.localStorage.setItem("crimata_messages", JSON.stringify(messageData));
-}
-
-const isSimmilar = (messageA: RenderMessage, messageB: RenderMessage) => {
-  if ((Math.abs(messageA.time - messageB.time) < 20000) && (messageA.modifier == messageB.modifier) && (messageA.context == messageB.context)) {
-    return true
-  }
-  return false
-}
-
-const updateGrouping = () => {
-  console.log("updating grouping")
-  const refs = Array.from(messages.value.keys())
-
-  // Get the last three messages.
-  const first = messages.value.get(refs[refs.length - 1])
-  const second = messages.value.get(refs[refs.length - 2])
-  const third = messages.value.get(refs[refs.length - 3])
-
-  // If messages are simmilar, update the classes.
-  if ((first) && (second)) {
-    if (isSimmilar(first, second)) {
-      first.isChild = "last" // i.e. last in group.
-      second.isChild = "first"
-
-      if (third) {
-        if ((third.isChild == "first") || (third.isChild == "middle")) {
-          second.isChild = "middle"
-        }
-      }
-    }
-  }
-}
-
-
-export default function useMessages() {
-
-  // Scroll controller.
-  const { setScroll, updateScrollRef, adjustScroll } = useScroll("messenger");
-
-  // Main function for updating the message view.
-  const updateMessageView = (message: RenderMessage | Annotation) => {
-
-    // Step 1: See if user is scrolled down.
-    updateScrollRef()
-
-    // Step 2: Add the new content to the view.
-    if ("content" in message) {
-      addMessage(message)
-    } else {
-      updateMessage(message)
-    }
-
-    // Step 3: Pop off oldest message (if > 200).
-    if (messages.value.size >= 200) {
-      const oldest = Array.from(messages.value.keys()).shift();
-      messages.value.delete(oldest);
-    }
-
-    // Step 4: Update grouping.
-    updateGrouping()
-
-    // Setp 5: Scroll the view (if scrolled down).
-    setTimeout(adjustScroll, 20);
-
-    // Step 6: Save the view data.
-    saveMessages()
-
-  }
-
-  // Seed message view with message history.
-  const prepMessageView = (newMessages: RenderMessage[]) => {
-    console.log("MSGR:Prepping messenger view.")
-
-    // Load and render saved messages and immediately scroll to bottom.
-    loadSavedMessages()
-    setTimeout(setScroll.bind(false), 10);
-
-    // Render new messages, then wait 1s to scroll.
-    if (newMessages.length) {
-
-      console.log("MSGR:Adding new messages")
-
-      newMessages.forEach(message => {
-        message.newMessage = true;
-        addMessage(message)
-      })
-
-      setTimeout(setScroll.bind(true), 1000);
-
-    }
-  }
-
-  return {
-    messages,
-    prepMessageView,
-    updateMessageView
-  }
-}
\ No newline at end of file
diff --git a/src/modules/mitt.ts b/src/modules/mitt.ts
deleted file mode 100644 (file)
index 4f3f448..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-import { inject } from "vue";
-import { Emitter } from "mitt";
-
-// Frontend emitter
-
-type Mitt = Emitter;
-
-let emitter: Mitt;
-
-export default function useMitt() {
-
-    const emitterInject: Mitt | undefined = inject("mitt");
-    if (emitterInject) {
-      emitter = emitterInject;
-    }
-    return {
-        emitter
-    }
-}
\ No newline at end of file
diff --git a/src/modules/scroll.ts b/src/modules/scroll.ts
deleted file mode 100644 (file)
index 58370f7..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-export default function useScroll(element: string) {
-
-  let isScrolledToBottom: boolean;
-
-  // Set the initial scroll position.
-  const setScroll = (smooth: boolean) => {
-    const view = document.getElementById(element)
-
-    if (view) {
-      view.scrollTo({
-        top: view.scrollHeight - view.clientHeight,
-        behavior: (smooth) ? 'smooth' : 'auto' 
-      });
-    }
-  }
-
-  // Update isScrolledToBottom
-  const updateScrollRef = () => {
-    const view = document.getElementById(element)
-
-    if (view) {
-      isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1
-    }
-
-  }
-
-  // Adjust scroll after we add content to the messenger.
-  const adjustScroll = () => {
-    const view = document.getElementById(element)
-
-    if (view) {
-      if (isScrolledToBottom) {
-        view.scrollTo({
-          top: view.scrollHeight - view.clientHeight,
-          behavior: 'smooth'
-        });
-      }
-    }
-
-  }
-
-  return {
-    updateScrollRef,
-    adjustScroll, 
-    setScroll,
-  }
-  
-}
diff --git a/src/render/App.vue b/src/render/App.vue
new file mode 100644 (file)
index 0000000..e5c477d
--- /dev/null
@@ -0,0 +1,86 @@
+<template>
+  <!-- Only render when profile has been set -->
+  <main id="app" v-if="typeof profile !== 'undefined'">
+
+    <Header />
+
+    <!-- Main Components -->
+    <Messenger
+      v-if="profile"
+      :profile="profile"
+    />
+    <Login v-else />
+
+  </main>
+
+  <!-- Show spash screen if ready is false -->
+  <Splash v-else />
+
+</template>
+
+<script lang="ts">
+
+import { defineComponent, onMounted, onUnmounted, Ref } from "vue";
+import { invokeProfile } from "@/ipc/account";
+
+import Splash from "@/render/components/splash.vue";
+import Messenger from "@/render/components/messenger.vue";
+import Login from "@/render/components/login.vue";
+import Header from "@/render/components/header.vue";
+
+export default defineComponent({
+
+  components: {
+    Splash,
+    Messenger,
+    Login,
+    Header
+  },
+
+  setup() {
+
+    const state: Ref;
+
+    onMounted(async () => {
+      console.log("[APP]:mounted.");
+
+      /* listen for auth related messages */
+      window.addEventListener("update-state", (event: any) => {
+        state.value = event.data;
+      });
+
+    });
+
+    onUnmounted(() => {
+      window.ipcRenderer.removeAllListeners("update-state");
+    });
+
+    return {
+        profile
+    }
+  }
+})
+
+</script>
+
+<style lang="scss">
+
+html, body {
+  margin: 0;
+  padding: 0;
+  // Background color set in window.ts
+}
+
+#app {
+  font-family: "SF Pro Text";
+
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  height: 100vh;
+  width: 100vw;
+
+  border-radius: 15px;
+ }
+
+</style>
diff --git a/src/render/components/bubble.vue b/src/render/components/bubble.vue
new file mode 100644 (file)
index 0000000..6a62e62
--- /dev/null
@@ -0,0 +1,330 @@
+<template>
+  <div :class="`${type}-message`">
+
+    <!-- message bubble -->
+    <div :class="`${type}-${child}-bubble`">
+      <div class="notification-dot"/>
+      <div class="error-dot"/>
+      {{ text }}
+    </div>
+
+    <!-- message context -->
+    <span class="context">
+      <div :class="`${type}-icon`">
+      {{ context }}
+    </span>
+
+  </div>
+</template>
+
+<script lang='ts'>
+
+ import { defineComponent, ref, onMounted } from 'vue';
+
+ export default defineComponent({
+  name: "MessageItem",
+
+  props: ["text", "context", "type", "position"],
+
+  setup() {
+
+    /* initialize seen state */
+    const seen = ref(() => {
+      if (document.visibilityState === "visible") {
+        return true;
+      } else return false;
+    });
+
+    onMounted(() => {
+
+      if (!seen) 
+      document.addEventListener("visibilitychange", () => seen = true);
+
+    });
+
+    return {
+      seen
+    };
+
+  }
+
+ })
+
+</script>
+
+<style lang="scss" scoped>
+
+  .message {
+    width: 100vw;
+    display: flex;
+    flex-direction: column;
+    padding-top: 9px;
+    padding-bottom: 9px;
+  }
+
+  .message:first-child {
+    margin-top: 55px;
+  }
+
+  .message:last-child {
+    margin-bottom: 18px;
+  }
+
+  .sf-message {
+    @extend .message;
+    justify-content: flex-end;
+  }
+
+  .ai-message, .fr-message {
+    @extend .message;
+    justify-content: flex-start;
+  }
+
+  .bubble {
+    position: relative;
+    max-width: 66vw;
+    font-family: "SF Pro Text";
+    font-size: 14px;
+    padding: 10px;
+    border-radius: 18px;
+  }
+
+  .sf-bubble {
+    @extend .bubble;
+    background-color: #58c4fd;
+    color: white;
+  }
+
+
+
+
+
+
+
+
+  .firstChildMessage {
+    padding-bottom: 2px;
+  }
+
+  .middleChildMessage {
+    padding-top: 2px;
+    padding-bottom: 2px;
+  }
+
+  .lastChildMessage {
+    padding-top: 2px;
+  }
+
+  #aiMessage {
+    align-items: flex-start;
+  }
+
+  #sfMessage {
+    align-items: flex-end;
+  }
+
+  #frMessage {
+    align-items: flex-start;
+  }
+
+  .messageBox {
+    position: relative;
+
+    display: flex;
+    flex-direction: column;
+
+    // Animate on render.
+    animation-name: appear;
+    animation-duration: 0.25s;
+  }
+
+  #aiMessageBox {
+    margin-left: 20px;
+    align-items: flex-start;
+  }
+
+  #sfMessageBox {
+    margin-right: 20px;
+    align-items: flex-end;
+  }
+
+  #frMessageBox {
+    margin-left: 20px;
+    align-items: flex-start;
+  }
+
+  .bubble {
+    position: relative;
+
+    max-width: 66vw;
+
+    display: flex;
+    flex-direction: column;
+
+    font-family: "SF Pro Text";
+    font-size: 14px;
+
+    padding: 10px;
+
+    border-radius: 18px;
+  }
+
+  @keyframes appear {
+    10% {
+      transform: scale(0.3);
+    }
+    100% {
+      transform: scale(1);
+    }
+  }
+
+  #aiBubble {
+    background-color: white;
+  }
+
+  #sfBubble {
+    background-color: #58c4fd;
+    color: white;
+  }
+
+  #frBubble {
+    background-color: white;
+  }
+
+  .sf-firstChildBubble {
+    border-bottom-right-radius: 9px;
+  }
+
+  .sf-middleChildBubble {
+    border-top-right-radius: 9px;
+    border-bottom-right-radius: 9px;
+  }
+
+  .sf-lastChildBubble {
+    border-top-right-radius: 9px;
+  }
+
+  .ai-firstChildBubble, .fr-firstChildBubble {
+    border-bottom-left-radius: 9px;
+  }
+
+  .ai-middleChildBubble, .fr-middleChildBubble {
+    border-top-left-radius: 9px;
+    border-bottom-left-radius: 9px;
+  }
+
+  .ai-lastChildBubble, .fr-lastChildBubble {
+    border-top-left-radius: 9px;
+  }
+
+  .notify {
+    position: absolute;
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    background-color: #58D9FF;
+    top: -5px;
+    left: -5px;
+    border: 2px solid #EBEBEB;
+    transform: scale(0);
+
+    animation-name: notify-anim;
+    animation-duration: 5s;
+  }
+
+  @keyframes notify-anim {
+    0%, 90% {
+      transform: scale(1);
+    }
+    100% {
+      transform: scale(0);
+    }
+  }
+
+  .context {
+    position: relative;
+
+    display: flex;
+    align-items: center;
+
+    font-family: "SF Compact Display";
+    font-size: 12px;
+    font-weight: bold;
+    margin-top: 5px;
+  }
+
+  .photo {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    margin-right: 5px;
+
+    width: 30px;
+    height: 30px;
+
+    font-size: 14px;
+
+    background-color: white;
+    border-radius: 15px;
+  }
+
+  // Apply for audio message playback.
+  .playing {
+    animation-name: circle1;
+    animation-duration: 2s;
+    animation-iteration-count: infinite;
+  }
+
+  @keyframes circle1 {
+    0%,
+    100% {
+      box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
+    }
+    25% {
+      box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
+    }
+    50% {
+      box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
+    }
+    75% {
+      box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
+    }
+  }
+
+  .contentLoader {
+    display: flex;
+  }
+
+  .contentLoaderDot {
+    width: 5px;
+    height: 5px;
+
+    margin: 2px;
+    border-radius: 2.5px;
+    background-color: #357CA2;
+  }
+
+  .questionMark {
+    font-weight: 900;
+    color: #357CA2;
+  }
+
+  // .divider {
+  //   width: 100vw;
+  //   display: flex;
+  //   justify-content: center;
+  //   align-items: center;
+
+  //   font-family: "SF Compact Display";
+  //   font-size: 12px;
+  //   font-weight: bold;
+  //   color: #9B9B9B;
+
+  //   margin-bottom: 18px;
+  // }
+
+
+</style>
+
+
diff --git a/src/render/components/controllers/bubble.control.ts b/src/render/components/controllers/bubble.control.ts
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/render/components/controllers/helpers.ts b/src/render/components/controllers/helpers.ts
new file mode 100644 (file)
index 0000000..7509bf0
--- /dev/null
@@ -0,0 +1,102 @@
+import { ref } from "vue";
+import anime from "animejs";
+import { v4 as uuidv4 } from 'uuid';
+
+
+export function animateTextInput () {
+
+    const side = ref("right");
+
+    function show () {
+        const t1 = (side.value === "right") ? 50 : -70;
+        const t2 = (side.value === "right") ? 110 : -130;
+
+        anime({
+            targets: '#textInput',
+            opacity: [0, 1],
+            translateX: [t1, t2],
+            scale: [0.3, 1],
+            duration: 500,
+            easing: 'easeOutExpo',
+        })
+
+    }
+
+    function hide () {
+        const t = (side.value === "right") ? 50 : -80;
+
+        anime({
+            targets: '#textInput',
+            opacity: [1, 0],
+            translateX: t,
+            scale: 0.3,
+            duration: 500,
+            easing: 'easeOutExpo',
+        })
+
+    }
+
+    function switchSide () {
+        const t = (side.value === "right") ? -130 : 110;
+
+        anime({
+            targets: '#textInput',
+            translateX: t,
+            duration: 500,
+            easing: 'easeOutExpo',
+        })
+
+    }
+
+    return {
+        side,
+        show,
+        hide,
+        switchSide
+    };
+
+}
+
+export function animateAudioInput () {
+
+    function show () {
+        anime({
+            targets: '#recIcon',
+            opacity: [0, 0.75],
+            scale: [0.0, 1],
+            duration: 250,
+            easing: 'linear',
+        })
+    }
+
+    function hide () {
+        anime({
+            targets: '#recIcon',
+            opacity: [0.75, 0],
+            scale: [1, 0],
+            duration: 250,
+            easing: 'linear',
+        })
+    }
+
+    return {
+        show,
+        hide
+    };
+
+}
+
+
+export function newMessage ({
+    text=false,
+    audio=false,
+    context=false,
+    uid=uuidv4()
+}): Message {
+    return {
+        text: text,
+        audio: audio,
+        context: context,
+        uid: uid
+    };
+}
diff --git a/src/render/components/controllers/inputItem.control.audio.ts b/src/render/components/controllers/inputItem.control.audio.ts
new file mode 100644 (file)
index 0000000..b699405
--- /dev/null
@@ -0,0 +1,79 @@
+import anime from "animejs";
+import { useIpc } from '@/modules/ipc';
+import { onMounted, onUnmounted, ref, Ref } from "vue";
+import { postMessage } from "@/ipc/session";
+import { newMessage, animateAudioInput } from "./helpers";
+import { invokeStopRecord, postAudioChunk } from "@/ipc/audio";
+
+export default function useAudioInputController (typing: Ref) {
+
+    const recording = ref(false);
+    let mediaRecorder: MediaRecorder;
+
+    const { show, hide } = animateAudioInput();
+
+    // initialize audio
+    const conf = {audio: true, video: false}
+    navigator.mediaDevices.getUserMedia(conf).then((stream: MediaStream) => {
+
+        const options = {mimeType: 'audio/webm'};
+        mediaRecorder = new MediaRecorder(stream, options);
+
+        // post any mew audio to backend
+        mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => {
+            e.data.arrayBuffer().then((buff: ArrayBuffer) => {
+                postAudioChunk(buff);
+            });
+        });
+
+        // get audio and post new message to backend
+        mediaRecorder.addEventListener('stop', (_e: Event) => {
+            invokeStopRecord().then((audio: ArrayBuffer[] | Error) => {
+                console.log(audio);
+                // postMessage(newMessage({audio: audio}));
+            });
+        });
+
+    });
+
+    // start recording on space bar
+    const record = () => {
+        console.log("INPT:Capturing audio...")
+        mediaRecorder.start();
+        recording.value = true;
+        show()
+    }
+
+    // stop recording and send on release
+    const stop = () => {
+        console.log("INPT:Stopping record.")
+        mediaRecorder.stop();
+        recording.value = false;
+        hide();
+    }
+
+    const onKeyDown = (e: KeyboardEvent) => {
+        if (e.keyCode == 32 && !typing.value) record();
+    }
+
+    const onKeyUp = (e: KeyboardEvent) => {
+        if (e.keyCode == 32 && recording.value) stop();
+    }
+
+    //-----------------------------------------------------------
+
+    onMounted(() => {
+        window.addEventListener("keydown", onKeyDown);
+        window.addEventListener("keyup", onKeyUp);
+    });
+
+    onUnmounted(() => {
+        window.removeEventListener("keydown", onKeyDown);
+        window.removeEventListener("keyup", onKeyUp);
+    })
+
+    return {
+        recording
+    }
+
+}
similarity index 53%
rename from src/components/controllers/textCtrl.ts
rename to src/render/components/controllers/inputItem.control.text.ts
index ced8aaf37019c00212298614c395c2b7524d6e29..21ada72780ea211b0f7dbbe4ca57566a1569cc25 100644 (file)
@@ -1,72 +1,20 @@
-import anime from "animejs";
-import useMitt from "@/modules/mitt";
-import { useIpc } from '@/modules/ipc';
 import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
-import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
-import { clientMessage, renderMessage } from '@/modules/message';
-import { postMessage } from "@/ipcRend/session";
-
-
-//---Animations-----------------------------------------------
-
-let side = "right"; // Side of parent we are on.
-
-function showTextInput () {
-    const t1 = (side === "right") ? 50 : -70;
-    const t2 = (side === "right") ? 110 : -130;
-
-    anime({
-        targets: '#textInput',
-        opacity: [0, 1],
-        translateX: [t1, t2],
-        scale: [0.3, 1],
-        duration: 500,
-        easing: 'easeOutExpo',
-    })
-
-}
-
-function hideTextInput() {
-    const t = (side === "right") ? 50 : -80;
-
-    anime({
-        targets: '#textInput',
-        opacity: [1, 0],
-        translateX: t,
-        scale: 0.3,
-        duration: 500,
-        easing: 'easeOutExpo',
-    })
-
-}
-
-function switchSide(currentSide: string) {
-    const t = (currentSide === "right") ? -130 : 110;
-
-    anime({
-        targets: '#textInput',
-        translateX: t,
-        duration: 500,
-        easing: 'easeOutExpo',
-    })
-
-}
-
-//------------------------------------------------------------
+import { postMessage } from "@/ipc/session";
+import { newMessage, animateTextInput } from "./helpers";
 
 
 export default function useTextInputController(elementX: Ref) {
+    
     let textInput: HTMLInputElement | null;
 
-    const { post } = useIpc();
-    const { emitter } = useMitt();
+    const { side, show, hide, switchSide } = animateTextInput();
 
     let firstKey = true;
     const typing = ref(false);
 
     // Prep inputItem for typing.
     const prepInput = () => {
-        showTextInput()
+        show()
         typing.value = true
     }
 
@@ -78,7 +26,7 @@ export default function useTextInputController(elementX: Ref) {
             textInput.blur();
         }
 
-        hideTextInput()
+        hide()
         firstKey = true;
         typing.value = false;
     }
@@ -87,28 +35,20 @@ export default function useTextInputController(elementX: Ref) {
     const sendMessage = () => {
         if (textInput) {
 
-            // Create the message.
-            const message = renderMessage(
-                textInput.value,
-                false,
-                "",
-                "sf"
-            )
-
-            emitter.emit("self-message", message);
-
             // Send it to the backend for processing.
-            const clientM = clientMessage(textInput.value, false, message.uid);
-            postMessage(clientM);
+            const message = newMessage({
+                text: textInput.value
+            });
+
+            postMessage(message);
 
             clearInput()
         }
     }
 
     // Keys that are capable of opening the text input (numbers and letters).
-    const hotKeyRange = keyboardNameMap.slice(47, 91)
-    const isHotKey = (key: string) => {
-        if (hotKeyRange.includes(key)) {
+    const isHotKey = (key: number) => {
+        if (key >= 47 && key <= 91) { // a letter
             return true
         }
     }
@@ -116,7 +56,7 @@ export default function useTextInputController(elementX: Ref) {
     //---Callbacks-----------------------------------------------
 
     const onKeyDown = (e: KeyboardEvent) => {
-        const key = keyboardNameMap[e.keyCode]
+        const key = e.keyCode;
 
         if (textInput) {
 
@@ -133,18 +73,18 @@ export default function useTextInputController(elementX: Ref) {
             textInput.focus();
 
             // Close input when no text or on ESC.
-            if ((textInput.value == "") && (!firstKey) && (key === "BACK_SPACE")) {
+            if ((textInput.value == "") && (!firstKey) && (key === 8)) { // backspace
                 clearInput()
                 return
             }
 
-            if (key === "ESCAPE") {
+            if (key === 27) { // escape
                 clearInput()
                 return
             }
 
             // Close and send on enter.
-            if (key === "ENTER") {
+            if (key === 13) {
                 if (textInput.value) {
                     sendMessage()
                     return
@@ -162,17 +102,17 @@ export default function useTextInputController(elementX: Ref) {
         const winW = window.innerWidth
 
         // Logic depends on the side we are on.
-        if (side === "right") {
+        if (side.value === "right") {
             if (winW - elementX < 230) {
-                switchSide(side)
-                side = "left"
+                switchSide()
+                side.value = "left"
             }
         }
 
         else {
             if (winW - elementX > 230) {
-                switchSide(side)
-                side = "right"
+                switchSide()
+                side.value = "right"
             }
         }
 
diff --git a/src/render/components/controllers/messenger.control.ts b/src/render/components/controllers/messenger.control.ts
new file mode 100644 (file)
index 0000000..63f15ee
--- /dev/null
@@ -0,0 +1,143 @@
+import { ref } from 'vue';
+import invokeSavedMessages from "@/render/ipc";
+import useScroll from "@/render/composables/scroll";
+
+const messages = ref(new Map());
+
+function getTimeStamp(): number {  
+  const currentdate = new Date();
+  return currentdate.getTime();
+}
+
+const newViewMessage = (message: Message): ViewMessage => {
+  return {
+    text: message.text,
+    context: message.context,
+    audio: message.audio,
+    from: message.from,
+    uid: message.uid,
+    time: getTimeStamp(),
+    isChild: "none",
+    seen: false,
+    newMessage: false
+  };
+}
+
+const addMessage = (message: Message, newMessage=false) => {
+  const viewMessage = newViewMessage(message);
+  if (newMessage) viewMessage.newMessage = true;
+  messages.value.set(viewMessage.uid, viewMessage);
+}
+
+const updateMessage = (message: Message) => {
+  const viewMessage = messages.value.get(message.uid);
+  viewMessage.context = message.context;
+  viewMessage.text = message.text;
+}
+
+const loadSavedMessages = async () => {
+  const messageData = await invokeSavedMessages();
+  messages.value = new Map(Object.entries(messageData));
+}
+
+const saveMessages = () => {
+  const messageData = Object.fromEntries(messages.value);
+  // must save to json.
+}
+
+const pruneMessages = (limit=200) => {
+  if (messages.value.size >= limit) {
+    const oldest = Array.from(messages.value.keys()).shift();
+    messages.value.delete(oldest);
+  }
+}
+
+const updateGrouping = () => {
+
+  const isSimmilar = (messageA: ViewMessage, messageB: ViewMessage) => {
+    if ((Math.abs(messageA.time - messageB.time) < 20000) && (messageA.from == messageB.from) && (messageA.context == messageB.context)) {
+      return true
+    }
+    return false
+  }
+
+  const refs = Array.from(messages.value.keys())
+
+  // Get the last three messages.
+  const first = messages.value.get(refs[refs.length - 1])
+  const second = messages.value.get(refs[refs.length - 2])
+  const third = messages.value.get(refs[refs.length - 3])
+
+  // If messages are simmilar, update the classes.
+  if ((first) && (second)) {
+    if (isSimmilar(first, second)) {
+      first.isChild = "last" // i.e. last in group.
+      second.isChild = "first"
+
+      if (third) {
+        if ((third.isChild == "first") || (third.isChild == "middle")) {
+          second.isChild = "middle"
+        }
+      }
+    }
+  }
+}
+
+export default function useMessages() {
+
+  const { updateScrollRef, adjustScroll } = useScroll("messenger");
+
+  /* Given new message object, update the view accordingly */
+  const updateMessageView = (newMessages: Message[]) => {
+
+    const bottom = updateScrollRef(); // see if the user is scrolled down
+
+    // take each message and apply view
+    newMessages.forEach((message: Message) => {
+
+      // add or update message depending
+      if (messages.value.has(message.uid)) {
+        updateMessage(message);
+      } else addMessage(message);
+
+      pruneMessages(); // pop off old messages from view
+      updateGrouping(); // group like message together
+
+      if (bottom) adjustScroll(); // only scroll if user was at bottom
+      
+      saveMessages();
+
+    });
+
+  }
+
+  return {
+    messages,
+    updateMessageView,
+    loadSavedMessages
+  }
+
+}
+
+
+  // // Seed message view with message history.
+  // const prepMessageView = async (newMessages: Message[]) => {
+  //   console.log("MSGR:Prepping messenger view.")
+
+  //   // Load and render saved messages and immediately scroll to bottom.
+  //   await loadSavedMessages();
+  //   setTimeout(setScroll.bind(false), 10);
+
+  //   // Render new messages, then wait 1s to scroll.
+  //   if (newMessages.length) {
+
+  //     console.log("MSGR:Adding new messages")
+
+  //     newMessages.forEach(message => {
+  //       addMessage(message, true);
+  //     })
+
+  //     setTimeout(setScroll.bind(true), 1000);
+
+  //   }
+  // }
\ No newline at end of file
diff --git a/src/render/components/header.vue b/src/render/components/header.vue
new file mode 100644 (file)
index 0000000..f0afc8a
--- /dev/null
@@ -0,0 +1,81 @@
+<template>
+    <button id="titlebar" />
+    <span id="menu">
+      <button
+        class="menuButton exitButton"
+        @click.prevent="postNavBarExit"
+      />
+      <button
+        class="menuButton minimizeButton"
+        @click.prevent="postNavBarMin"
+      />
+    </span>
+</template>
+
+<script lang="ts">
+
+    import { postNavBarExit, postNavBarMin } from "@/render/ipc";
+    
+    setup() {
+        return {
+            postNavBarExit,
+            postNavBarMin
+        }
+    }
+
+</script>
+
+<style lang="scss">
+#titlebar {
+  position: fixed;
+
+  width: 100vw;
+  height: 54px;
+
+  opacity: 0.75;
+
+  background-color: #EBEBEB;
+
+  -webkit-app-region: drag;
+
+  border: none;
+  outline: none;
+
+  z-index: 1;
+
+}
+
+#menu {
+  position: fixed;
+  margin-left: 20px;
+  margin-top: 20px;
+
+  z-index: 2;
+
+}
+
+.menuButton {
+  border: none;
+  outline:none;
+  min-width: 14px;
+  min-height: 14px;
+  border-radius: 7px;
+}
+
+.exitButton {
+  background-color: #FF6157;
+}
+
+.exitButton:active {
+  background: #c14645;
+}
+
+.minimizeButton {
+  background-color: #FFC12F;
+  margin-left: 8px;
+}
+
+.minimizeButton:active {
+  background-color: #c08e38;
+}
+</style>
\ No newline at end of file
similarity index 87%
rename from src/components/inputItem.vue
rename to src/render/components/inputItem.vue
index 861d7c135e35467865935fb466d12bf20a185267..30978b3a5e954c32e7a94f750de22fa066ba82bc 100644 (file)
     <span v-if="recording" class="pause"></span>
 
     <!-- Show text input on key-down -->
-    <TextInput />
+    <input 
+      id="textInput" 
+      type="text" 
+    />
 
     <!-- Show suggestions menu on click -->
     <!-- <Menu /> -->
@@ -29,9 +32,9 @@ import draggify from "@/modules/draggify";
 import TextInput from "@/components/textInput.vue";
 
 import useTextInputController from 
-  "@/components/controllers/textCtrl";
+  "@/components/controllers/inputItem.control.audio";
 import useAudioInputController from 
-  "@/components/controllers/audioCtrl";
+  "@/components/controllers/inputItem.control.text";
 
 export default defineComponent({
   name: "InputItem",
@@ -191,4 +194,32 @@ export default defineComponent({
   }
 }
 
+#textInput {
+  position: absolute;
+
+  opacity: 0;
+
+  min-width: 150px;
+  height: 16px;
+
+  border-radius: 18px;
+
+  padding: 10px;
+  margin-right: 10px;
+  margin-left: 10px;
+
+  outline: none;
+  border: none;
+  pointer-events: none;
+
+  background-color: white;
+
+  z-index: -1;
+
+  box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
+
+  transform: translateX(50px) scale(0.3);
+
+}
+
 </style>
similarity index 89%
rename from src/components/login.vue
rename to src/render/components/login.vue
index 2cb82f85090702bc9ec794672a88e842e40cd897..5ed0ad0580a192dfc244e7892998c62dad900fe6 100644 (file)
@@ -39,7 +39,6 @@ import { useIpc } from "@/modules/ipc";
 import { authRequest } from '@/modules/message';
 import { useProfile } from '@/modules/auth';
 import { invokeLogin } from "@/ipcRend/account";
-import { Profile } from "@/types";
 import { postInitSession } from "@/ipcRend/session";
 
 export default defineComponent({
@@ -47,8 +46,6 @@ export default defineComponent({
 
   setup() {
 
-    const { profile, setProfile } = useProfile();
-
     const usr = ref("");
     const pwd = ref("");
 
@@ -56,23 +53,17 @@ export default defineComponent({
     const submitForm = async () => {
 
       try {
+
           const profile = await invokeLogin({
               email: usr.value,
               password: pwd.value
-          }) as Profile;
-
-          setProfile(profile);
+          });
 
-      } catch(e) {
-        console.log('Error login in.')
-      } finally{
+          /* emit event to app.vue */
+          window.postMessage(profile);
 
-          if (profile.value.crimataId) {
-              // start session
-              postInitSession(profile.value.crimataId);
-          }
+      } catch (e) console.log(e);
 
-      }
     }
 
     return {
diff --git a/src/render/components/messenger.vue b/src/render/components/messenger.vue
new file mode 100644 (file)
index 0000000..77d54f3
--- /dev/null
@@ -0,0 +1,101 @@
+<template>
+
+  <!-- Position fixed items -->
+  <InputItem :initials="profile.initials"/>
+  <Settings />
+  <div id="recIcon" />
+
+  <!-- List of message bubbles. -->
+  <div id="messenger">
+    <Bubble
+      v-for="message in messages"
+      :text="message.text"
+      :context="message.context"
+      :key="message[0]"
+    />
+  </div>
+
+</template>
+
+<script lang="ts">
+
+import { defineComponent, onMounted, onUnmounted } from "vue";
+import Message from "@/render/components/message.vue";
+import InputItem from "@/render/components/inputItem.vue";
+import Settings from "@/render/components/settings.vue";
+import useMessages from "@/render/composables/messages";
+
+export default defineComponent({
+  name: "Messenger",
+
+  props: ["state"],
+
+  components: {
+    Message,
+    InputItem,
+    Settings
+  },
+
+  setup(props) {
+
+    // Handle messages in view.
+    const { messages, updateMessageView } = useMessages();
+
+    onMounted(() => {
+
+      /* populate the message view with existing messages */
+      updateMessageView(state.savedMessages, state.newMessages);
+
+      /* wait and listen for new messages to come in */
+      window.ipcRenderer.on("new-message", (_e: any, payload: any) => {
+        updateMessageView(payload.message);
+      });
+
+    });
+
+    onUnmounted(() => {
+      window.ipcRenderer.removeAllListeners("new-message");
+    });
+
+    return {
+      messages
+    };
+
+  },
+
+});
+</script>
+
+<style lang="scss" scoped>
+
+#messenger {
+  width: 100vw;
+  height: 100vh;
+  overflow: auto;
+}
+
+#messenger::-webkit-scrollbar {
+  display: none;
+}
+
+#recIcon {
+  position: fixed;
+  right: 0;
+
+  opacity: 0;
+  transform: scale(0.0);
+
+  margin-top: 24px;
+  margin-right: 66px;
+
+
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background-color: #FF3B3B;
+
+  z-index: 2;
+
+}
+
+</style>
similarity index 90%
rename from src/modules/auth.ts
rename to src/render/composables/auth.ts
index 5fb40c1301fbce14252862ee8afaf35b0cdfd23b..dea4bd1c4598098eb66fbd37e82397e8a8f47b6c 100644 (file)
@@ -1,6 +1,4 @@
 import { ref } from "vue";
-import { Profile } from "@/types";
-
 
 const profile = ref();
 
similarity index 91%
rename from src/modules/ipc.ts
rename to src/render/composables/ipc.ts
index 36658de32a77d54fe69e8ec2b1a8b6251f7f71c3..0d067d36954fa32522754182de80ef08e95904f1 100644 (file)
@@ -1,5 +1,5 @@
 
-export const useIpc = () => {
+export default function useIpc () {
     
     const invoke = async (endpoint: string, payload: any) => {
         try {
diff --git a/src/render/composables/scroll.ts b/src/render/composables/scroll.ts
new file mode 100644 (file)
index 0000000..811e7fc
--- /dev/null
@@ -0,0 +1,29 @@
+
+
+export default function useScroll(element: string) {
+
+  let isScrolledToBottom: boolean;
+  const view = document.getElementById(element)
+
+  // Update isScrolledToBottom
+  const updateScrollRef = () => {
+    if (view) isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1;
+    return isScrolledToBottom;
+  }
+
+  // Adjust scroll after we add content to the messenger.
+  const adjustScroll = () => {
+    if (view) {
+      view.scrollTo({
+        top: view.scrollHeight - view.clientHeight,
+        behavior: 'smooth'
+      });
+    }
+  }
+
+  return {
+    updateScrollRef,
+    adjustScroll, 
+  };
+  
+}
\ No newline at end of file
diff --git a/src/render/ipc.ts b/src/render/ipc.ts
new file mode 100644 (file)
index 0000000..a232f7f
--- /dev/null
@@ -0,0 +1,52 @@
+
+import useIpc from "@/render/composables/ipc";
+
+const { post, invoke } = useIpc();
+
+/**
+ * 
+ * Account and auth related endpoints
+ * 
+ */ 
+
+export const invokeProfile = async (): Promise<Profile | Error> => (
+    await invoke('user-profile', null)
+);
+
+export const invokeLogin = async (
+    payload: LoginPayload
+): Promise<Profile | Error> => (
+    await invoke('user-login', JSON.stringify(payload))
+);
+
+export const invokeLogout = async (): Promise<void> => (
+    await invoke("user-logout", null)
+);
+
+/**
+ * 
+ * Audio endpoints
+ * 
+ */
+
+export const postAudioChunk = (chunk: ArrayBuffer): void => (
+    post("audio-chunk", chunk)
+);
+
+export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
+    await invoke("get-audio", null)
+);
+
+/**
+ * 
+ * Crimata Platform (session) endpoints
+ * 
+ */
+
+export const invokeSession = async (cid: string): Promise<Profile | Error> => (
+    await invoke("messenger-init", cid)
+);
+
+export const postMessage = (payload: Message): void => (
+    post('client-message', payload)
+);
\ No newline at end of file
similarity index 100%
rename from src/preload.ts
rename to src/render/preload.ts
similarity index 100%
rename from src/shims-vue.d.ts
rename to src/render/shims-vue.d.ts
diff --git a/src/session.ts b/src/session.ts
new file mode 100644 (file)
index 0000000..0a893d8
--- /dev/null
@@ -0,0 +1,64 @@
+
+
+
+
+import useAudio from "@/audio";
+import { loadState, saveState, emitState } from "@/state";
+
+/* start and stop audio functionality */
+const { initAudio, closeAudio } = useAudio();
+
+/**
+ * Controls for interfacing with the platform.
+ * Takes an onMessage callback which we define below.
+ */
+const { connect, send, close } = usePlatform((message: Message) => {
+
+    /* add the message to the state */
+    addMessage(message);
+
+    /* push the message to the browser */
+    if (win) emit("new-message", message);
+
+});
+
+/* send a message to the platform */ 
+export function sendMessage(message: Message) {
+
+    /* add the message to the state */
+    addMessage(message);
+
+    /* push the message to the browser */
+    if (win) emit("new-message", message);
+
+    /* socket send */
+    send(message);
+
+}
+
+/* launch a new session (the main process for authenticated users) */
+export function launchSession(profile: Profile) {
+
+    /* load any previously saved state for that user */
+    loadState(profile);
+
+    /* connect to the platform */
+    connect(profile);
+
+    /* initialize the audio streams */
+    // initAudio();
+
+    /* finally we can push state to browser */
+    if (win) emitState();
+
+}
+
+export function endSession() {
+
+    closeAudioStreams();
+
+    closeSocket();
+
+    state.clear();
+
+}
\ No newline at end of file
diff --git a/src/state.ts b/src/state.ts
new file mode 100644 (file)
index 0000000..1d6ce02
--- /dev/null
@@ -0,0 +1,29 @@
+const Store = require('electron-store');
+
+/* simple data persistance */
+const store = new Store;
+
+/* state of the session (e.g. profile and messages for now) */
+let state: State | null = null;
+
+/* load saved state in electron store for given user */
+export function loadState(profile: Profile) {
+    state = store.get("state", null);
+}
+
+/* add a message to state.messages */
+export function addMessage(message: Message) {
+    if (state) {
+        state.messages.push(message);
+        saveState();
+    }
+}
+
+export function saveState() {
+    store.set("state", state);
+}
+
+export function emitState() {
+    emit("update-state", state);
+}
+
index 2e35fc5ccd9a2fe9a4295b95bcbaa337e6bcc78d..b437ffad4d126e20446149a6d3ec13c2f93462e0 100644 (file)
@@ -1,76 +1,37 @@
-export interface RenderMessage {
-    content: {
-        text: boolean | string;
-        audio: boolean | string;
-    };
-    context: string;
-    modifier: string;
-    time: number;
-    uid: string;
-    isChild: string;
-    seen: boolean;
-    newMessage: boolean;
-}
-
-export interface ClientMessage {
-    text: string;
+interface Message {
+    text: boolean | string;
+    context: boolean | string;
     audio: boolean | string;
+    type: 1 | 2 | 3;
+    time: number;
     uid: string;
 }
 
-export interface ClientRequest {
-    intent: string;
-    params: object;
-    epic: string | boolean;
-    confidence: number;
-}
-
-export interface SessionState {
-    key: string | boolean;
-    newMessages: RenderMessage[];
+interface ViewMessage extends Message {
+    child: string;
+    seen: boolean;
+    newMessage: boolean;
 }
 
-export interface WindowState {
+interface WindowState {
     width: number;
     height: number;
     x: number | null;
     y: number | null;
 }
 
-export interface AuthRequest {
-    key: boolean | string;
-    usr: boolean | string;
-    pwd: boolean | string;
-}
-
-export interface Profile {
+interface Profile {
     crimataId: string;
     alias: string;
     initials: string;
 }
 
-export interface AuthProtocol {
-    token: null | string;
-    profile: null | Profile;
-    password?: string;
-    email?: string;
-}
-
-export interface LogoutRequest {
-    logout: boolean;
-}
-
-export interface Annotation {
-    text: string;
-    context: string;
-    uid: string;
+interface State {
+    profile: Profile | null;
+    messages
 }
 
-export interface StandardMessage {
-    content: {
-        text: boolean | string;
-        audio: boolean | string;
-    };
-    context: string;
-    modifier: string;
+interface LoginPayload {
+    email: string;
+    password: string;
 }
similarity index 86%
rename from src/background/window.ts
rename to src/window.ts
index 162b3e1481cf6a5779d6a0d1c479692fa29b367e..4c69387e6a195fb67bfc76883a7a43ba9936fae7 100644 (file)
@@ -2,20 +2,39 @@
 
 import { BrowserWindow, ipcMain } from "electron";
 import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
-import { backgroundMitt } from '@/modules/emitter';
-import { RenderMessage, WindowState } from "@/types";
-import { loadWinState, saveToJson } from "./helpers";
+import { backgroundMitt } from './coposables/emitter';
+import { saveToJson } from "./coposables/json";
 import * as path from "path";
 const { autoUpdater } = require('electron-updater');
 
 interface IpcRendererPayload {
   endpoint: string;
-  message: RenderMessage | null;
+  message: Message | null;
 }
 
 let win: BrowserWindow | null;
 let winState: WindowState;
 
+const loadWinState = (fileName: string): WindowState => {
+    let state: WindowState;
+
+    try {
+        state = JSON.parse(fs.readFileSync(configPath + fileName).toString());
+    } 
+
+    catch (error) {
+        state = {
+            width: 600,
+            height: 500,
+            x: null,
+            y: null,
+        }
+    }
+
+    return state
+    
+} 
+
 // Called when a NavBar button is pressed.
 const onNavBar = (_event: any, action: string): void => {
   if (win) {
@@ -78,7 +97,7 @@ const onWindowDismount = (): void => {
 }
 
 // function used by run.ts to create the main window.
-export async function createWindow(): Promise<void> {
+export default async function createWindow(): Promise<void> {
   return new Promise((resolve, _reject) => {
 
     // avoid creating duplicate windows.
index 307539be3ec9f67e1b3725007d37c27b8d095cb8..5a01f4d4b309edb3850b89580977a3e4a8a0806d 100644 (file)
@@ -32,6 +32,7 @@
     ]
   },
   "include": [
+    "**/*.ts",
     "src/*.ts",
     "src/**/*.ts",
     "src/**/*.tsx",