[WIP] refactor, upgrading yarn

This commit is contained in:
Andrew Gundersen 2021-08-26 12:31:18 -05:00
commit f9dcb00e2d
28 changed files with 18227 additions and 13687 deletions

View file

@ -1,20 +1,23 @@
import { app } from "electron";
import { parseAuthRes } from "@/auth";
import { postAuth, postLogin, postLogout } from "@/api/account";
import { updateAppUI, launchSession, endSession } from "@/session";
import { getToken, setToken, clearToken } from "./store";
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
import { LaunchSession, EndSession } from "@/session";
import { store } from "@/composables/useStore";
import { postAuth, postLogin, postLogout } from "@/api/account";
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
/* Either null or a crimataId */
let account: string | null = null;
const parseAuthRes = (authRes: any) => {
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
const crimataId = authRes.data as string;
return {token, crimataId}
};
export const accountAuth = async (): Promise<void> => {
/* attempt to get a login token from the store */
const token = getToken();
const token = store.get("token");
try {
@ -25,17 +28,17 @@ export const accountAuth = async (): Promise<void> => {
const parsed = parseAuthRes(res);
// save jwt token and profile
setToken(parsed.token);
store.set("token", parsed.token);
account = parsed.crimataId
// launch session
launchSession(parsed.token);
LaunchSession(parsed.token);
} else throw(new Error('Failed to authenticate (no token).'));
} catch (e) {
console.log(e);
clearToken();
store.delete("token");
} finally {
@ -57,11 +60,11 @@ export const accountLogin = async (payload: any): Promise<Error | void> => {
const parsed = parseAuthRes(res);
// save jwt token and profile
setToken(parsed.token);
store.set("token", parsed.token);
account = parsed.crimataId;
// launch session
launchSession(parsed.token);
LaunchSession(parsed.token);
// push state changes to the frontend
updateAppState();
@ -69,7 +72,7 @@ export const accountLogin = async (payload: any): Promise<Error | void> => {
return;
} catch(e) {
clearToken();
store.delete("token");
throw e;
}
@ -82,14 +85,14 @@ export const accountLogout = async (): Promise<Error | void> => {
await postLogout();
// remove key and account
clearToken();
store.delete("token");
account = null;
// push account state to browser
ipcEmit("set-account", account);
// kill crimata platform session
endSession();
EndSession();
return;

View file

@ -1,65 +1,82 @@
const FFT = require('fft.js');
import { globalShortcut } from "electron";
const nodeAudio = require('@crimata/nodeaudio');
import { sendMessage } from "@/io";
import { updateTray } from "@/tray";
import { sendMessage } from "@/session";
import { backgroundMitt, ipcEmit } from '@/composables/useEmitter';
let inputDevice: number;
let outputDevice: number;
let setWriteId: ReturnType<typeof setTimeout>;
let setStreamsId: ReturnType<typeof setTimeout>;
let playbackEl;
let writeToOBuffer = false;
let recording = false;
let playbackId: string;
const streamState = { rec: false, pb: false };
const chunks: Int16Array[] = [];
backgroundMitt.on("data", (int16Arr: Int16Array) => {
if (recording) {
const fft = new FFT(4);
function calcFFT(int16Arr: Int16Array)
{
const out = fft.createComplexArray();
fft.realTransform(out, int16Arr);
return out;
}
backgroundMitt.on("read", (int16Arr: Int16Array) =>
{
if (streamState.rec)
{
chunks.push(int16Arr);
ipcEmit("recording", int16Arr);
ipcEmit("recording", calcFFT(int16Arr)); /* fft used for animation */
}
});
}
);
backgroundMitt.on("playback", (uid: string, aplitude: number) => {
ipcEmit("animate-playback", { uid: uid, aplitude: aplitude });
});
backgroundMitt.on("write", (int16Arr: Int16Array) =>
{
ipcEmit("playback", { uid: playbackId, fft: calcFFT(int16Arr) });
streamState.pb = true;
backgroundMitt.on("write", (status: boolean) => {
writeToOBuffer = status;
});
const setStreams = () => {
clearTimeout(setWriteId);
setWriteId = setTimeout(() => streamState.pb = false, 1000);
}
);
function setStreams()
{
const defaultInput = nodeAudio.core.GetDefaultInputDevice();
const defaultOutput = nodeAudio.core.GetDefaultOutputDevice();
if (inputDevice !== defaultInput) {
if (inputDevice !== defaultInput)
{
inputDevice = defaultInput;
nodeAudio.core.CloseInputStream(inputDevice);
nodeAudio.core.OpenInputStream(inputDevice);
}
if (outputDevice !== defaultOutput) {
if (outputDevice !== defaultOutput)
{
outputDevice = defaultOutput;
nodeAudio.core.CloseOutputStream(outputDevice);
nodeAudio.core.OpenOutputStream(outputDevice);
}
}
const startRecording = () => {
/* If playback is happening, we kill it */
if (playback) nodeAudio.core.ClearOutputBuffer();
recording = true;
updateTray("recording", recording);
function startRecording()
{
streamState.rec = true;
updateTray("recording", streamState.rec);
}
const stopRecording = () => {
recording = false;
updateTray("recording", recording);
function stopRecording()
{
streamState.rec = false;
updateTray("recording", streamState.rec);
/* Send the data to the platform right away */
sendMessage({
@ -70,40 +87,38 @@ const stopRecording = () => {
chunks.length = 0;
}
export const initAudio = () => {
function InitAudio()
{
nodeAudio.core.Initialize(backgroundMitt.emit.bind(backgroundMitt));
setStreamsId = setInterval(setStreams, 2000); // sense device
/* Toggle recording switch with global shortcut */
globalShortcut.register('CommandOrControl+R', () => {
if (recording) {
globalShortcut.register('CommandOrControl+R', () =>
{
if (streamState.rec)
{
stopRecording();
} else {
}
else
{
startRecording();
};
});
}
}
);
}
export const playback = (audio: Int16Array, source: string) => {
/* If existing playback is happening, we kill it */
if (playback) nodeAudio.core.ClearOutputBuffer();
const playbackEl = uid;
/* Will override an existing playback process */
function playback(audio: Int16Array, uid: string)
{
playbackId = uid; /* uid of message */
// nodeAudio.core.CancelPlayback(); /* terminate any current playback */
nodeAudio.core.WriteToOutputStream(audio.buffer);
}
export const terminateAudio = () => {
function TerminateAudio()
{
clearInterval(setStreamsId);
nodeAudio.core.Terminate();
}
export const getStreamState = () => {
let busy = false;
if (playback || recording) {
busy = true;
}
return busy;
}
export { InitAudio, TerminateAudio, playback, streamState };

View file

@ -1,5 +0,0 @@
export const parseAuthRes = (authRes: any) => {
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
const crimataId = authRes.data as string;
return {token, crimataId}
};

View file

@ -1,13 +0,0 @@
import {config} from "@/config";
import fs from 'fs';
export const saveToJson = (fileName: string, data: any) => {
fs.writeFile(config.configPath + fileName, JSON.stringify(data), (err) => {
if (err) {
console.log("Error when saving to json.")
}
})
}

View file

@ -0,0 +1,12 @@
const Store = require('electron-store');
const schema = {
token: {
type: 'string',
},
};
export const store = new Store({
schema,
encryptionKey: "super user test"
});

View file

@ -8,17 +8,15 @@ const _connectionCheckTimeout = 4000;
const _reconnectTimeout = 1000;
let _connectionCheckInterval: ReturnType<typeof setTimeout>;
/* ws-status key:
* 0 - onMessage (connected)
* 1 - pongMessage (nominal)
* 2 - closeMessage (reconnecting)
* 3 - pingErrorMessage (connection lost)
*/
export default function useWebSockets(
messageCallback: (message: string) => void,
connectionStatusCallback: (status: string) => void,
statusOptions?: {
openMessage: string;
pongMessage: string;
closeMessage: string;
pingErrorMessage: string;
},
) {
export default function useWebSockets(emit: (eventName: string | symbol, [...args]: any) => boolean)
{
let socket: WebSocket;
@ -47,7 +45,7 @@ export default function useWebSockets(
socket.send(JSON.stringify({key: secret}));
connectionStatusCallback(statusOptions ? statusOptions.openMessage : "Connection Opened");
emit("ws-status", 0);
// ping server
_connectionCheckInterval = setInterval(() => {
@ -55,7 +53,7 @@ export default function useWebSockets(
socket.ping(null, true, (e: Error) => {
if (e) {
socket.close();
connectionStatusCallback(statusOptions ? statusOptions.pingErrorMessage : "Connection Lost");
emit("ws-status", 3);
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
}
});
@ -66,11 +64,11 @@ export default function useWebSockets(
socket.on("message", (event: WebSocket.MessageEvent) => {
console.log("message received", event);
messageCallback(event.toString())
emit("ws-message", event.toString());
});
socket.on("close", (code: number, reason: string) => {
connectionStatusCallback(statusOptions ? statusOptions.closeMessage : "Connection Closed");
emit("ws-status", 2);
clearInterval(_connectionCheckInterval);
if (code !== 1000 || reason !== 'session-logout') {
setTimeout(() => {
@ -80,7 +78,7 @@ export default function useWebSockets(
});
socket.on("pong", () => connectionStatusCallback(statusOptions ? statusOptions.pongMessage : "Pong"));
socket.on("pong", () => emit("ws-status", 1));
socket.on('error', () => {});

View file

@ -6,11 +6,11 @@
"use strict";
import { app, protocol, globalShortcut } from "electron";
import createWindow from "./window";
import main from "./main";
import main from "@/main";
import { CreateWindow, winState } from "@/window";
import { TerminateAudio } from '@/audio';
import { backgroundMitt } from '@/composables/useEmitter';
import { setWindowOpen, getWindowOpen } from './store';
import { terminateAudio } from './audio';
console.log('Starting Crimata electron app.');
@ -21,37 +21,33 @@ protocol.registerSchemesAsPrivileged([
const isDev = require('electron-is-dev');
// Listen for window creation.
backgroundMitt.on('window-active', (state: boolean) => {
setWindowOpen(state)
});
/* Start main process on ready */
app.on("ready", async () => {
app.on("ready", async () =>
{
await main();
});
}
);
app.on("will-quit", () => {
terminateAudio();
app.on("will-quit", () =>
{
TerminateAudio();
globalShortcut.unregisterAll();
});
// Must keep to ensure app doesn't quit on close.
app.on("before-quit", async () => {
});
// Must keep to ensure app doesn't quit on close.
app.on("window-all-closed", () => {
});
}
);
// When user clicks app icon (re-open)
app.on("activate", () => {
if (!getWindowOpen()) createWindow();
});
app.on("activate", () =>
{
if (!winState.open) CreateWindow();
}
);
// Exit cleanly on request from parent process in development mode.
if (isDev) {
process.on("SIGTERM", () => {
if (isDev)
{
process.on("SIGTERM", () =>
{
app.quit();
});
}
);
}

34
src/io.ts Normal file
View file

@ -0,0 +1,34 @@
import { config } from "@/config";
import { updateTray } from "@/tray";
import useWebsockets from "@/composables/useWebsockets";
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
backgroundMitt.on("ws-status", (status: any) =>
{
let simpleStatus = false;
if (status == 2 || status == 3) simpleStatus = true;
updateTray("disconnect", simpleStatus);
ipcEmit('set-connection-status', status);
});
const { connect, send, close } =
useWebsockets(backgroundMitt.emit.bind(backgroundMitt))
function sendMessage(message: Raw | Request): void
{
send(message);
}
function ConnectToPlatform(platformKey: string)
{
connect(config.PLATFORM_URL, platformKey);
}
function DisconnectFromPlatform(code: number, reason: string)
{
close(code, reason);
}
export { ConnectToPlatform, DisconnectFromPlatform, sendMessage };

View file

@ -1,7 +1,6 @@
import { sendMessage } from '@/io';
import { playback } from "@/audio";
import { onNavBar } from "@/window";
import { sendMessage } from '@/session';
import { setWindowFocus } from '@/store';
import { winState, onNavBar } from "@/window";
import { updateAppState } from "@/account";
import { IpcListener } from "@/composables/useIpcMain"
@ -26,12 +25,12 @@ export const navBarListener = new IpcListener({
listenerCallback: onNavBar
});
export const windowFocusListener = new IpcListener<FocusPayload>({
export const windowFocusListener = new IpcListener<boolean>({
channel: POST_WINDOW_FOCUS,
listenerCallback: ({ isFocused }) => setWindowFocus(isFocused)
listenerCallback: (state) => winState.focus = state
});
export const playbackListener = new IpcListener({
channel: PLAYBACK_CHANNEL,
listenerCallback: playback
});
// export const playbackListener = new IpcListener({
// channel: PLAYBACK_CHANNEL,
// listenerCallback: playback
// });

View file

@ -1,5 +1,5 @@
import { initTray } from '@/tray';
import createWindow from "@/window";
import { CreateWindow } from "@/window";
import initIpcMain from "@/ipc/index";
import { accountAuth } from "@/account";
@ -12,7 +12,7 @@ export default async function main() {
initTray();
/* launch browser window */
await createWindow();
await CreateWindow();
/* try to authenticate with token */
accountAuth();

View file

@ -1,22 +0,0 @@
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()
}
}

View file

@ -85,23 +85,23 @@ export function animateAudioInput () {
}
/* TODO: animation function that takes in a message ID, and data about the
* playedback audio. We target the element with the UID and animate it according
* to the data. - still very hypothetical...
*/
export function animatePlayback (uid: string, meta: number) {
console.log("animating playback!")
}
// Given index and length of content, return message child status.
export function calcChild (index: number, len: number) {
if (len === 1) {
export function calcChild (index: number, len: number)
{
if (len === 1)
{
return "none";
} else if (index === 0) {
}
else if (index === 0)
{
return "first-child";
} else if (index === len - 1) {
}
else if (index === len - 1)
{
return "last-child";
} else {
}
else
{
return "middle-child";
}
}

View file

@ -1,17 +1,13 @@
<template>
<div
id="inputItem"
class="input-item"
id="input-item"
:class="{ playing: recording }"
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
>
<!-- 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>
<span v-if="recording" class="pause"></span>
<!-- Show the profile photo in the center of the input item -->
<!-- Background shadow animates on record -->
<div id="profile">{{ profile.photo }}</div>
<!-- Show text input on key-down -->
<input
@ -30,9 +26,7 @@ import { profile } from "@/render/shared/profile";
import { recording } from "@/render/shared/audio";
import { animateAudioInput } from "@/render/components/controllers/helpers";
import useTextInputController from
"@/render/components/controllers/inputItem.control.text";
"@/render/components/controllers/inputItem.control.audio";
import useTextInputController from "@/render/components/controllers/inputItemControl";
export default defineComponent({
name: "InputItem",
@ -67,7 +61,8 @@ export default defineComponent({
<style lang="scss" scoped>
.input-item {
#input-item {
position: absolute;
display: flex;
@ -93,51 +88,6 @@ export default defineComponent({
cursor: pointer;
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
.play,
.pause {
z-index: 5;
&::before,
&::after {
-webkit-border-radius: 1000px;
-moz-border-radius: 1000px;
border-radius: 1000px;
content: "";
position: absolute;
height: 2.35em;
width: 2.35em;
left: 50%;
transform: translate(-50%, -50%);
top: 50%;
z-index: 0;
}
}
.play::before {
box-shadow: 0 0 0 rgba(195, 195, 195, 0);
}
.pause {
opacity: 0;
}
&.playing {
.play {
opacity: 0;
}
.pause {
opacity: 1;
&::before {
-moz-animation: circle1 1.5s infinite ease-in-out;
-o-animation: circle1 1.5s infinite ease-in-out;
-webkit-animation: circle1 1.5s infinite ease-in-out;
animation: circle1 1.5s infinite ease-in-out;
}
&::after {
-moz-animation: circle2 2.2s infinite ease-in-out;
-o-animation: circle2 2.2s infinite ease-in-out;
-webkit-animation: circle2 2.2s infinite ease-in-out;
animation: circle2 2.2s infinite ease-in-out;
}
}
}
}
.rec-icon {
@ -160,38 +110,6 @@ export default defineComponent({
}
}
@keyframes circle1 {
0%,
100% {
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4);
}
25% {
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15);
}
50% {
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55);
}
75% {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25);
}
}
@keyframes circle2 {
0%,
100% {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.15);
}
25% {
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.3);
}
50% {
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.05);
}
75% {
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.45);
}
}
#textInput {
position: absolute;

View file

@ -28,14 +28,20 @@
export default defineComponent({
name: "Message",
props: ["modifier", "content", "context", "seen", "uid"],
props: ["modifier", "content", "context", "seen", "uid", "playback"],
components: {
Bubble,
Context
},
setup() {
setup(props) {
watch(props.playback, (val, _oldval) =>
{
boxShadow = val/200;
}
);
onMounted(() => {

View file

@ -46,7 +46,7 @@ export default defineComponent({
setup() {
const onFocus = () => {
postWindowFocus({ isFocused: true });
postWindowFocus(true);
postMessage({
intent: "focus",
params: { "focus": true },
@ -56,7 +56,7 @@ export default defineComponent({
}
const onBlur = () => {
postWindowFocus({ isFocused: false });
postWindowFocus(false);
postMessage({
intent: "focus",
params: { "focus": false },

View file

@ -26,9 +26,9 @@ export const invokeLogout = async (): Promise<void> => (
*
*/
export const postPlayback = (payload: string): void => (
post('post-playback', payload)
);
// export const postPlayback = (audio: Int16Array, uid: string): void => (
// post('post-playback', payload)
// );
/**
*
@ -60,7 +60,7 @@ export const postNavBar = (payload: string): void => (
*
*/
export const postWindowFocus = (payload: { isFocused: boolean }): void => (
export const postWindowFocus = (payload: boolean): void => (
post('post-window-focus', payload)
);

View file

@ -7,7 +7,8 @@ import {
setMessages,
addMessage,
updateMessage,
deleteMessage
deleteMessage,
animatePlayback
} from "./shared/messages";
import { setAccount } from "./shared/account";
import { setProfile } from "./shared/profile";
@ -23,8 +24,11 @@ const INIT_MESSAGES_CHANNEL = "init-messages";
const ADD_MESSAGE_CHANNEL = "add-message";
const UPDATE_MESSAGE_CHANNEL = "update-message";
const DELETE_MESSAGE_CHANNEL = "delete-message";
const CONNECTION_STATUS_CHANNEL = "set-connection-status";
const RECORDING_STATUS_CHANNEL = "recording-status";
const RECORDING_CHANNEL = "recording";
const PLAYBACK_CHANNEL = "playback";
export const setAccountListener = new IpcRendererListener({
channel: SET_ACCOUNT_CHANNEL,
@ -46,7 +50,6 @@ export const initMessagesListener = new IpcRendererListener({
listenerCallback: setMessages
});
export const addMessagesListener = new IpcRendererListener({
channel: ADD_MESSAGE_CHANNEL,
listenerCallback: addMessage
@ -67,12 +70,12 @@ export const connectionStatusListener = new IpcRendererListener({
listenerCallback: setConnectionStatus
});
export const recordingStatusListener = new IpcRendererListener({
channel: RECORDING_STATUS_CHANNEL,
export const recordingListener = new IpcRendererListener({
channel: RECORDING_CHANNEL,
listenerCallback: setRecording
});
export const animatePlaybackListener = new IpcRendererListener({
channel: PLAYBACK_LISTENER,
export const playbackListener = new IpcRendererListener({
channel: PLAYBACK_CHANNEL,
listenerCallback: animatePlayback
});

View file

@ -4,7 +4,8 @@ import { ref } from "vue";
export const recording = ref();
export const setRecording: IpcListenerCallback<boolean> = (payload) => {
recording.value = payload;
console.log("Recording");
// recording.value = payload;
}
export default {

View file

@ -36,6 +36,24 @@ export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
}
let playbackId: ReturnType<typeof setTimeout>;
export const animatePlayback: IpcListenerCallback<any> = (payload) => {
console.log("animatePlayback");
// clearTimeout(playbackId);
// const context = payload as AnimationContext;
// const el = document.getElementById(context.uid);
// el.style.boxShadow = `0px 0px 0px 20px rgba(0,0,0,${payload.fft})`;
// /* box shadow will return to 0 in 300ms if no more playback is done */
// playbackId = setTimeout(() => {
// el.style.boxShadow = `0px 0px 0px 20px rgba(0,0,0,0)`;
// }, 300);
}
export const deleteMessage: IpcListenerCallback<string> = (payload) => {
const uid = payload as string;
@ -56,5 +74,6 @@ export default {
messages,
setMessages,
addMessage,
updateMessage
updateMessage,
animatePlayback
};

View file

@ -1,112 +1,97 @@
import { Notification } from 'electron';
import UIState from "@/uiState";
import { config } from "@/config";
import { updateTray } from "@/tray";
import { getWindowFocus, getWindowOpen } from '@/store';
import useWebsockets from "@/composables/useWebsockets";
import UIState from "@/ui";
import { winState } from "@/window";
import { ConnectToPlatform, DisconnectFromPlatform } from "@/io";
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
import { initAudio, terminateAudio, playback, getStreamState } from "@/audio";
import { InitAudio, TerminateAudio, playback, streamState } from "@/audio";
let processContentId: ReturnType<typeof setInterval>;
const contentQueue: Update[] = [];
const uiState = new UIState();
const onMessageCallback = async (payload: string) =>
backgroundMitt.on("ws-message", (payload: string) =>
{
const message = JSON.parse(payload) as ClientProtocol;
if (message.header == "init") {
uiState.set(message.body as Init);
return;
}
if (content)
if (message.header == "init")
{
contentQueue.push(message.body)
}
uiState.set(message.body as Init);
}
else
{
uiState.update(message.body as Update);
const update = message.body as Update;
if (update.notify)
{
contentQueue.push(update);
}
else
{
uiState.update(update);
}
}
}
const statusOptions = { openMessage: 'Connected',
pongMessage: 'Nominal',
closeMessage: 'Reconnecting',
pingErrorMessage: 'Connection Lost' };
const connectionStatusCallback = (status: string) =>
{
/* Simplify connectionStatus for tray state */
let connectionStatusSimple = false;
if (status == statusOptions.pingErrorMessage || statusOptions.closeMessage)
{
connectionStatusSimple = true;
}
updateTray("disconnect", connectionStatusSimple);
ipcEmit('set-connection-status', status);
}
const { connect, send, close } = useWebsockets(onMessageCallback,
connectionStatusCallback, statusOptions);
export function sendMessage(message: Raw | Request): void
{
send(message);
}
let processContentId: ReturnType<typeof setTimeout>;
);
/* launch a new session (the main process for authenticated users) */
export function launchSession(platformKey: string)
function LaunchSession(platformKey: string)
{
/* connect to the platform */
connect(config.PLATFORM_URL, platformKey);
ConnectToPlatform(platformKey);
initAudio();
InitAudio();
/* Process new content every 100ms */
processContentId = setInterval(() =>
{
if (getStreamState())
if (!streamState.rec || !streamState.pb)
{
if (contentQueue.length > 0)
{
const update = contentQueue.shift() as Update;
/* Update the UI */
uiState.update(update);
/* Spawn notification if window not in focus */
if (!getWindowFocus())
if (!winState.focus)
{
new Notification({
title: "New Message",
subtitle: data.context.text,
body: data.content[0].text
}).show();
new Notification(update.notify as NotifyOptions).show();
}
/* Play audio if audio */
if (update.data.content[-1].audio)
let uid: string;
let content: Content[];
if (update.name == "add")
{
playback(message.audio, message.uid);
const message = update.data as Message;
content = message.content;
uid = message.uid;
}
else
{
const annotation = update.data as Annotation;
content = annotation.data as Content[];
uid = annotation.uid;
}
const audio = content[-1].audio;
if (audio) playback(audio, uid);
}
}
}, 100);
}
export function endSession()
function EndSession()
{
terminateAudio();
TerminateAudio();
close(1000, 'session-logout');
DisconnectFromPlatform(1000, 'session-logout');
clearInterval(processContentId)
uiState.reset();
}
export { LaunchSession, EndSession };

View file

@ -1,58 +0,0 @@
const Store = require('electron-store');
const schema = {
token: {
type: 'string',
},
};
const store = new Store({
schema,
encryptionKey: "super user test"
});
/*
* Token state management
* */
export const setToken = (token: string): void => {
store.set("token", token);
}
export const getToken = (): string | undefined => {
return store.get("token");
}
export const clearToken = (): void => {
store.delete("token");
}
/*
* Window state management
* */
export const setWindowOpen = (state: boolean): void => {
store.set("window.isOpen", state);
}
export const getWindowOpen = (): boolean | undefined => {
return store.get("window.isOpen");
}
export const setWindowFocus = (state: boolean): void => {
store.set("window.isFocused", state);
}
export const getWindowFocus = (): boolean | undefined => {
return store.get("window.isFocused");
}
/*
* Icon state management
* */
export const setIconState = (state: string): void => {
store.set("iconStatus", state);
}
export const getIconState = (): string | undefined => {
return store.get("iconStatus");
}

View file

@ -21,6 +21,13 @@ interface Init {
interface Update {
name: string;
data: Profile | Message[] | Message | Annotation | string;
notifiy: NotifyOptions | false;
}
interface NotifyOptions {
title: string;
subtitle: string;
body: string;
}
/**
@ -72,6 +79,11 @@ interface PlatformRequest {
}
interface WindowState {
open: boolean;
focus: boolean;
}
interface WindowPosition {
width: number;
height: number;
x: number | null;
@ -89,6 +101,10 @@ interface AccountCredentials {
password: string;
}
interface FocusPayload {
isFocused: boolean;
}
/*
* Electron Ipc
*/
@ -125,7 +141,3 @@ interface IpcRendererEvent<T> {
payload: T | null;
}
interface FocusPayload {
isFocused: boolean;
}

View file

@ -7,7 +7,7 @@ class Profile implements Profile
photo = "";
}
export default class UIState
export default class UI
{
profile: Profile;
messages: Message[];

View file

@ -1,157 +1,120 @@
"use strict";
import { BrowserWindow, ipcMain, app } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
import { backgroundMitt } from './composables/useEmitter';
import { saveToJson } from "./composables/useSaveToJSON";
import * as path from "path";
import fs from 'fs';
import path from "path";
import { BrowserWindow, app } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
import { config } from "@/config";
const { autoUpdater } = require('electron-updater');
import { store } from "@/composables/useStore";
import { backgroundMitt } from '@/composables/useEmitter';
let win: BrowserWindow | null;
/* x, y coordinates and size, must persist; initialized with defaults */
let winPosition: WindowPosition = { width: 600, height: 500, x: null, y: null }
/* Focused and open states */
let winState: WindowState;
const loadWinState = (fileName: string): WindowState => {
let state: WindowState;
function updateWindowPosition()
{
if (win)
{
const bounds = win.getBounds();
const pos = win.getPosition();
try {
state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString());
winPosition.width = bounds.width;
winPosition.height = bounds.height;
winPosition.x = pos[0];
winPosition.y = pos[1];
store.set("window-position", winPosition);
}
catch (error) {
state = {
width: 600,
height: 500,
x: null,
y: null,
}
}
return state
}
// Called when a NavBar button is pressed.
export const onNavBar: IpcListenerCallback<string> = (payload): void => {
if (win) {
if (payload === "close") {
function onNavBar(payload: string): void
{
if (win)
{
if (payload === "close")
{
win.close();
} else {
backgroundMitt.removeAllListeners("ipc-renderer");
}
else
{
win.minimize();
}
}
}
// Util function to render message on ipc-renderer event.
const postToWindow = <T>(e: IpcRendererEvent<T>): void => {
if (win) {
const postToWindow = <T>(e: IpcRendererEvent<T>): void =>
{
if (win)
{
win.webContents.send(e.channel, e.payload);
}
}
// Write a json with position and size of window.
const saveWindowState = () => {
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.
const onWindowMount = (): void => {
// Must tell initApp that window exists.
backgroundMitt.emit('window-active', true);
// Gateway for messages to the frontend.
backgroundMitt.removeAllListeners("ipc-renderer")
backgroundMitt.on("ipc-renderer", postToWindow);
}
// Do this on window dismount (close).
const onWindowDismount = (): void => {
win = null;
backgroundMitt.emit('window-active', false);
}
// function used by run.ts to create the main window.
export default async function createWindow(): Promise<void> {
return new Promise((resolve, _reject) => {
async function CreateWindow(): Promise<void>
{
return new Promise((resolve, _reject) =>
{
if (win) resolve(); /* no duplicate */
// avoid creating duplicate windows.
if (win) resolve();
const pos = store.get("window-position");
if (pos) winPosition = pos;
// Load the saved window state.
winState = loadWinState("window.json")
win = new BrowserWindow(
{
width: winPosition.width,
height: winPosition.height,
x: winPosition.x as number,
y: winPosition.y as number,
resizable: true,
backgroundColor: '#EBEBEB',
frame: false,
minWidth: 350,
minHeight: 500,
webPreferences: { nodeIntegration: (process.env.ELECTRON_NODE_INTEGRATION as unknown) as boolean, preload: path.join(__dirname, "preload.js") }
}
);
// Define the browser window.
win = new BrowserWindow({
width: winState.width,
height: winState.height,
x: winState.x as number,
y: winState.y as number,
resizable: true,
backgroundColor: '#EBEBEB',
frame: false,
minWidth: 350,
minHeight: 500,
webPreferences: {
nodeIntegration: (process.env.ELECTRON_NODE_INTEGRATION as unknown) as boolean, preload: path.join(__dirname, "preload.js")
}
});
if (win) winState.open = true;
// Load the URL.
if (process.env.WEBPACK_DEV_SERVER_URL) {
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); // dev
}
// Load the URL.
if (process.env.WEBPACK_DEV_SERVER_URL)
{
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); // dev
}
else
{
createProtocol("app");
win.loadURL("app://./index.html"); // prod
}
else {
createProtocol("app");
win.loadURL("app://./index.html"); // prod
}
// Handle window close.
win.on("closed", () => win = null);
// Handle window close.
win.on("closed", onWindowDismount);
// Save window state on resize and move.
win.on("moved", updateWindowPosition);
win.on("resize", updateWindowPosition);
// Save window state on resize and move.
win.on("moved", saveWindowState);
win.on("resize", saveWindowState);
// For showing/hiding the splash screen.
win.webContents.on('did-finish-load', () =>
{
if (win)
win.webContents.send('window-ready', { message: true });
win.once('ready-to-show', () => {
autoUpdater.checkForUpdatesAndNotify();
if (win) win.show()
})
backgroundMitt.on("ipc-renderer", postToWindow);
// For showing/hiding the splash screen.
win.webContents.on('did-finish-load', () => {
if (win) win.webContents.send('window-ready', {
message: true
});
onWindowMount();
resolve();
});
});
resolve();
}
);
}
);
}
autoUpdater.on('update-available', () => {
if (win) win.webContents.send('update_available');
});
autoUpdater.on('update-downloaded', () => {
if (win) win.webContents.send('update_downloaded');
});
export { CreateWindow, onNavBar, winState };