--- /dev/null
+{"key":"0c116d29-8cfe-491f-a84d-1d021297cc69","newMessages":[]}
\ No newline at end of file
<template>
- <div id="app" v-if="(windowReady && sessionReady)">
+ <!-- Only render when profile has been set -->
+ <div id="app" v-if="(ready)">
<button class="titlebar" />
<span class="menu">
<button
class="menuButton exitButton"
- @click.prevent="callNavbar('close')"
+ @click.prevent="post('nav-bar', 'close')"
/>
<button
class="menuButton minimizeButton"
- @click.prevent="callNavbar('min')"
+ @click.prevent="post('nav-bar', 'min')"
/>
</span>
<!-- Main Pages -->
- <Messenger v-if="auth"/>
+ <Messenger
+ v-if="profile"
+ :profile="profile"
+ :newMessages="newMessages"
+ />
<Login v-else />
</div>
- <!-- Show spash screen if not ready -->
+ <!-- Show spash screen if ready is false -->
<Splash v-else />
</template>
import { IpcRendererEvent } from "electron";
import { authRequest } from '@/modules/message';
import { useIpc } from "@/modules/ipc";
+import { Profile } from "@/types/message";
import Splash from "@/components/splash.vue";
import Messenger from "@/components/messenger.vue";
},
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.`)
+ } else {
+ console.log(`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(() => {
- window.ipcRenderer.on("on-connect", onOpen)
- window.ipcRenderer.on("auth-response", onAuthResponse)
- window.ipcRenderer.on("window-ready", () => windowReady.value = true);
+ console.log("APP:mounted.")
+ window.ipcRenderer.on("update-state", updateState)
+ post("app-mounted", "")
});
onUnmounted(() => {
- window.ipcRenderer.removeAllListeners("on-connect")
- window.ipcRenderer.removeAllListeners("window-ready")
- window.ipcRenderer.removeAllListeners("auth-response")
+ window.ipcRenderer.removeAllListeners("update-state")
})
return {
- callNavbar,
- sessionReady,
- windowReady,
- auth
+ ready,
+ profile,
+ post,
+ newMessages
}
}
})
"use strict";
import { ipcMain } from "electron";
-import { backgroundMitt } from './emitter';
+import { backgroundMitt } from '@/modules/emitter';
const portAudio = require('naudiodon');
// Audio in and out stream objects.
-let ai: typeof portAudio.AudioIO | null = null;
-let ao: typeof portAudio.AudioIO | null = null;
+let ai: typeof portAudio.AudioIO | Boolean = false;
+let ao: typeof portAudio.AudioIO | Boolean = false;
// Whether activly recording.
let record = false;
closeOnError: false,
}
+// Toggles record to true to begin capturing chunks.
+const onRecordingStart = (_event: any, _payload: any) => {
+ console.log("AUDIO: Beginning audio capture.")
+ record = true;
+}
// Returns recorded audio to frontend and sets record to false.
const onRecordingEnd = async (_event: any, payload: any) => {
+ console.log("AUDIO:Sending audio to browser.")
return new Promise((resolve, reject) => {
// If recording, we capture the data.
if (record) {
- console.log('Recording...')
+ console.log('AUDIO:Recording...')
audioContainer.input += chunk;
}
// Listen to record.
console.log("AUDIO:Adding recording listeners.")
- ipcMain.removeAllListeners('start-recording');
- ipcMain.on('start-recording', () => record = true);
+ ipcMain.removeAllListeners("start-recording");
+ ipcMain.on("start-recording", onRecordingStart);
- ipcMain.removeHandler('stop-recording');
- ipcMain.handle('stop-recording', onRecordingEnd);
+ ipcMain.removeHandler("stop-recording");
+ ipcMain.handle("stop-recording", onRecordingEnd);
}
}
// Audio playback.
-export function play(input: Buffer): void {
- let i = 0;
+export function play(input: string): void {
- const audio = bufSplit(input, 8192);
+ // Format the audio.
+ const audio = bufSplit(
+ Buffer.from(input as string, 'hex'),
+ 8192
+ );
// Called on end of write.
const callback = () => {
function write() {
let chunk: Buffer;
let ok = true;
+ let i = 0;
do {
chunk = audio[i];
// Get's called on window close.
export function stopStream() {
console.log("AUDIO:Stopping audio stream.")
- if(ai != null) {
- ai.quit();
- ai = null;
+ if (ai) {
+ ai.quit()
}
- if (ao != null) {
- ao.quit();
- ao = null;
+ if (ao) {
+ ao.quit()
}
+ console.log("AUDIO:Audio closed.")
}
-// // Returns recorded audio to frontend.
-// const getAudio = async (_event: any, payload: any) => {
-
-// return new Promise((resolve, reject) => {
-
-// // Handle auth response from server.
-// backgroundMitt.once('auth-res', (res: string) => {
-
-// if (res == "locked") {
-// reject(res);
-// } else {
-// console.log('Success!');
-// auth = true;
-// resolve(res);
-// }
-
-// });
-
-// if (typeof payload !== "string") {
-// payload = JSON.stringify(payload)
-// }
-
-// // Send token to backend
-// socket.send(payload);
-
-// });
-// };
-
+++ /dev/null
-import { backgroundMitt } from "./emitter";
-import { renderMessage } from "@/modules/message";
-import { Profile, StandardMessage, Annotation } from "@/types/message/index";
-import { play } from "./audio";
-
-
-export const handleAuthMessage = (message: string) => {
- backgroundMitt.emit('ipc-renderer', {
- endpoint: 'auth-response',
- message: message
- });
-}
-
-export const handleProfileMessage = (message: Profile) => {
- backgroundMitt.emit('ipc-renderer', {
- endpoint: 'update-profile',
- message: message
- });
-}
-
-export const handleStandardMessage = (m: StandardMessage) => {
-
- // Create a render message object.
- const message = renderMessage(
- m.content.text, m.content.audio, m.context, m.modifier);
-
- // Play audio if any.
- if (message.content.audio) {
- const audioBytes = Buffer.from(m.content.audio as string, 'hex');
- m.content.audio = true;
- play(audioBytes);
- }
-
- backgroundMitt.emit('ipc-renderer', {
- endpoint: 'render-message',
- message: message
- });
-}
-
-export const handleAnnotation = (message: Annotation) => {
- backgroundMitt.emit('ipc-renderer', {
- endpoint: 'render-message',
- message: message
- });
-}
\ No newline at end of file
--- /dev/null
+import fs from 'fs';
+import { backgroundMitt } from "@/modules/emitter";
+import { SessionState } 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
+
+}
+
+export const saveState = (fileName: string, state: SessionState) => {
+ fs.writeFile(fileName, JSON.stringify(state), (err) => {
+ if (err) {
+ console.log("Error when saving state.")
+ }
+ });
+}
\ No newline at end of file
main()
});
- // Quit app on window closed.
- app.on("window-all-closed", () => {
- console.log("MAIN:Quitting app.")
- app.quit()
+ app.on("before-quit", () => {
});
- // Shutdown audio streams peacefully.
- app.on("before-quit", () => {
- stopStream()
+ app.on("window-all-closed", () => {
});
// When user clicks app icon (re-open)
/*
* 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 { 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;
+// 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.")
+ saveState("session.json", state)
+
+ }
+
+}
+
+// Send state on new window.
+const onNewBrowserWindow = (_event: IpcMainInvokeEvent, _payload: any) => {
+ console.log("SESS:New window, sending profile.")
+ if (profile) {
+ ipcEmit("update-state", {
+ profile: profile,
+ newMessages: state.newMessages
+ })
+ }
+}
// Calls appropriate endpoint for a server message.
const onMessage = (data: string) => {
- const message = JSON.parse(data)
+ let message = JSON.parse(data)
+ // AuthProtocol message.
if (message.hasOwnProperty("key")) {
- handleAuthMessage(message)
+ updateState(message)
}
+ // Standard message.
else if (message.content) {
- handleStandardMessage(message)
- }
- else if (message.first) {
- handleProfileMessage(message)
+ // 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 {
- handleAnnotation(message)
+ if (win) {
+ ipcEmit("render-message", message)
+ }
}
};
-// Attempt token authentication onOpen.
+// When socket connects, we update state.
const onOpen = () => {
- if (key) {
+ console.log(`SESS:Sending key: ${state.key}`)
+
+ if (state) {
sendMessage({
- "key": key,
+ "key": state.key,
"usr": false,
"pwd": false
})
}
}
+// Websockets module.
const { createSocket, sendMessage } = useWebSockets(onMessage, onOpen);
// Handle messages from window/client.
-const onMessageFromWindow = (_event: IpcMainEvent, payload: any) => {
+const onClientMessage = (_event: IpcMainEvent, payload: any) => {
console.log("New client message")
const success = sendMessage(payload)
}
// Call this to initialize session with Crimata servers.
-export const initSession = (key: string) => {
+export const initSession = () => {
+ console.log("SESS:Creating new session.")
- // Set the key.
- key = key;
+ // Load Json or createState.
+ state = loadState("session.json")
+
+ console.log("SESS:State loaded: \n" +
+ ` key: ${state.key}\n` +
+ ` new: ${state.newMessages}`)
// Open socket connection.
createSocket()
+ // Attack browser window init listener.
+ ipcMain.removeAllListeners("app-mounted")
+ ipcMain.on("app-mounted", onNewBrowserWindow);
+
// Attach listeners for frontend.
ipcMain.removeAllListeners("client-message")
- ipcMain.on("client-message", onMessageFromWindow);
+ ipcMain.on("client-message", onClientMessage);
-
-}
\ No newline at end of file
+ // Keep win up-to-date.
+ backgroundMitt.on('window-active', (state: boolean) => {
+ win = state;
+ });
+
+}
import { BrowserWindow, ipcMain } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
-import { backgroundMitt } from './emitter';
+import { backgroundMitt } from '@/modules/emitter';
import { RenderMessage } from "@/types/message/index";
import * as path from "path";
import fs from 'fs';
);
fs.writeFile('windowState.json', state, (err) => {
- if (err) throw err;
- return;
+ if (err) {
+ console.log("BW:Error saving window state.")
+ }
});
}
}
backgroundMitt.emit('window-active', true);
// Handle win nav-bar event.
- ipcMain.removeHandler("nav-bar") // avoid setting duplicate handlers
- ipcMain.handle("nav-bar", onNavBar);
+ ipcMain.removeAllListeners("nav-bar") // avoid setting duplicate handlers
+ ipcMain.on("nav-bar", onNavBar);
// Gateway for messages to the frontend.
- backgroundMitt.removeAllListeners('ipc-renderer')
- backgroundMitt.on('ipc-renderer', renderMessage);
+ backgroundMitt.removeAllListeners("ipc-renderer")
+ backgroundMitt.on("ipc-renderer", renderMessage);
console.log("BW:Listeners created.")
}
// Start recording on space bar.
if (cmd == "SPACE" && !recording.value && !typing.value) {
- post('start-recording', "");
+ console.log("INPT:Starting record.")
+ post("start-recording", "");
showRecIcon()
recording.value = true;
- console.log("Recording...")
}
emitter.emit("self-message", message);
// Stop recording and get audio from recorder.
- const audio = await invoke('stop-recording', "");
+ console.log("INPT:Stopping record.")
+ const audio = await invoke("stop-recording", "");
// Send message to the backend for processing.
const clientM = clientMessage("", audio, message.uid);
hideRecIcon()
recording.value = false;
- console.log("Stopping record...")
}
import { defineComponent, ref, onMounted, onUnmounted } 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",
+
+ props: ["initials"],
+
components: {
TextInput
},
- setup() {
-
- // Mark input item with user's initials.
- const initials = ref("")
- // Set initials.
- const initialData = window.localStorage.getItem("initials")
- if (initialData) initials.value = initialData
+ setup() {
// Default values for position.
const xStart = 15;
const { typing } = useTextInputController(elementX)
const { recording } = useAudioInputController(typing)
- // Update profile functionality.
- const onUpdateProfile = (_event: any, payload: any) => {
- initials.value = payload.message.first[0] + payload.message.last[0]
- window.localStorage.setItem("initials", initials.value)
- }
-
- onMounted(() => {
- window.ipcRenderer.on("update-profile", onUpdateProfile);
- })
-
- onUnmounted(() => {
- window.ipcRenderer.removeAllListeners("update-profile");
- })
-
return {
elementX,
elementY,
- recording,
- initials
+ recording
};
},
});
</form>
<!-- back to login button: position: fixed -->
- <button class="createAccountButton button" @click.prevent="switchView">Create account</button>
+<!-- <button class="createAccountButton button" @click.prevent="switchView">Create account</button> -->
</template>
<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';
setup() {
const { post } = useIpc();
- const { setToken } = useAuth();
- const { resetMessages } = useMessages();
- let usr = ref("");
- let pwd = ref("");
+ const usr = ref("");
+ const pwd = ref("");
// Submit login credentials to the backend.
const submitForm = () => {
<template>
- <InputItem />
+ <InputItem :initials="profile.initials"/>
<Settings />
<div id="recIcon" />
<!-- 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]"
+ />
</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",
+
+ props: ["profile", "newMessages"],
+
components: {
- MessageItem,
+ Message,
InputItem,
Settings
},
- setup() {
+
+ setup(props) {
// Listen for self-messages from inputItem.
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;
- // Front end listener.
+ // Load the message history.
+ if (crimata_id === 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)
});
return {
messages
};
+
},
+
});
</script>
<style lang="scss" scoped>
+
#messenger {
width: 100vw;
height: 100vh;
<script lang="ts">
import { defineComponent, ref } from "vue";
- import { useAuth } from "@/modules/auth";
import { useIpc } from "@/modules/ipc";
import { logoutRequest } from '@/modules/message';
+++ /dev/null
-import { reactive, toRefs } from 'vue';
-import { useIpc } from './ipc';
-
-interface AuthState {
- accessToken: string | null;
- error?: Error;
-}
-
-const state = reactive<AuthState>({
- accessToken: null,
- error: undefined,
-});
-
-const AUTH_KEY = 'crimata_token';
-
-let token: string | null;
-
-token = window.localStorage.getItem(AUTH_KEY);
-if (token) {
- state.accessToken = token;
- window.localStorage.setItem(AUTH_KEY, token);
-}
-
-// Token authentication.
-const authToken = async () => {
- console.log("Calling auth token!!")
- const { invoke } = useIpc();
- try {
- token = window.localStorage.getItem(AUTH_KEY);
-
- if (!token) {
- token = "no-token"
- }
-
- console.log(`Token ${token}`)
- const res = await invoke('auth-session', token);
- window.localStorage.setItem(AUTH_KEY, res);
- state.accessToken = res;
- }
- catch(e) {
- state.error = e;
- console.log('ERROR: failed authentication');
- window.localStorage.removeItem(AUTH_KEY);
- state.accessToken = null;
- }
-}
-
-// Gets called on socket open.
-window.ipcRenderer.on("auth", async (_event, _arg) => {
- await authToken();
-});
-
-export const useAuth = () => {
-
- const setToken = (token: string) => {
- window.localStorage.setItem(AUTH_KEY, token);
- state.accessToken = token;
- state.error = undefined;
- }
-
- const logout = (): Promise<null> => {
- window.localStorage.removeItem(AUTH_KEY);
- return Promise.resolve(state.accessToken = null);
- }
-
- return {
- setToken,
- logout,
- ...toRefs(state), // accessToken, error
- }
-}
export const useIpc = () => {
+
const invoke = async (endpoint: string, payload: any) => {
try {
const res = await window.ipcRenderer.invoke(endpoint, payload);
}
const post = (endpoint: string, payload: any) => {
- window.postMessage({
- endpoint: endpoint,
- content: payload
- }, '*')
+ window.ipcRenderer.send(endpoint, payload);
};
return {
message.content.text = annotation.text
}
-const loadMessages = () => {
+const loadSavedMessages = () => {
const rawData = window.localStorage.getItem("crimata_messages");
if (rawData) {
const messageData = JSON.parse(rawData)
}
}
-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);
- return {
- messages,
- updateMessageView,
- resetMessages
+ // Render new messages, then wait 1s to scroll.
+ if (newMessages.length) {
+ console.log("MSGR:Adding new messages")
+ newMessages.forEach(message => addMessage(message))
+ setTimeout(setScroll.bind(true), 1000);
}
+ }
+
+ return {
+ messages,
+ prepMessageView,
+ updateMessageView
+ }
}
\ No newline at end of file
let isScrolledToBottom: boolean;
// Set the initial scroll position.
- const setScroll = () => {
+ const setScroll = (smooth: boolean) => {
const view = document.getElementById(element)
if (view) {
view.scrollTo({
top: view.scrollHeight - view.clientHeight,
- behavior: 'smooth'
+ behavior: (smooth) ? 'smooth' : 'auto'
});
}
}
uid: string;
}
+export interface SessionState {
+ key: string | boolean;
+ newMessages: RenderMessage[];
+}
+
export interface AuthRequest {
key: boolean | string;
usr: boolean | string;
pwd: boolean | string;
}
-export interface LogoutRequest {
- logout: boolean;
+export interface Profile {
+ crimata_id: 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 {
-{"w":629,"h":500,"x":1423,"y":657}
\ No newline at end of file
+{"w":627,"h":801,"x":1462,"y":450}
\ No newline at end of file