]> Repos - mime-chat/commitdiff
updated protocols
authorAndrew Gundersen <gundersena@xavier.edu>
Sun, 11 Jul 2021 16:54:39 +0000 (11:54 -0500)
committerAndrew Gundersen <gundersena@xavier.edu>
Sun, 11 Jul 2021 16:54:39 +0000 (11:54 -0500)
37 files changed:
build/icon.icns [new file with mode: 0644]
src/account.ts
src/audio.ts [deleted file]
src/auth.ts
src/composables/useMessageCanvas.ts [deleted file]
src/composables/useWebsockets.ts
src/ipc/handlers.ts
src/ipc/listeners.ts
src/main.ts
src/profile.ts [new file with mode: 0644]
src/render/App.vue
src/render/assets/connectLogo.svg [new file with mode: 0644]
src/render/assets/crimataAvatar.svg [new file with mode: 0644]
src/render/assets/settingsIcon.svg [new file with mode: 0644]
src/render/components/bubble.vue
src/render/components/connectionStatus.vue
src/render/components/context.vue
src/render/components/controllers/bubble.control.ts [deleted file]
src/render/components/controllers/helpers.ts
src/render/components/header.vue
src/render/components/inputItem.vue
src/render/components/login.vue
src/render/components/message.vue
src/render/components/messenger.vue
src/render/components/settings.vue
src/render/composables/useScroll.ts
src/render/ipc.ts
src/render/listeners.ts
src/render/shared/account.ts [new file with mode: 0644]
src/render/shared/connectionStatus.ts
src/render/shared/messages.ts
src/render/shared/profile.ts
src/render/shared/version.ts [new file with mode: 0644]
src/session.ts
src/store.ts
src/types.ts
src/uiState.ts [moved from src/messages.ts with 56% similarity]

diff --git a/build/icon.icns b/build/icon.icns
new file mode 100644 (file)
index 0000000..b173533
Binary files /dev/null and b/build/icon.icns differ
index 965502cb40bf9c8b4fcc4327460be0f2175de1a4..6ea1ac4a71cb8ff0921ce77071f5dc97cdb7e518 100644 (file)
@@ -1,66 +1,79 @@
 
+import { app } from "electron";
+import { parseAuthRes } from "@/auth";
 import { postAuth, postLogin, postLogout } from "@/api/account";
-import { endSession, launchSession } from "@/session";
-import { getToken,  setToken, setProfile, clearStore } from "./store";
-import { parseAuthRes } from "./auth";
-import { ipcEmit } from "@/composables/useEmitter";
+import { updateAppUI, launchSession, endSession } from "@/session";
+import { getToken, setToken, clearToken } from "./store";
+import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
 
-export const accountAuth = async (): Promise<Error | AuthState> => {
+
+/* Either null or a crimataId */
+let account: string | null = null;
+
+
+export const accountAuth = async (): Promise<void> => {
 
     /* attempt to get a login token from the store */
     const token = getToken();
 
-    /* try to login with it, returns platform secret and new token on success */
-    if (token) {
-        try {
+    try {
 
-            const res = await postAuth(token);
+        if (token) {
 
+            // attempt to login with token
+            const res = await postAuth(token);
             const parsed = parseAuthRes(res);
 
-            setToken(parsed.token)
-            setProfile(parsed.profile);
+            // save jwt token and profile
+            setToken(parsed.token);
+            account = parsed.crimataId
+
+            // launch session
+            launchSession(parsed.token);
 
-            return {
-                profile: parsed.profile,
-                token: parsed.token
-            };
+        } else throw(new Error('Failed to authenticate (no token).'));
 
-        } catch(e) {
-            console.log('[ACCOUNT]', e);
-            clearStore();
-            throw(new Error('Failed to authenticate.'));
+    } catch (e) {
+        console.log(e);
+        clearToken();
+
+    } finally {
+
+        // push state changes to the frontend
+        updateAppState();
 
-        }
-    } else {
-        throw(new Error('Unable to authenticate.'));
     }
+
 };
 
-export const accountLogin: IpcHandlerCallback<AccountCredentials, Profile> = async (payload) => {
-     const account = payload as AccountCredentials;
+export const accountLogin = async (payload: any): Promise<Error | void> => {
+
+     const creds = payload as AccountCredentials;
+
      try {
 
          // attempt login with email password
-         const res = await postLogin(account.email, account.password);
+         const res = await postLogin(creds.email, creds.password);
          const parsed = parseAuthRes(res);
 
          // save jwt token and profile
          setToken(parsed.token);
-         setProfile(parsed.profile);
+         account = parsed.crimataId;
 
          // launch session
          launchSession(parsed.token);
 
-         // return profile to renderer
-         return parsed.profile;
+         // push state changes to the frontend
+         updateAppState();
+
+         return;
 
      } catch(e) {
-         clearStore();
+         clearToken();
          throw e;
      }
-}
 
+}
 
 export const accountLogout = async (): Promise<Error | void> => {
 
@@ -68,8 +81,12 @@ export const accountLogout = async (): Promise<Error | void> => {
         // post logout to backend
         await postLogout();
 
-        // remove key and crimataId
-        clearStore();
+        // remove key and account
+        clearToken();
+        account = null;
+
+        // push account state to browser
+        ipcEmit("set-account", account);
 
         // kill crimata platform session
         endSession();
@@ -83,4 +100,24 @@ export const accountLogout = async (): Promise<Error | void> => {
 
 }
 
+// logout when auth fails on platform end
+backgroundMitt.on("close-auth-fail", async (_payload: any) => {
+
+    await accountLogout();
+
+});
+
+// emits state of account, version, and UI
+export const updateAppState = () => {
+
+    ipcEmit("set-account", account);
+
+    ipcEmit("set-version", app.getVersion());
+
+    updateAppUI();
+
+}
+
+
+
 
diff --git a/src/audio.ts b/src/audio.ts
deleted file mode 100644 (file)
index 7ae2562..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-/* eslint @typescript-eslint/no-var-requires: "off" */
-
-"use strict";
-
-// where the audio goes
-let buffer: ArrayBuffer[] = [];
-
-// place audio data in buffer
-export const collect: IpcListenerCallback<ArrayBuffer> = (chunk) => {
-    if (chunk) buffer.push(chunk);
-}
-
-// return audio and clear buffer
-export const flush = async () => {
-    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;
-//         }
-//     }
-// }
-
-
index 14ed36a92cf5e5d3f1bb7138c93f29366a126107..31c66e09f1c94ed71d1d96a2ed50b5aac68f56de 100644 (file)
@@ -1,13 +1,5 @@
-
 export const parseAuthRes = (authRes: any) => {
     const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
-    const profile = authRes.data as Profile;
-    return {
-        token,
-        profile
-    }
-};
-
-
-
-
+    const crimataId = authRes.data as string;
+    return {token, crimataId}
+};
\ No newline at end of file
diff --git a/src/composables/useMessageCanvas.ts b/src/composables/useMessageCanvas.ts
deleted file mode 100644 (file)
index c85f7ed..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-export default class Canvas {
-
-    messages: Message[];
-
-    /* seed canvas with messages on init */
-    constructor(messages: Message[]) {
-        this.messages = messages;
-        ipcEmit("seed-view", this.messages);
-    }
-
-    /* add a new message to the canvas */
-    add(message: Message) {
-        this.messages.push(message);
-        ipcEmit("update-view", message);
-    }
-
-    /* update an existing message */
-    update(message: Message) {
-
-        /* get the target message */
-        let target_message = this.messages.filter((m: Message) => {
-            return m.uid = message.uid;
-        })[0];
-
-        /* replace the target message */
-        if (target_message) {
-            target_message = message;
-            ipcEmit("update-view", message);
-        }
-
-    }
-
-}
index aed517c3128428d887243fddeea281273f6930a2..ed0973e2ee27a52faeb621c09a3b776e971de2f8 100644 (file)
@@ -76,7 +76,6 @@ export default function useWebSockets(
                 setTimeout(() => {
                     connect(socketUrl, secret), _reconnectTimeout
                 });
-
             }
 
         });
index 5529662b9c8ed8c5279ddecb1285328e347bc52d..1c812cd9cbde19ea223813dcabc0061d059cab15 100644 (file)
@@ -1,7 +1,7 @@
 
 "use strict";
 
-import { accountLogin, accountLogout, accountProfile } from "@/account";
+import { accountLogin, accountLogout } from "@/account";
 import { IpcHandler } from "@/composables/useIpcMain";
 import { flush } from "@/audio";
 
@@ -24,5 +24,3 @@ export const getAudioHandler = new IpcHandler({
     channel: GET_AUDIO_CHANNEL,
     handlerCallback: flush
 });
-
-
index 17af103218285d503b369e435d8809a8d50070cb..0f63873ce909e76b08353b12a3c3d55a4f5082bb 100644 (file)
@@ -2,25 +2,19 @@
 import { IpcListener } from "@/composables/useIpcMain"
 import { sendMessage } from '@/session';
 import { collect } from "@/audio";
-import { updateAppState } from "@/session";
+import { updateAppState } from "@/account";
 import { onNavBar } from "@/window";
+import { toggleRecord } from "@/audio";
 
 const CLIENT_MESSAGE_CHANNEL = "post-session-send"
-const GET_AUDIO_CHANNEL = "post-audio-collect";
 const APP_MOUNT_CHANNEL = "post-app-mount";
 const NAV_BAR_CHANNEL = "post-nav-bar";
-const WINDOW_BLUR_CHANNEL = "post-window-focus";
 
-export const messageListener = new IpcListener<Message>({
+export const messageListener = new IpcListener<Raw | Request>({
     channel: CLIENT_MESSAGE_CHANNEL,
     listenerCallback: sendMessage
 });
 
-export const audioChunkListener = new IpcListener<ArrayBuffer>({
-    channel: GET_AUDIO_CHANNEL,
-    listenerCallback: collect
-});
-
 export const appMountListener = new IpcListener<null>({
     channel: APP_MOUNT_CHANNEL,
     listenerCallback: updateAppState
@@ -31,16 +25,7 @@ export const navBarListener = new IpcListener({
     listenerCallback: onNavBar
 });
 
-
-const onWindowFocus: IpcListenerCallback<{ isFocused: boolean }> = (payload) => {
-    if (payload.isFocused) {
-        console.log('window is focused')
-    } else {
-        console.log('window is blurred')
-    }
-}
-
-export const windowFocusListener = new IpcListener({
-    channel: WINDOW_BLUR_CHANNEL,
-    listenerCallback: onWindowFocus
-});
+export const recordingListener = new IpcListener({
+    channel: RECORDING_CHANNEL,
+    listenerCallback: toggleRecord
+});
\ No newline at end of file
index 6df9f6cee6623a661d344d24c9bf6ed0c3b6ccb0..99c1dfaf9c472eaac891ba5d9ea472c948330ccb 100644 (file)
 
 import initIpcMain from "@/ipc/index";
 import  { accountAuth } from "./account";
-import { launchSession, updateAppState } from "./session";
 import createWindow from "./window";
 
-let authState: AuthState | null;
 
 export default async function main() {
 
@@ -24,16 +22,7 @@ export default async function main() {
     /* launch browser window */
     await createWindow();
 
-    try {
-        authState = await accountAuth() as AuthState;
-    } catch(e) {
-        console.log('AUTH:', e);
-        authState = null;
-    } finally {
-        if (authState) {
-            launchSession(authState.token as string);
-        }
-        updateAppState();
-    }
+    /* try to authenticate with token */
+    await accountAuth();
 
 }
diff --git a/src/profile.ts b/src/profile.ts
new file mode 100644 (file)
index 0000000..0c5428d
--- /dev/null
@@ -0,0 +1,22 @@
+import { ipcEmit } from "@/composables/useEmitter";
+
+
+export default class ProfileHandler {
+
+    profile: Profile;
+
+    constructor(profile: Profile) {
+        this.profile = profile;
+        this.emit();
+    }
+
+    emit() {
+        ipcEmit("set-profile", this.profile);
+    }
+
+    update(update: Update) {
+        this.profile = update.data as Profile;
+        this.emit()
+    }
+
+}
\ No newline at end of file
index ba56bac5ed0cbeefd59d96eeeeb2e73df57a66e8..3e5cf13e3943d67d552f1f2c8dd3a3be29fe5749 100644 (file)
@@ -1,15 +1,10 @@
 <template>
   <!-- Only render when profile has been set -->
-  <main id="app" v-if="authComplete">
+  <main id="app" v-if="ready">
 
     <Header />
 
-    <Messenger
-        v-if="profile"
-    />
-
-    <!-- Main Components
-    -->
+    <Messenger v-if="account"/>
     <Login v-else />
 
   </main>
 
 <script lang="ts">
 
-import { defineComponent, onMounted, onUnmounted } from "vue";
-import { IpcRendererEvent } from "electron";
+import { defineComponent, onMounted } from "vue";
 
 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";
 
-import { profile, authComplete } from "@/render/shared/profile";
+import { ready, account, setAccount, clearAccount } from "@/render/shared/account";
 import { postAppMount } from "@/render/ipc";
 
 export default defineComponent({
@@ -43,30 +37,15 @@ export default defineComponent({
 
   setup() {
 
-    const onFocus = () => {
-        console.log('window is focused');
-    }
-
-    const onBlur = () => {
-        console.log('window is blurred');
-    }
-
     onMounted(() => {
-        // subscribe to visibility change events
-        window.addEventListener('blur', onBlur);
-        window.addEventListener('focus', onFocus);
 
         postAppMount()
-    });
 
-    onUnmounted(() => {
-        window.removeEventListener("blur", onBlur);
-        window.removeEventListener("focus", onFocus);
     });
 
     return {
-        authComplete,
-        profile
+        account,
+        ready
     }
   }
 })
diff --git a/src/render/assets/connectLogo.svg b/src/render/assets/connectLogo.svg
new file mode 100644 (file)
index 0000000..7a390a4
--- /dev/null
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24.87" height="23.001" viewBox="0 0 24.87 23.001">
+  <g id="Group_541" data-name="Group 541" 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 11.948)">
+          <circle id="Ellipse_50" data-name="Ellipse 50" cx="5.527" cy="5.527" r="5.527" transform="translate(0 0)" fill="#383838"/>
+          <circle id="Ellipse_51" data-name="Ellipse 51" cx="5.527" cy="5.527" r="5.527" transform="translate(13.817 0)" fill="#383838"/>
+        </g>
+        <circle id="Ellipse_52" data-name="Ellipse 52" cx="5.527" cy="5.527" r="5.527" transform="translate(6.898 0)" fill="#383838"/>
+        <path id="Path_150" data-name="Path 150" d="M36.27,83.67" transform="translate(-30.733 -66.196)" fill="#ff0"/>
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/src/render/assets/crimataAvatar.svg b/src/render/assets/crimataAvatar.svg
new file mode 100644 (file)
index 0000000..b4f3fd6
--- /dev/null
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
+  <g id="Group_604" data-name="Group 604" transform="translate(-366 -464)">
+    <circle id="Ellipse_69" data-name="Ellipse 69" cx="15" cy="15" r="15" transform="translate(366 464)" fill="#fff"/>
+    <g id="Group_603" data-name="Group 603">
+      <circle id="Ellipse_50" data-name="Ellipse 50" cx="3.116" cy="3.116" r="3.116" transform="translate(374 479.252)" fill="#383838"/>
+      <circle id="Ellipse_51" data-name="Ellipse 51" cx="3.116" cy="3.116" r="3.116" transform="translate(381.789 479.252)" fill="#383838"/>
+      <circle id="Ellipse_52" data-name="Ellipse 52" cx="3.116" cy="3.116" r="3.116" transform="translate(377.889 472.517)" fill="#383838"/>
+    </g>
+  </g>
+</svg>
diff --git a/src/render/assets/settingsIcon.svg b/src/render/assets/settingsIcon.svg
new file mode 100644 (file)
index 0000000..25399f5
--- /dev/null
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="25" height="5" viewBox="0 0 25 5">
+  <g id="Group_611" data-name="Group 611" transform="translate(-4032 499)">
+    <circle id="Ellipse_71" data-name="Ellipse 71" cx="2.5" cy="2.5" r="2.5" transform="translate(4032 -499)" fill="#9b9b9b"/>
+    <circle id="Ellipse_72" data-name="Ellipse 72" cx="2.5" cy="2.5" r="2.5" transform="translate(4042 -499)" fill="#9b9b9b"/>
+    <circle id="Ellipse_73" data-name="Ellipse 73" cx="2.5" cy="2.5" r="2.5" transform="translate(4052 -499)" fill="#9b9b9b"/>
+  </g>
+</svg>
index f664310e701d6b20fff5e2670a09c0a4747dd81b..f1c1f151d8cb79ada24c407872098dc08ea80d50 100644 (file)
@@ -1,6 +1,7 @@
 <template>
   <div :class="`bubble ${modifier}-bubble ${modifier}-${child}`">
-    {{ content }}
+    <div v-show="!seen" class="notify"></div>
+    {{ text }}
   </div>
 </template>
 
@@ -11,7 +12,7 @@
  export default defineComponent({
   name: "Bubble",
 
-  props: ["modifier", "content", "child"],
+  props: ["modifier", "text", "child", "seen"],
 
  })
 
   border-top-right-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);
+  }
+}
+
 </style>
\ No newline at end of file
index c02b43a69c81e8a4d2fb75b184922a5287a78fc8..ca5f3bf54335394424b59ae1bef4e2e06ad88e89 100644 (file)
@@ -1,20 +1,34 @@
 <template>
-    <div class="statusContainer">
-        <div>{{status}}</div>
+  <div id="container">
+    <img id="logo" src="@/render/assets/connectLogo.svg">
+    <div id="connect">
+      <div class="dot"></div>
     </div>
-
+  </div>
 </template>
 
 
 <script lang="ts">
 
-import { defineComponent } from 'vue'
-import { status } from "@/render/shared/connectionStatus";
+import { defineComponent, onMounted, watch } from 'vue'
+import { hiddenState, useAnim, status, show, hide } from "@/render/shared/connectionStatus";
 
 export default defineComponent({
     name: "ConnectionStatus",
 
     setup() {
+
+        watch(status, (val, _oldval) => {
+          if (val === "Connected" || val === "Nominal") {
+            if (!hiddenState) hide();
+          } else {
+            if (hiddenState)
+            show();
+          }
+        });
+
+        onMounted(useAnim);
+
         return {
             status
         }
@@ -25,13 +39,65 @@ export default defineComponent({
 </script>
 
 
-<style lang="scss">
-.statusContainer {
-    width: 100vw;
-    display: flex;
-    justify-content: center;
-    :first-child {
-        margin-right: 2px;
-    }
+<style lang="scss" scoped>
+
+#container {
+  position: fixed;
+  width: 100vw;
+  height: 54px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
 }
+
+#connect {
+  opacity: 0;
+}
+
+shape {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background-color: #48E065;
+}
+
+.dot {
+  @extend shape;
+  position: relative;
+  transform: translateX(-15px);
+  animation: flashing 1s infinite linear alternate;
+  animation-delay: .25s;
+}
+
+.dot::before, .dot::after {
+  content: '';
+  display: inline-block;
+  position: absolute;
+}
+
+.dot::before {
+  @extend shape;
+  left: -9px;
+  animation: flashing 1s infinite alternate;
+  animation-delay: 0s;
+}
+
+.dot::after {
+  @extend shape;
+  left: 9px;
+  animation: flashing 1s infinite alternate;
+  animation-delay: 0.5s;
+}
+
+@keyframes flashing {
+  0% {
+    background-color: #48E065;
+  }
+  50%,
+  100% {
+    background-color: #9B9B9B;
+  }
+}
+
 </style>
index 0efcb7a50373bf892f763d2e1094e4150fe1b60c..1092eb505119f2c6b6ade4a3e69f51e898bf6cb9 100644 (file)
@@ -1,35 +1,27 @@
 <template>
+
   <span :class="`context ${modifier}-context`">
-    <div v-if="modifier==='ai'" class="avatar"></div>
-    {{ contextRef }}
+
+    <div v-if="context.img" class="avatar">      
+      <img v-if="context.img === 'crimata'" :src="require('@/render/assets/crimataAvatar.svg')">
+      <div v-else>{{ context.img }}</div>
+    </div>
+
+    {{ context.text == false ? " " : context.text }}
+
   </span>
+
 </template>
 
 <script lang='ts'>
 
- import { defineComponent, ref, watch } from 'vue';
- import { annotateAnim } from "@/render/components/controllers/helpers";
+ import { defineComponent } from 'vue';
 
  export default defineComponent({
   name: "Context",
 
   props: ["modifier", "context", "uid"],
 
-  setup(props) {
-
-    const contextRef = ref(" ");
-    contextRef.value = props.context;
-
-    watch(() => props.context, (val: string, _oldval: string) => {
-      annotateAnim(props.uid, val, contextRef);
-    });
-
-    return {
-      contextRef
-    };
-
-  }
-
  })
 
 </script>
 }
 
 .avatar {
-  margin-right: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
   width: 30px;
   height: 30px;
   background-color: white;
   border-radius: 15px;
+  margin-right: 5px;
 }
 
 .playing {
diff --git a/src/render/components/controllers/bubble.control.ts b/src/render/components/controllers/bubble.control.ts
deleted file mode 100644 (file)
index e69de29..0000000
index 017cfb3ef23f376543e0b5040766badf08af6595..2fb11611ad70edb94a785372cfccdeb8302e9884 100644 (file)
@@ -85,31 +85,6 @@ export function animateAudioInput () {
 
 }
 
-// animate a message annotation.
-//! probably could be improved.
-export function annotateAnim (uid: string, val: string, contextRef: Ref) {
-
-  anime({
-    targets: `#${uid} span`,
-    opacity: [1, 0],
-    duration: 250,
-    easing: 'easeOutExpo'
-  })
-
-  .finished.then(() => {
-    contextRef.value = val; 
-  })
-
-  .then(() => {
-    anime({
-      targets: `#${uid} span`,
-      opacity: [0, 1],
-      duration: 250,
-      easing: 'easeOutExpo'
-    })
-  })
-}
-
 // Given index and length of content, return message child status.
 export function calcChild (index: number, len: number) {
   if (len === 1) {
index a97f061293fad83151b72f3af710b63f7e4b9a65..ebc47c65c573818a2dc0c0e71d24c502d3e76cb0 100644 (file)
@@ -9,7 +9,6 @@
         class="menuButton minimizeButton"
         @click.prevent="postNavBarMin"
       />
-      <connection-status />
     </span>
 </template>
 
     import { defineComponent } from "vue";
     import { postNavBar } from "@/render/ipc";
 
-    import ConnectionStatus from "./connectionStatus.vue";
-
     export default defineComponent({
         name: "Header",
-        components: { ConnectionStatus },
         setup() {
             const postNavBarExit = () => postNavBar('close');
             const postNavBarMin = () => postNavBar('min');
index 5deb739027a73e413c4307eaba08afa8938ed56e..5b9c4030d1ede161068733705d99bdd41b4fb337 100644 (file)
@@ -5,8 +5,9 @@
     :class="{ playing: recording }"
     :style="{ top: `${elementY}px`, left: `${elementX}px` }"
   >
-    <div>{{ initials }}</div>
 
+    <!-- show the profile photo in the center of the input item -->
+    <div>{{ profile.photo }}</div>
 
     <!-- Recording animation on space bar -->
     <span v-if="recording" class="play"></span>
@@ -28,7 +29,7 @@
 
 import { defineComponent } from "vue";
 import draggify from "@/render/composables/useDraggify";
-
+import { profile } from "@/render/shared/profile";
 
 import useTextInputController from
   "@/render/components/controllers/inputItem.control.text";
@@ -38,8 +39,6 @@ import useTextInputController from
 export default defineComponent({
   name: "InputItem",
 
-  props: ["initials"],
-
   setup() {
 
     // Default values for position.
@@ -55,6 +54,7 @@ export default defineComponent({
     const recording = false;
 
     return {
+      profile,
       elementX,
       elementY,
       recording
index 1de47cac4f7b0312ce8c8a62522482fe2483cd9a..e78654987521e24d2421bc32237942611a0cb9d6 100644 (file)
@@ -1,14 +1,8 @@
 <template>
   <form id="login" @submit.prevent="submitForm">
 
-
-    <!-- login title -->
-    <div id="loginTitle">
-      <div>Login</div>
-    </div>
-
     <!-- username and password forms -->
-    <div id="loginBox">
+    <div id="form">
       <!-- username -->
       <div class="inputBox">
         <input class="input" type="text" v-model="usr" />
     </div>
 
     <!-- submit button; position: fixed -->
-    <button class="submitButton button" type="submit">Submit</button>
+    <button class="submit button" type="submit">Login</button>
 
   </form>
 
-    <!-- back to login button: position: fixed  -->
-<!--     <button class="createAccountButton button" @click.prevent="switchView">Create account</button> -->
 </template>
 
 <script lang="ts">
 
 import { defineComponent, ref } from "vue";
-import { setProfile } from '@/render/shared/profile';
 import { invokeLogin } from "@/render/ipc";
 
 export default defineComponent({
@@ -50,21 +41,26 @@ export default defineComponent({
     const submitForm = async () => {
 
       try {
-
-          const profile = await invokeLogin({
-              email: usr.value,
-              password: pwd.value
-          }) as Profile;
-
-          setProfile(profile)
-
-
+          await invokeLogin({email: usr.value, password: pwd.value});
       } catch(e) {
+          shake("form");
           console.log(e);
       }
 
     }
 
+    const shake = (elementId: string) => {
+      const el = document.getElementById(elementId);
+
+      if (el) 
+      el.classList.remove("shake");
+      
+      setTimeout(() => {
+        if (el) el.classList.add("shake");
+      }, 250);
+
+    }
+
     return {
       usr,
       pwd,
@@ -87,16 +83,7 @@ export default defineComponent({
   justify-content: center;
 }
 
-#loginTitle {
-  font-family: "SF Pro Text";
-  min-width: 181px;
-  font-size: 24px;
-  font-weight: bold;
-  text-align: left;
-  padding-bottom: 10px;
-}
-
-#loginBox {
+#form {
   font-family: "SF Compact Display";
   display: flex;
   flex-direction: column;
@@ -132,7 +119,7 @@ export default defineComponent({
   margin-left: 15px;
 }
 
-.submitButton {
+.submit {
   font-family: "SF Compact Display";
   position: fixed;
   margin-top: 141px;
@@ -144,21 +131,10 @@ export default defineComponent({
   font-weight: bold;
 }
 
-.submitButton:active {
+.submit:active {
   background-color: #4296C3;
 }
 
-.createAccountButton {
-  position: absolute;
-  border: none;
-  background-color: #EBEBEB;
-  text-decoration: none;
-  color: #58C4FD;
-  font-weight: bold;
-  bottom: 20px;
-  right: 20px;
-}
-
 .button {
   border: none;
   outline: none;
@@ -169,12 +145,29 @@ export default defineComponent({
   cursor: pointer;
 }
 
-.invalid {
-    margin-bottom: 30px;
-    font-family: "SF Compact Display";
-    font-weight: bold;
-    font-size: 14px;
-    color: #F53737;
+.shake {
+  animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
+  transform: translate3d(0, 0, 0);
+  backface-visibility: hidden;
+  perspective: 1000px;
+}
+
+@keyframes shake {
+  10%, 90% {
+    transform: translate3d(-1px, 0, 0);
+  }
+  
+  20%, 80% {
+    transform: translate3d(2px, 0, 0);
+  }
+
+  30%, 50%, 70% {
+    transform: translate3d(-4px, 0, 0);
+  }
+
+  40%, 60% {
+    transform: translate3d(4px, 0, 0);
+  }
 }
 
 </style>
index f7876f788e274d5bf4cb8783e1c0477f511e7de6..c948c8530241739956364c17d3aa6ef61355f74a 100644 (file)
@@ -4,13 +4,14 @@
     <Bubble
       v-for="(val, index) in content"
       :modifier="modifier"
-      :content="val"
+      :text="val.text"
       :child="calcChild(index, content.length)"
+      :seen="seen"
     />
 
-    <Context
+    <Context 
       :modifier="modifier"
-      :context="context" 
+      :context="context"
       :uid="uid"
     />
 
@@ -19,7 +20,7 @@
 
 <script lang='ts'>
 
- import { defineComponent } from 'vue';
+ import { defineComponent, onMounted } from 'vue';
  import Bubble from "@/render/components/bubble.vue";
  import Context from "@/render/components/context.vue";
  import { calcChild } from "@/render/components/controllers/helpers";
@@ -27,7 +28,7 @@
  export default defineComponent({
   name: "Message",
 
-  props: ["modifier", "content", "context", "uid"],
+  props: ["modifier", "content", "context", "seen", "uid"],
 
   components: {
     Bubble,
 
   setup() {
 
+    onMounted(() => {
+
+      const messengerEl = document.getElementById("messenger");
+
+      if (messengerEl)
+      messengerEl.dispatchEvent(new CustomEvent('adjust-scroll'))
+
+    });
+
     return {
       calcChild
     };
@@ -55,7 +65,7 @@
   padding-top: 9px;
   padding-bottom: 9px;
   animation-name: message-init-anim;
-  animation-duration: 0.25s; 
+  animation-duration: 0.25s;
 }
 
 @keyframes message-init-anim {
index d1c27d70b464b48667299c6a1c833d5a40436908..df7dc151010c440b468e715148ebf0e2423a07dd 100644 (file)
@@ -1,9 +1,10 @@
 <template>
 
   <!-- Position fixed items -->
-  <InputItem :initials="profile.initials"/>
   <Settings />
+  <ConnectionStatus />
   <div id="recIcon" />
+  <InputItem />
 
   <!-- List of message bubbles. -->
   <div id="messenger">
@@ -12,6 +13,7 @@
       :modifier="message.modifier"
       :content="message.content"
       :context="message.context"
+      :seen="message.seen"
       :uid="message.uid"
     />
   </div>
 
 <script lang="ts">
 
-import { defineComponent } from "vue";
+import { defineComponent, onMounted, onUnmounted } from "vue";
 import InputItem from "@/render/components/inputItem.vue";
 import Settings from "@/render/components/settings.vue";
 import Message from "@/render/components/message.vue";
+import ConnectionStatus from "./connectionStatus.vue";
 
 import { profile } from "@/render/shared/profile";
 import { messages } from "@/render/shared/messages";
+import useScroll from "@/render/composables/useScroll";
+import { postMessage } from "@/render/ipc";
 
 export default defineComponent({
   name: "Messenger",
 
   components: {
+    ConnectionStatus,
     InputItem,
     Settings,
-    Message
+    Message,
   },
 
   setup() {
+
+    const onFocus = () => {
+        postMessage({
+          intent: "focus",
+          params: { "focus": true },
+          epic: false,
+          confidence: 1.0
+        } as PlatformRequest);
+    }
+
+    const onBlur = () => {
+        postMessage({
+          intent: "focus",
+          params: { "focus": false },
+          epic: false,
+          confidence: 1.0
+        } as PlatformRequest);
+    }
+
+    onMounted(() => {
+
+      useScroll("messenger");
+
+      window.addEventListener('blur', onBlur);
+      window.addEventListener('focus', onFocus);
+
+    });
+
+    onUnmounted(() => {
+
+        window.removeEventListener("blur", onBlur);
+        window.removeEventListener("focus", onFocus);
+
+    });
+
     return {
       messages,
       profile
index b0025bd08aa838642c131f7f94a4b30bc2f44a4b..8e2ca8bf2bed16d507924fdc1a2bf7ea772f08d4 100644 (file)
@@ -1,35 +1,38 @@
 <template>
 
-  <!-- Settings icon. -->
-  <button
-    class="settingsIcon"
-    v-if="toggleSettings == false"
-    @click="onActive"
-  >
-    <div class="settingsButtonDot"></div>
-    <div class="settingsButtonDot"></div>
-    <div class="settingsButtonDot"></div>
-  </button>
+  <!-- Settings content -->
+  <div v-if="toggleSettings" id="settings" >
 
-  <!-- Settings div. -->
-  <div id="settings" v-show="toggleSettings">
-  </div>
+    <p class="account">{{ `Hi, ${account}` }}</p>
 
     <button
-      v-if="toggleSettings"
-      class="settingsOption"
+      class="logout"
       @click="onLogout"
     >
       Logout
     </button>
 
+    <p class="version">{{ `Version ${version}` }}</p>
+
+  </div>
+
+  <!-- Icon -->
+  <button
+    v-else
+    class="icon"
+    @click="onActive"
+  >
+    <img src="@/render/assets/settingsIcon.svg">
+  </button>
+
 </template>
 
 <script lang="ts">
 
   import { defineComponent, ref } from "vue";
-  import { clearProfile } from "@/render/shared/profile"
   import { invokeLogout } from "@/render/ipc";
+  import { version } from "@/render/shared/version";
+  import { ready, account, setAccount, clearAccount } from "@/render/shared/account";
 
   export default defineComponent({
     name: "Settings",
@@ -58,7 +61,6 @@
 
         try {
             await invokeLogout();
-            clearProfile();
         } catch(e) {
             console.log('error')
         }
@@ -66,6 +68,8 @@
       }
 
       return {
+        account,
+        version,
         onActive,
         toggleSettings,
         onLogout
 
 #settings {
   position: fixed;
-
+  width: 100vw;
+  height: 100vh;
+  background-color: rgba(235, 235, 235, 0.75);
+  z-index: 4;
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
-
-  width: 100vw;
-  height: 100vh;
-
-  background-color: #EBEBEB;
-
-  opacity: 0.75;
-
-  z-index: 4;
-
   animation-name: appear;
   animation-duration: 0.5s;
 }
 
 @keyframes appear {
   from {
-    opacity: 0
+    background-color: rgba(235, 235, 235, 0);
   }
   to {
-    opacity: 0.75
+    background-color: rgba(235, 235, 235, 0.75);
   }
 }
 
-.settingsIcon {
+.icon {
   position: fixed;
   right: 0;
-
   border: none;
   outline: none;
-
   display: flex;
   flex-direction: row;
-
   padding: 5px;
   margin-right: 20px;
   margin-top: 19px;
-
   z-index: 2;
   background-color: Transparent;
 }
 
-.settingsIcon:hover {
+.icon:hover {
   cursor: pointer;
 }
 
-.settingsButtonDot {
-  width: 5px;
-  height: 5px;
-
-  margin: 2px;
-  border-radius: 2.5px;
-  background-color: #9B9B9B;
-
+.account {
+  font-size: 12px;
+  font-weight: bold;
+  margin-bottom: 25px;
 }
 
-.settingsOption {
+.logout {
   font-family: "SF Compact Display";
   background-color: #B7B7B7;
   padding: 10px 30px;
   border: none;
   outline: none;
   text-decoration: none;
-  position: absolute;
-  left: 50%;
-  top: 50%;
-  transform: translate(-50%, -50%);
-  z-index: 5;
 }
 
-.settingsOption:hover {
+.logout:hover {
   cursor: pointer;
 }
 
+.version {
+  position: absolute;
+  left: 50%;
+  top: 75%;
+  transform: translate(-50%, -50%);
+  font-size: 12px;
+  font-weight: bold;
+  color: #575757;
+}
+
 </style>
 
index 3ad2b1fafe74e983609842b683a79774f0c1474a..07417f303eda77b7d06c2374bcd14d4e4fc3a997 100644 (file)
@@ -1,33 +1,25 @@
 
-
 export default function useScroll(elementId: string) {
 
-  // Update isScrolledToBottom
-  const isScrolledToBottom = () => {
-    const el = document.getElementById(elementId);
+  const el = document.getElementById(elementId);
 
+  const isBottom = () => {
     if (el) {
-      return el.scrollHeight - el.clientHeight <= el.scrollTop + 30;
+      return el.scrollHeight - el.clientHeight <= el.scrollTop + 1;
     }
-
   }
 
-  // Adjust scroll after we add content to the messenger.
   const adjustScroll = () => {
-    const el = document.getElementById(elementId);
-
     if (el) {
       el.scrollTo({
         top: el.scrollHeight - el.clientHeight,
         behavior: 'smooth'
       });
     }
-
   }
 
-  return {
-    isScrolledToBottom,
-    adjustScroll, 
-  };
+  if (el)
+  el.addEventListener('adjust-scroll', adjustScroll);
+  window.addEventListener('resize', adjustScroll);
   
-}
\ No newline at end of file
+}
index cb1477895ab98f4fdeaacd4d8d20bf1cc027a4b4..b787831e75781f7e6283ddd9f63e21f3f1f811dc 100644 (file)
@@ -12,7 +12,7 @@ const { post, invoke } = useIpc();
 
 
 export const invokeLogin = async (
-    payload: LoginPayload
+    payload: AccountCredentials
 ): Promise<Profile | Error> => (
     await invoke('invoke-account-login', payload)
 );
@@ -41,7 +41,7 @@ export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
  *
  */
 
-export const postMessage = (payload: Raw): void => (
+export const postMessage = (payload: Raw | PlatformRequest): void => (
     post('post-session-send', payload)
 );
 
index e6ada7f92eb65384fd58af518475a89338f1d16f..9e03afb18b2ae7945e7baa0a4122f1fa7e00cd84 100644 (file)
@@ -3,11 +3,20 @@ import { IpcRendererListener } from "./composables/useIpcRend"
 /*
  * Shared state imports
  * */
-import { setProfile } from "./shared/profile";
-import { setMessages, addMessage, updateMessage, deleteMessage } from "./shared/messages";
+import { 
+    setMessages, 
+    addMessage, 
+    updateMessage, 
+    deleteMessage 
+} from "./shared/messages";
+import { setAccount } from "./shared/account";
+import { setProfile } from "./shared/profile"; 
+import { setVersion } from "./shared/version";
 import { setConnectionStatus } from "./shared/connectionStatus";
 
+const SET_ACCOUNT_CHANNEL = "set-account";
 const SET_PROFILE_CHANNEL = "set-profile";
+const SET_VERSION_CHANNEL = "set-version";
 
 const INIT_MESSAGES_CHANNEL = "init-messages";
 const ADD_MESSAGE_CHANNEL = "add-message";
@@ -15,6 +24,16 @@ const UPDATE_MESSAGE_CHANNEL = "update-message";
 const DELETE_MESSAGE_CHANNEL = "delete-message";
 const CONNECTION_STATUS_CHANNEL = "set-connection-status";
 
+export const setAccountListener = new IpcRendererListener({
+    channel: SET_ACCOUNT_CHANNEL,
+    listenerCallback: setAccount
+});
+
+export const setVersionListener = new IpcRendererListener({
+    channel: SET_VERSION_CHANNEL,
+    listenerCallback: setVersion
+});
+
 export const setProfileListener = new IpcRendererListener({
     channel: SET_PROFILE_CHANNEL,
     listenerCallback: setProfile
@@ -44,4 +63,4 @@ export const deleteMessagesListener = new IpcRendererListener({
 export const connectionStatusListener = new IpcRendererListener({
     channel: CONNECTION_STATUS_CHANNEL,
     listenerCallback: setConnectionStatus
-});
+});
\ No newline at end of file
diff --git a/src/render/shared/account.ts b/src/render/shared/account.ts
new file mode 100644 (file)
index 0000000..a612867
--- /dev/null
@@ -0,0 +1,26 @@
+// shared
+import { ref } from "vue";
+
+export const ready = ref(false);
+export const account = ref();
+
+export const setAccount: IpcListenerCallback<string | null> = (payload) => {
+    payload ? account.value = payload : clearAccount();
+    showRender();
+};
+
+export const clearAccount = () => {
+    console.log("Clearing account");
+    account.value = null;
+}
+
+const showRender = () => {
+    ready.value = true;
+}
+
+export default {
+    ready,
+    account,
+    setAccount,
+    clearAccount
+};
index 742a6be5c69eaa0f53ae83e566a0dc9fd75b66ff..6dd95dae6255624630dbc1e9b46144d2f4632704 100644 (file)
@@ -1,12 +1,65 @@
 // shared
 import { ref } from "vue";
+import anime from "animejs";
 
 export const status = ref("");
 
+export let hiddenState = true;
+
+let animateLogo: anime.AnimeInstance;
+let animateConnect: anime.AnimeInstance;
+
+export function useAnim() {
+
+    animateLogo = anime({
+        targets: '#logo',
+        translateX: [0, 15],
+        duration: 500,
+        autoplay: false,
+        easing: 'easeInOutBack'
+    });
+
+    animateConnect = anime({
+        targets: '#connect',
+        translateX: [0, -15],
+        opacity: [0, 1],
+        duration: 500,
+        autoplay: false,
+        easing: 'easeInOutBack'
+    });
+
+}
+
+export function show() {
+    if (animateLogo && animateConnect) {
+        animateLogo.direction = "normal";
+        animateConnect.direction = "normal";
+        animateLogo.play();
+        animateConnect.play();
+        hiddenState = false;
+    }
+}
+
+export function hide() {
+    if (animateLogo && animateConnect) {
+        animateLogo.direction = "reverse";
+        animateConnect.direction = "reverse";
+        animateLogo.play();
+        animateConnect.play();
+        hiddenState = true;
+    }
+}
+
 export const setConnectionStatus: IpcListenerCallback<string> = (payload) => {
     status.value = payload;
 };
 
 export default {
-    status
+    hiddenState,
+    useAnim,
+    status,
+    show,
+    hide
 };
+
+
index acf24e45da63efa47a59711988ea93a9cc5c82fb..01f3a5c5b234fc19fa9ecf4bdf6a7b69d4a74339 100644 (file)
@@ -1,26 +1,14 @@
 // shared
 import { ref, Ref } from "vue";
-import useScroll from "@/render/composables/useScroll";
-
-const { isScrolledToBottom, adjustScroll } = useScroll("messenger");
 
 export const messages: Ref<Array<Message>> = ref([]);
 
 export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
-    messages.value = payload as Array<Message>;
-    setTimeout(() => {
-        adjustScroll();
-    }, 1000);
+    messages.value = payload;
 }
 
 export const addMessage: IpcListenerCallback<Message> = (payload) => {
-    const bottom = isScrolledToBottom();
     messages.value.push(payload as Message);
-    if (bottom) {
-        setTimeout(() => {
-            adjustScroll();
-        }, 10);
-    }
 }
 
 export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
@@ -31,11 +19,15 @@ export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
         if (messages.value[i].uid == annotation.uid) {
 
             if (annotation.name == "content") {
-                messages.value[i].content = annotation.data as string[];
+                messages.value[i].content = annotation.data as Content[];
             }
 
             if (annotation.name == "context") {
-                messages.value[i].context = annotation.data as string;
+                messages.value[i].context = annotation.data as Context;
+            }
+
+            if (annotation.name == "seen") {
+                messages.value[i].seen = annotation.data as boolean;
             }
 
         }
@@ -56,6 +48,10 @@ export const deleteMessage: IpcListenerCallback<string> = (payload) => {
 
 }
 
+export const resetMessages = () => {
+    messages.value = [];
+}
+
 export default {
     messages,
     setMessages,
index 3575de7a15bea3914e495c72a8e6ef43a2b4af55..cdf39f2f107b12fd6166f145c2c7fc95e9fedccb 100644 (file)
@@ -1,27 +1,19 @@
 // shared
 import { ref } from "vue";
 
-export const profile = ref();
+class Profile implements Profile{
+    crimata_id = "";
+    name = "";
+    photo = "";
+}
 
-export const authComplete = ref(false);
+export const profile = ref(new Profile());
 
-export const setProfile: IpcListenerCallback<Profile | null> = (payload) => {
-    payload ? profile.value = payload : clearProfile();
-    showRender();
-};
-
-export const clearProfile = () => {
-    profile.value = null;
-};
-
-export const showRender = () => {
-    authComplete.value = true;
+export const setProfile: IpcListenerCallback<Profile> = (payload) => {
+    profile.value = payload;
 };
 
 export default {
-    setProfile,
-    clearProfile,
     profile,
-    showRender,
-    authComplete,
-};
+    setProfile
+};
\ No newline at end of file
diff --git a/src/render/shared/version.ts b/src/render/shared/version.ts
new file mode 100644 (file)
index 0000000..a412cf6
--- /dev/null
@@ -0,0 +1,12 @@
+// shared
+import { ref } from "vue";
+
+export const version = ref();
+
+export const setVersion: IpcListenerCallback<Profile | null> = (payload) => {
+    version.value = payload;
+};
+
+export default {
+    setVersion
+}
\ No newline at end of file
index 6b40e43bf3657b2a6d00fed3dbad6fedfbb47476..f4f3b607f33492853ba4f0922d561434625451fa 100644 (file)
@@ -1,37 +1,28 @@
 
-
-
-
 // import useAudio from "@/audio";
-import Messages from "./messages";
-import { getProfile } from "./store";
+import UIState from "@/uiState";
+import { config } from "@/config";
 import useWebsockets from "./composables/useWebsockets";
-import {config} from "@/config";
-import { ipcEmit } from "@/composables/useEmitter";
+import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
 
-/* start and stop audio functionality */
-// const { initAudio, closeAudio } = useAudio();
+// UI state
+let uiState = new UIState()
 
-let messages: Messages;
+// let audio = new Audio();
 
 
 const onMessageCallback = (payload: string) => {
 
-    const message = JSON.parse(payload);
-
-    if (message === "CLOSE_AUTH_FAIL") {
-        console.log("deauthenticating");
+    if (payload === "CLOSE_AUTH_FAIL") {
+        backgroundMitt.emit("close-auth-fail");
+        return
     }
 
-    else if (message.header === "init") {
-        messages = new Messages(message.body);
-    }
+    const message = JSON.parse(payload) as ClientProtocol;
 
-    else {
-        if (messages) {
-            messages.update(message.body);
-        }
-    }
+    if (message.header === "init") {
+        uiState.set(message.body as Init);
+    } else uiState.update(message.body as Update);
 
 }
 
@@ -39,17 +30,6 @@ const connectionStatusCallback = (status: string) => {
     ipcEmit('set-connection-status', status);
 }
 
-
-export const updateAppState = (): void => {
-    const profile = getProfile();
-
-    ipcEmit("set-profile", profile);
-
-    if (messages)
-    messages.emit();
-
-}
-
 const statusOptions = {
    openMessage: "Connected",
    pongMessage: "Nominal",
@@ -63,31 +43,29 @@ const { connect, send, close } = useWebsockets(
     statusOptions
 );
 
-/* send a message to the platform */
-export function sendMessage<Message>(message: Message): void {
+export const updateAppUI = (): void => {
+    uiState.emit();
+}
 
-    /* socket send */
+export function sendMessage<Message>(message: Raw | Request): void {
     send(message);
-
 }
 
-
 /* launch a new session (the main process for authenticated users) */
 export function launchSession(platformKey: string) {
 
     /* connect to the platform */
     connect(config.PLATFORM_URL, platformKey);
 
-    /* initialize the audio streams */
-    // initAudio();
+    //  initialize the audio streams 
+    // audio.record();
 
 }
 
-
 export function endSession() {
 
-    // closeAudio();
-
     close();
 
+    uiState.reset();
+
 }
index e532625a7f246bd105a312933b4e5ba04dc7f21b..020068143b0a2a51f953739847ad6e7838a6df1e 100644 (file)
@@ -3,8 +3,7 @@ const Store = require('electron-store');
 const schema = {
        token: {
                type: 'string',
-       },
-    profile: {}
+       }
 };
 
 const store = new Store({
@@ -12,20 +11,14 @@ const store = new Store({
     encryptionKey: "super user test"
 });
 
-export const getToken = (): string | undefined => (store.get("token"));
+export const setToken = (token: string): void => {
+    store.set("token", token);
+} 
 
-export const clearToken = (): void => (store.delete("token"));
-
-export const setToken = (token: string): void => (store.set('token', token));
-
-export const setProfile = (profile: Profile): Profile => (store.set('profile', profile));
-
-export const getProfile = (): Profile => (store.get('profile'));
-
-export const clearProfile = (): void => (store.delete('profile'));
-
-export const clearStore = (): void => {
-    clearToken();
-    clearProfile();
+export const getToken = (): string | undefined => {
+    return store.get("token");
 }
 
+export const clearToken = (): void => {
+    store.delete("token");
+}
\ No newline at end of file
index bcadf73c871b2b4a293a4d78fbc381f36e478b9a..95f0df3929f02a633f537107ea9155ee4aafbfa9 100644 (file)
@@ -1,27 +1,76 @@
 
+/**
+ * Messages from platform except for CLOSE_AUTH_FAIL
+ */
+interface ClientProtocol {
+    header: string;
+    body: Init | Update
+}
+
+/**
+ * Initial message from platform to seed uiState
+ */
+interface Init {
+    profile: Profile;
+    messages: Message[];
+}
+
+/**
+ * Update something in uiState
+ */
 interface Update {
     name: string;
-    data: Message[] | Message | Annotation | string;
+    data: Profile | Message[] | Message | Annotation | string;
 }
 
+/**
+ * A standard message in Messenger
+ */
 interface Message {
     modifier: string;
-    content: string[];
-    context: boolean | string;
+    content: Content[];
+    context: Context;
+    seen: boolean;
     uid: string;
 }
 
+interface Content {
+    text: string;
+    audio: string | boolean;
+}
+
+interface Context {
+    img: string | boolean;
+    text: string | boolean;
+}
+
+/**
+ * Update a specific attribute in a given message
+ */
 interface Annotation {
     name: string;
-    data: string | string[];
+    data: Context | Content[] | boolean;
     uid: string;
 }
 
+/**
+ * Send raw, uncategorized data to the platform
+ */
 interface Raw {
     text: string | boolean;
     audio: string | boolean;
 }
 
+/**
+ * The oppisite of Raw, when you know exactly what needs to get done
+ */
+interface PlatformRequest {
+    intent: string;
+    params: any;
+    epic: string | boolean;
+    confidence: number;
+}
+
 interface WindowState {
     width: number;
     height: number;
@@ -30,19 +79,9 @@ interface WindowState {
 }
 
 interface Profile {
-    crimataId: string;
-    alias: string;
-    initials: string;
-}
-
-interface LoginPayload {
-    email: string;
-    password: string;
-}
-
-interface AuthState {
-    profile: Profile | null;
-    token: string  | null;
+    crimata_id: string;
+    name: string;
+    photo: string;
 }
 
 interface AccountCredentials {
similarity index 56%
rename from src/messages.ts
rename to src/uiState.ts
index 9a72b38a5eda762009466f1bc84f94eefcc5feed..9e3e769779c7f9c2ae64eb5c25e39c09e886742a 100644 (file)
@@ -1,25 +1,43 @@
 import { ipcEmit } from "@/composables/useEmitter";
 
-export default class Messages {
+class Profile implements Profile{
+    crimata_id = "";
+    name = "";
+    photo = "";
+}
 
+export default class UIState {
+
+    profile: Profile;
     messages: Message[];
 
-    constructor(messages: Message[]) {
-        console.log(messages);
-        this.messages = messages;
-        this.emit();
+    constructor() {
+        this.profile = new Profile();
+        this.messages = [];
     }
 
     emit() {
+        ipcEmit("set-profile", this.profile);
         ipcEmit("init-messages", this.messages);
     }
 
+    set (message: Init) {
+        this.profile = message.profile;
+        this.messages = message.messages;
+        this.emit();
+    }
+
     update(update: Update) {
 
-        if (update.name === "add") {
+        if (update.name === "profile") {
+            this.profile = update.data as Profile;
+            ipcEmit("set-profile", this.profile);
+        }
+
+        else if (update.name === "add") {
             this.messages.push(update.data as Message);
             ipcEmit("add-message", update.data);
-        } 
+        }
 
         else if (update.name === "annotate") {
             this._annotate(update.data as Annotation);
@@ -33,18 +51,28 @@ export default class Messages {
 
     }
 
+    reset() {
+        this.profile = new Profile();
+        this.messages = [];
+        this.emit();
+    }
+
     _annotate(annotation: Annotation) {
 
-        for (let i in this.messages) {
+        for (const i in this.messages) {
 
             if (this.messages[i].uid == annotation.uid) {
 
                 if (annotation.name == "content") {
-                    this.messages[i].content = annotation.data as string[];
+                    this.messages[i].content = annotation.data as Content[];
                 }
 
                 if (annotation.name == "context") {
-                    this.messages[i].context = annotation.data as string;
+                    this.messages[i].context = annotation.data as Context;
+                }
+
+                if (annotation.name == "seen") {
+                    this.messages[i].seen = annotation.data as boolean;
                 }
 
                 ipcEmit("update-message", annotation);
@@ -56,7 +84,7 @@ export default class Messages {
     }
 
     _delete(uid: string) {
-        for (var i = 0; i < this.messages.length; i++) {
+        for (let i = 0; i < this.messages.length; i++) {
             if (this.messages[i].uid === uid) {
                 this.messages.splice(i, 1);
                 break;