]> Repos - mime-chat/commitdiff
merging
authorAndrew Gundersen <gundersena@xavier.edu>
Tue, 30 Mar 2021 15:48:45 +0000 (10:48 -0500)
committerAndrew Gundersen <gundersena@xavier.edu>
Tue, 30 Mar 2021 15:48:45 +0000 (10:48 -0500)
14 files changed:
1  2 
session.json
src/App.vue
src/background/helpers.ts
src/background/session.ts
src/background/window.ts
src/components/inputItem.vue
src/components/login.vue
src/components/message.vue
src/components/messenger.vue
src/modules/message.ts
src/modules/messages.ts
src/types/message/index.ts
window.json
windowState.json

diff --cc session.json
index 0000000000000000000000000000000000000000,50bed80450b790411a0ad90b4ce7066a7d427720..3a10d11baf8c11c941aa705a0dbf34829262cbe3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1 +1,1 @@@
 -{"key":"0c116d29-8cfe-491f-a84d-1d021297cc69","newMessages":[]}
++{"key":"854fb3e1-9207-473d-82b8-b6385f47e895","newMessages":[]}
diff --cc src/App.vue
index be19d8ecc64dbda6ce6864cea3dc7582f5d39c63,80525ddc447ad9855679f4fb846dc6ac1f4373f0..a8247ea8f54105c7f0828fcdee07e0350e528a9e
@@@ -1,7 -1,8 +1,8 @@@
  <template>
-   <div id="app" v-if="(windowReady && sessionReady)">
+   <!-- Only render when profile has been set -->
+   <div id="app" v-if="(ready)">
  
 -    <button class="titlebar" />
 +    <button id="titlebar" />
  
      <!-- Minimize and exit buttons. -->
      <span class="menu">
@@@ -44,55 -48,35 +48,35 @@@ export default defineComponent(
    },
  
    setup() {
-     const { post, invoke } = useIpc();
+     const { post } = useIpc();
  
-     const auth = ref(false);
-     const windowReady = ref(false);
-     const sessionReady = ref(false);
+     // Whether browser has received user info yet.
+     const ready = ref(false);
  
-     // When connection is established, we send key over.
-     const onOpen = (_event: IpcRendererEvent, payload: any) => {
-       console.log(`Connected to Crimata.`)
-       const key = window.localStorage.getItem("key");
+     // Information about current user.
+     const profile = ref(false);
  
-       if (key) {
-         console.log(`Sending key: ${key}`)
-         post("client-message", authRequest(key, false, false))
-       } 
+     // New messages that browser missed while closed.
+     const newMessages = ref([])
  
-       else {
-         console.log("No key, session ready.")
-         sessionReady.value = true
-       }
-     }
-     // Update authenticate state on auth message from server.
-     const onAuthResponse = (_event: IpcRendererEvent, payload: any) => {
-       const key = payload.message.key
-       const usr = payload.message.usr
-       console.log(`Received auth response: ${usr}, ${key}`)
+     // Receive updated information about the session.
+     const updateState = (_event: IpcRendererEvent, payload: any) => {
+       console.log("APP:Received updated profile and new messages: \n" +
+                   `    profile: ${payload.message.profile}\n` +
+                   `    new: ${payload.message.newMessages}`)
  
-       if (key) {
-         console.log(`Auth success, saving key: ${key}.`)
-         window.localStorage.setItem("key", payload.message.key)
-         auth.value = true;
+       if (payload.message.profile) {
 -        console.log(`Logged-in, showing Messenger View.`)
++        console.log(`APP:Logged-in, showing Messenger View.`)
+       } else {
 -        console.log(`Logged-out, showing Login View.`)
++        console.log(`APP:Logged-out, showing Login View.`)
        }
  
-       else {
-         console.log(`Auth failed, clearing local storage.`)
-         window.localStorage.clear()
-         auth.value = false;
-       }
-       console.log("Session is ready.")
-       sessionReady.value = true
+       // Set profile and newMessages.
+       profile.value = payload.message.profile;
+       newMessages.value = payload.message.newMessages;
  
-     }
+       ready.value = true;
  
-     // Post navbar action to backend.
-     const callNavbar = (action: string) => {
-       invoke("nav-bar", action);
      }
  
      onMounted(() => {
index 0000000000000000000000000000000000000000,098c20323c0414f5fc587157e43809478abe3212..a124c4484cf7a1d64a9e73c2b73fe553429c9451
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,37 +1,60 @@@
 -import { SessionState } from "@/types/message"; 
+ import fs from 'fs';
+ import { backgroundMitt } from "@/modules/emitter";
 -export const saveState = (fileName: string, state: SessionState) => {
 -    fs.writeFile(fileName, JSON.stringify(state), (err) => {
++import { SessionState, WindowState } from "@/types/message"; 
+ 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(fileName).toString());
+     } 
+     catch (error) {
+         state = {
+             key: false,
+             newMessages: []
+         }
+     }
+     return state
+     
+ } 
 -            console.log("Error when saving state.")
++export const loadWinState = (fileName: string): WindowState => {
++    let state: WindowState;
++
++    try {
++        state = JSON.parse(fs.readFileSync(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(fileName, JSON.stringify(data), (err) => {
+         if (err) {
 -    });
++            console.log("Error when saving to json.")
+         }      
++    })
++
+ }
index ece5915de8fcf5b16ef84111f8e66902d9cd7320,828709aaaaae67d2caeef353b900bbc0877ee8b9..5c5ebb26f5bbe7fd7f6abe3c5c167fcc39507bd9
@@@ -1,24 -1,70 +1,70 @@@
  /*
   * Creates a websocket session with Crimata Servers.
   * 
-  * Connects to Servers and attempts token authentication. Will send the result 
-  * of the authentication to the window. It will then serve as a communication
-  * interface between the window and the servers. It will automatically try to 
-  * reconnect on websocket disconnect.
+  * 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 './emitter';
- import { ipcMain, IpcMainEvent } from "electron";
+ import { backgroundMitt } from "@/modules/emitter";
 -import { ipcEmit, loadState, saveState } from './helpers';
++import { ipcEmit, loadState, saveToJson } from './helpers';
+ import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
  
- import useWebSockets from "@/modules/ws";
+ import useWebSockets from "@/modules/websockets";
  
- import { handleAuthMessage, handleStandardMessage, handleProfileMessage, handleAnnotation } from "./handlers";
+ import { play } from "./audio";
+ import { renderMessage } from "@/modules/message";
+ import { AuthProtocol, SessionState, Profile } from "@/types/message"; 
  
- // Key for key-based auth.
- let key: string;
+ let win = true;
  
+ // Info saved to json on quit (key, newMessages).
+ let state: SessionState;
  
+ // Profile of current user.
+ let profile: Profile | boolean;
  
 -        saveState("session.json", state)
+ // Called when server sends auth message.
+ const updateState = (res: AuthProtocol) => {
+     console.log("SESS:Auth message received: \n" +
+                 `    key: ${res.key}\n` +
+                 `    alias: ${res.profile}`)
+     if (state) {
+         // Update key.
+         state.key = res.key;
+         // Update the user profile.
+         profile = res.profile;
+         // Send upated profile to frontend.
+         console.log("SESS:Sending updated user profile to browser.")
+         ipcEmit("update-state", {
+             profile: profile,
+             newMessages: state.newMessages
+         })
+         // Save the updated state to json.
+         console.log("SESS:Saving session state.")
 -    console.log("SESS:New window, sending profile.")
 -    if (profile) {
++        saveToJson("session.json", state)
+     }
+ }
+ // Send state on new window.
+ const onNewBrowserWindow = (_event: IpcMainInvokeEvent, _payload: any) => {
++    if (typeof profile !== 'undefined') {
++        console.log("SESS:Sending user profile to browser.")
+         ipcEmit("update-state", {
+             profile: profile,
+             newMessages: state.newMessages
+         })
+     }
+ }
  
  // Calls appropriate endpoint for a server message.
  const onMessage = (data: string) => {
index 246f5a15838d91c4a4679938881b40fb1dbeab8e,5d180cc95dbd38a852518025346ddd3205de5e6c..33afcccc21da67aaca5cf28678d7252123131a52
@@@ -2,8 -2,8 +2,9 @@@
  
  import { BrowserWindow, ipcMain } from "electron";
  import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
- import { backgroundMitt } from './emitter';
- import { RenderMessage } from "@/types/message/index";
+ import { backgroundMitt } from '@/modules/emitter';
 -import { RenderMessage } from "@/types/message/index";
++import { RenderMessage, WindowState } from "@/types/message/index";
++import { loadWinState, saveToJson } from "./helpers";
  import * as path from "path";
  import fs from 'fs';
  
@@@ -13,6 -13,6 +14,7 @@@ interface IpcRendererPayload 
  }
  
  let win: BrowserWindow | null;
++let winState: WindowState;
  
  // Called when a NavBar button is pressed.
  const onNavBar = (_event: any, action: string): void => {
@@@ -36,24 -36,25 +38,21 @@@ const renderMessage = (payload: IpcRend
  
  // Write a json with position and size of window.
  const saveWindowState = () => {
--    if (win) {
--       const bounds = win.getBounds();
--       const position = win.getPosition();
--
--       const state = JSON.stringify(
--         {
--           w: bounds.width, 
--           h: bounds.height,
--           x: position[0],
--           y: position[1]
--         }
--       );
--
--       fs.writeFile('windowState.json', state, (err) => {
-           if (err) throw err;      
-           return;
 -          if (err) {
 -            console.log("BW:Error saving window state.")
 -          }
--       });
++  if (win) {
++    const bounds = win.getBounds();
++    const position = win.getPosition();
++
++    if (winState) {
++
++      winState.width = bounds.width;
++      winState.height = bounds.height;
++      winState.x = position[0];
++      winState.y = position[1]
++
++      saveToJson("window.json", winState)
++
      }
++  }
  }
  
  // Do this on window mount.
@@@ -84,20 -85,20 +83,20 @@@ const onWindowDismount = (): void => 
  // function used by run.ts to create the main window.
  export async function createWindow(): Promise<void> {
    return new Promise((resolve, _reject) => {
--    console.log("BW:Creating browser window. ")
  
      // avoid creating duplicate windows.
      if (win) resolve();
  
      // Load the saved window state.
--    const state = JSON.parse(fs.readFileSync('windowState.json').toString());
++    winState = loadWinState("window.json")
++    console.log(`BW:Creating window [${winState.width}, ${winState.height}].`)
  
      // Define the browser window.
      win = new BrowserWindow({
--      width: state.w,
--      height: state.h,
--      x: state.x,
--      y: state.y,
++      width: winState.width,
++      height: winState.height,
++      x: winState.x,
++      y: winState.y,
        resizable: true,
        backgroundColor: '#EBEBEB',
        frame: false,
index baa6f5b5b3ea4a4c09b94faaadf057f04a948907,b24d9c4c8465fd672b2e27955fee491af1900196..861d7c135e35467865935fb466d12bf20a185267
  
  <script lang="ts">
  
--import { defineComponent, ref, onMounted, onUnmounted } from "vue";
++import { defineComponent } from "vue";
  import draggify from "@/modules/draggify";
  
- import TextInput from "@/components/inputItem/textInput.vue";
+ import TextInput from "@/components/textInput.vue";
  import useTextInputController from 
-   "@/components/inputItem/controllers/textCtrl";
+   "@/components/controllers/textCtrl";
  import useAudioInputController from 
-   "@/components/inputItem/controllers/audioCtrl";
+   "@/components/controllers/audioCtrl";
  
  export default defineComponent({
    name: "InputItem",
index 838217957c523d0775bbeff7628ae254157e5b64,95cf62f790cb2be38f851869363f80c5abb29ac3..521517c272c2b9fb56188e4278fb80e8570e5893
  <script lang="ts">
  
  import { defineComponent, ref } from "vue";
- import { useAuth } from "@/modules/auth";
  import { useIpc } from "@/modules/ipc";
--import useMessages from "@/modules/messages";
--import { authRequest } from '@/modules/message'; 
++import { authRequest } from '@/modules/message';
  
  export default defineComponent({
    name: "Login",
index 63c58bd80a3118866bf0a0852c66606b3ae82f1d,63c58bd80a3118866bf0a0852c66606b3ae82f1d..b24e9c16b1a33b0d920befcec9ec03dd61b2da94
@@@ -9,6 -9,6 +9,14 @@@
      <!-- 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 === 'launch'"
++      class="divider"
++    >
++      <div>new session</div>
++    </div>
++
      <!-- Includes bubble and context. -->
      <div 
        :id="`${message.modifier}MessageBox`"
          :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 === ''" 
  <script lang='ts'>
  
   import {defineComponent, ref, onMounted} from 'vue';
++ import { RenderMessage } from "@/types/message/index";
  
  
   export default defineComponent({
    name: "MessageItem",
  
--  props: {
--      message: {
--        type: Object,
--        required: true
--      } 
--  },
++  props: ["message"],
  
    setup(props) {
++
++    let addListener = false;
++
++    const notify = ref(false)
      const isPlaying = ref(false);
  
++    if (!props.message.seen) {
++
++      // 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 = (e: 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);
      })
  
      return {
++      notify,
        isPlaying
      }
    }
      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;
    }
    }
  
    .bubble {
++    position: relative;
++
      max-width: 66vw;
  
      display: flex;
      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 {
      display: flex;
      align-items: center;
index 318c5cf2720aef871e1adc22bbfdeda50620bde6,625f18c0eb00b423e2fdba1b332ee54e253491bc..edc50a1954d78bba3bdeaf93390d41e1fdc4829b
@@@ -6,18 -6,23 +6,22 @@@
  
    <!-- List of message bubbles. -->
    <div id="messenger">
-     <MessageItem v-for="message in messages" :message="message[1]" :key="message[0]" />
+     <Message 
+       v-for="message in messages" 
+       :message="message[1]" 
 -      :key="message[0]" 
++      :key="message[0]"
+     />
    </div>
  
  </template>
  
  <script lang="ts">
  import { defineComponent, onMounted, onUnmounted } from "vue";
- import MessageItem from "@/components/messageItem.vue";
- import InputItem from "@/components/inputItem/inputItem.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';
+ import useMessages from "@/modules/messages";
 -import { RenderMessage } from "@/types/message/index" 
  
  export default defineComponent({
    name: "Messenger",
      const { emitter } = useMitt();
  
      // Handle messages in view.
-     const { messages, updateMessageView } = useMessages();
-     // Update message view on new content.
-     const onNewContent = (_event: any, payload: any) => {
-       updateMessageView(payload.message)
-     }
+     const { messages, prepMessageView, updateMessageView } = useMessages();
  
      onMounted(() => {
 -      const crimata_id = props.profile.crimata_id;
++      const crimataId = props.profile.crimataId;
++      console.log(`MSGR:Initializing messenger for ${crimataId}.`)
  
-       // Front end listener.
+       // Load the message history.
 -      if (crimata_id === window.localStorage.getItem("last_usr")) {
++      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)
+       });
  
-       // Back end listener.
-       window.ipcRenderer.on("render-message", onNewContent);
+       // Save the usr for next time.
 -      window.localStorage.setItem("last_usr", crimata_id)
++      window.localStorage.setItem("last_usr", crimataId)
  
      });
  
index c6322bf5bcf7057bb64afbdac66d866a1381e15d,c6322bf5bcf7057bb64afbdac66d866a1381e15d..9601f952ef60591543ae5190926b382d288a8ac8
@@@ -17,7 -17,7 +17,9 @@@ export const renderMessage = (text: boo
      modifier: modifier,
      time: getTimeStamp(),
      uid: uuidv4(),
--    isChild: "none"
++    isChild: "none",
++    seen: false,
++    newMessage: false
    }
  )
  
index 4b0c8b7ad3973f9cc1edb35d9be79a8639e62062,a406cc5f895f52785852f0cd3ecc43f2c35fceea..0b89aaa54e27fdb35b6987f20f757d491d3b60ab
@@@ -39,8 -39,8 +39,8 @@@ const updateGrouping = () => 
    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 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.
    }
  }
  
- const { setScroll, updateScrollRef, adjustScroll } = useScroll("messenger");
  
- loadMessages()
- setTimeout(setScroll, 1000);
+ export default function useMessages() {
  
+   // Scroll controller.
+   const { setScroll, updateScrollRef, adjustScroll } = useScroll("messenger");
  
- export default function useMessages() {
+   // Main function for updating the message view.
+   const updateMessageView = (message: RenderMessage | Annotation) => {
  
-     // Main function for updating the message view.
-     const updateMessageView = (message: RenderMessage | Annotation) => {
+     // Step 1: See if user is scrolled down.
+     updateScrollRef()
  
-       // 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 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 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()
  
-       // Step 4: Update grouping.
-       updateGrouping()
+     // Setp 5: Scroll the view (if scrolled down).
+     setTimeout(adjustScroll, 20);
  
-       // Setp 5: Scroll the view (if scrolled down).
-       setTimeout(adjustScroll, 20);
+     // Step 6: Save the view data.
+     saveMessages()
  
-       // Step 6: Save the view data.
-       saveMessages()
+   }
  
-     }
+   // Seed message view with message history.
+   const prepMessageView = (newMessages: RenderMessage[]) => {
+     console.log("MSGR:Prepping messenger view.")
  
-     const resetMessages = () => {
-       messages.value.clear()
-     }
+     // 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 => addMessage(message))
++
++      newMessages.forEach(message => {
++        message.newMessage = true;
++        addMessage(message)
++      })
++
+       setTimeout(setScroll.bind(true), 1000);
 +
-     return {
-       messages,
-       updateMessageView,
-       resetMessages
      }
+   }
+   return {
+     messages,
+     prepMessageView,
+     updateMessageView
+   }
  }
index ce571273428baec007615dfb294628d20c785ad4,a1880f8d9a7d8e278639ab0572796419235b8d1b..eaf38452a0dd670da31159bd96bc16beca42ffda
@@@ -8,6 -8,6 +8,8 @@@ export interface RenderMessage 
      time: number;
      uid: string;
      isChild: string;
++    seen: boolean;
++    newMessage: boolean;
  }
  
  export interface ClientMessage {
      uid: string;
  }
  
+ export interface SessionState {
+     key: string | boolean;
+     newMessages: RenderMessage[];
+ }
++export 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 LogoutRequest {
-     logout: boolean;
+ export interface Profile {
 -    crimata_id: string;
++    crimataId: string;
+     alias: string;
+     initials: string;
  }
  
- // Possible messages from server:
+ export interface AuthProtocol {
+     key: boolean | string;
+     profile: boolean | Profile;
+ }
  
- export interface Profile {
-     crimata_id: string;
-     title: string;
-     first: string;
-     middle: string;
-     last: string;
-     suffix: string | number;
-     nickname: string;
-     full: string;
+ export interface LogoutRequest {
+     logout: boolean;
  }
  
  export interface Annotation {
diff --cc window.json
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..16e3948f94cce3ed163f12a9948c2ca3a581a48a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++{"width":386,"height":815,"x":1720,"y":495}
diff --cc windowState.json
index 4394ee18dca3782c33e10e51e9ea9330fcdccbf7,fcf3113b2a3529dcd0cbfe20a38a2e08f21a3883..0000000000000000000000000000000000000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,5 -1,1 +1,0 @@@
- <<<<<<< HEAD
- {"w":629,"h":500,"x":1423,"y":657}
- =======
- {"w":622,"h":616,"x":1163,"y":499}
- >>>>>>> logout
 -{"w":627,"h":801,"x":1462,"y":450}