[WIP] refactor, upgrading yarn
This commit is contained in:
parent
a4d6a883b0
commit
f9dcb00e2d
28 changed files with 18227 additions and 13687 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -17,6 +17,15 @@ yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Yarn cache
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/cache
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
nodeLinker: node-modules
|
||||||
|
|
||||||
npmScopes:
|
npmScopes:
|
||||||
crimata:
|
crimata:
|
||||||
|
npmAuthToken: "${NPM_TOKEN}"
|
||||||
npmRegistryServer: "https://gitlab.example.com/api/v4/projects/28849281/packages/npm/"
|
npmRegistryServer: "https://gitlab.example.com/api/v4/projects/28849281/packages/npm/"
|
||||||
npmAuthToken: ${NPM_TOKEN}
|
|
||||||
|
yarnPath: .yarn/releases/yarn-berry.cjs
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
},
|
},
|
||||||
"main": "background.js",
|
"main": "background.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@crimata/nodeaudio": "^0.0.0",
|
|
||||||
"@types/animejs": "^3.1.2",
|
"@types/animejs": "^3.1.2",
|
||||||
"@types/bindings": "^1.3.0",
|
"@types/bindings": "^1.3.0",
|
||||||
"@types/dom-mediacapture-record": "^1.0.7",
|
"@types/dom-mediacapture-record": "^1.0.7",
|
||||||
|
|
@ -28,6 +27,7 @@
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
"electron-store": "^8.0.0",
|
"electron-store": "^8.0.0",
|
||||||
"electron-updater": "^4.3.8",
|
"electron-updater": "^4.3.8",
|
||||||
|
"fft.js": "^4.0.4",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"update-electron-app": "^2.0.1",
|
"update-electron-app": "^2.0.1",
|
||||||
"vue": "^3.0.0-0",
|
"vue": "^3.0.0-0",
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
|
|
||||||
import { app } from "electron";
|
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 */
|
/* Either null or a crimataId */
|
||||||
let account: string | null = null;
|
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> => {
|
export const accountAuth = async (): Promise<void> => {
|
||||||
|
|
||||||
/* attempt to get a login token from the store */
|
/* attempt to get a login token from the store */
|
||||||
const token = getToken();
|
const token = store.get("token");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
@ -25,17 +28,17 @@ export const accountAuth = async (): Promise<void> => {
|
||||||
const parsed = parseAuthRes(res);
|
const parsed = parseAuthRes(res);
|
||||||
|
|
||||||
// save jwt token and profile
|
// save jwt token and profile
|
||||||
setToken(parsed.token);
|
store.set("token", parsed.token);
|
||||||
account = parsed.crimataId
|
account = parsed.crimataId
|
||||||
|
|
||||||
// launch session
|
// launch session
|
||||||
launchSession(parsed.token);
|
LaunchSession(parsed.token);
|
||||||
|
|
||||||
} else throw(new Error('Failed to authenticate (no token).'));
|
} else throw(new Error('Failed to authenticate (no token).'));
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
clearToken();
|
store.delete("token");
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
|
|
@ -57,11 +60,11 @@ export const accountLogin = async (payload: any): Promise<Error | void> => {
|
||||||
const parsed = parseAuthRes(res);
|
const parsed = parseAuthRes(res);
|
||||||
|
|
||||||
// save jwt token and profile
|
// save jwt token and profile
|
||||||
setToken(parsed.token);
|
store.set("token", parsed.token);
|
||||||
account = parsed.crimataId;
|
account = parsed.crimataId;
|
||||||
|
|
||||||
// launch session
|
// launch session
|
||||||
launchSession(parsed.token);
|
LaunchSession(parsed.token);
|
||||||
|
|
||||||
// push state changes to the frontend
|
// push state changes to the frontend
|
||||||
updateAppState();
|
updateAppState();
|
||||||
|
|
@ -69,7 +72,7 @@ export const accountLogin = async (payload: any): Promise<Error | void> => {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
clearToken();
|
store.delete("token");
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,14 +85,14 @@ export const accountLogout = async (): Promise<Error | void> => {
|
||||||
await postLogout();
|
await postLogout();
|
||||||
|
|
||||||
// remove key and account
|
// remove key and account
|
||||||
clearToken();
|
store.delete("token");
|
||||||
account = null;
|
account = null;
|
||||||
|
|
||||||
// push account state to browser
|
// push account state to browser
|
||||||
ipcEmit("set-account", account);
|
ipcEmit("set-account", account);
|
||||||
|
|
||||||
// kill crimata platform session
|
// kill crimata platform session
|
||||||
endSession();
|
EndSession();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
115
src/audio.ts
115
src/audio.ts
|
|
@ -1,65 +1,82 @@
|
||||||
|
const FFT = require('fft.js');
|
||||||
import { globalShortcut } from "electron";
|
import { globalShortcut } from "electron";
|
||||||
const nodeAudio = require('@crimata/nodeaudio');
|
const nodeAudio = require('@crimata/nodeaudio');
|
||||||
|
|
||||||
|
import { sendMessage } from "@/io";
|
||||||
import { updateTray } from "@/tray";
|
import { updateTray } from "@/tray";
|
||||||
import { sendMessage } from "@/session";
|
|
||||||
import { backgroundMitt, ipcEmit } from '@/composables/useEmitter';
|
import { backgroundMitt, ipcEmit } from '@/composables/useEmitter';
|
||||||
|
|
||||||
let inputDevice: number;
|
let inputDevice: number;
|
||||||
let outputDevice: number;
|
let outputDevice: number;
|
||||||
|
|
||||||
|
let setWriteId: ReturnType<typeof setTimeout>;
|
||||||
let setStreamsId: ReturnType<typeof setTimeout>;
|
let setStreamsId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
let playbackEl;
|
let playbackId: string;
|
||||||
let writeToOBuffer = false;
|
|
||||||
let recording = false;
|
const streamState = { rec: false, pb: false };
|
||||||
|
|
||||||
const chunks: Int16Array[] = [];
|
const chunks: Int16Array[] = [];
|
||||||
|
|
||||||
backgroundMitt.on("data", (int16Arr: Int16Array) => {
|
const fft = new FFT(4);
|
||||||
if (recording) {
|
|
||||||
|
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);
|
chunks.push(int16Arr);
|
||||||
ipcEmit("recording", int16Arr);
|
ipcEmit("recording", calcFFT(int16Arr)); /* fft used for animation */
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
backgroundMitt.on("playback", (uid: string, aplitude: number) => {
|
backgroundMitt.on("write", (int16Arr: Int16Array) =>
|
||||||
ipcEmit("animate-playback", { uid: uid, aplitude: aplitude });
|
{
|
||||||
});
|
ipcEmit("playback", { uid: playbackId, fft: calcFFT(int16Arr) });
|
||||||
|
streamState.pb = true;
|
||||||
|
|
||||||
backgroundMitt.on("write", (status: boolean) => {
|
clearTimeout(setWriteId);
|
||||||
writeToOBuffer = status;
|
setWriteId = setTimeout(() => streamState.pb = false, 1000);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
const setStreams = () => {
|
|
||||||
|
|
||||||
|
function setStreams()
|
||||||
|
{
|
||||||
const defaultInput = nodeAudio.core.GetDefaultInputDevice();
|
const defaultInput = nodeAudio.core.GetDefaultInputDevice();
|
||||||
const defaultOutput = nodeAudio.core.GetDefaultOutputDevice();
|
const defaultOutput = nodeAudio.core.GetDefaultOutputDevice();
|
||||||
|
|
||||||
if (inputDevice !== defaultInput) {
|
if (inputDevice !== defaultInput)
|
||||||
|
{
|
||||||
inputDevice = defaultInput;
|
inputDevice = defaultInput;
|
||||||
nodeAudio.core.CloseInputStream(inputDevice);
|
nodeAudio.core.CloseInputStream(inputDevice);
|
||||||
nodeAudio.core.OpenInputStream(inputDevice);
|
nodeAudio.core.OpenInputStream(inputDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputDevice !== defaultOutput) {
|
if (outputDevice !== defaultOutput)
|
||||||
|
{
|
||||||
outputDevice = defaultOutput;
|
outputDevice = defaultOutput;
|
||||||
nodeAudio.core.CloseOutputStream(outputDevice);
|
nodeAudio.core.CloseOutputStream(outputDevice);
|
||||||
nodeAudio.core.OpenOutputStream(outputDevice);
|
nodeAudio.core.OpenOutputStream(outputDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startRecording = () => {
|
function startRecording()
|
||||||
|
{
|
||||||
/* If playback is happening, we kill it */
|
streamState.rec = true;
|
||||||
if (playback) nodeAudio.core.ClearOutputBuffer();
|
updateTray("recording", streamState.rec);
|
||||||
|
|
||||||
recording = true;
|
|
||||||
updateTray("recording", recording);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopRecording = () => {
|
function stopRecording()
|
||||||
recording = false;
|
{
|
||||||
updateTray("recording", recording);
|
streamState.rec = false;
|
||||||
|
updateTray("recording", streamState.rec);
|
||||||
|
|
||||||
/* Send the data to the platform right away */
|
/* Send the data to the platform right away */
|
||||||
sendMessage({
|
sendMessage({
|
||||||
|
|
@ -70,40 +87,38 @@ const stopRecording = () => {
|
||||||
chunks.length = 0;
|
chunks.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initAudio = () => {
|
function InitAudio()
|
||||||
|
{
|
||||||
nodeAudio.core.Initialize(backgroundMitt.emit.bind(backgroundMitt));
|
nodeAudio.core.Initialize(backgroundMitt.emit.bind(backgroundMitt));
|
||||||
setStreamsId = setInterval(setStreams, 2000); // sense device
|
setStreamsId = setInterval(setStreams, 2000); // sense device
|
||||||
|
|
||||||
/* Toggle recording switch with global shortcut */
|
/* Toggle recording switch with global shortcut */
|
||||||
globalShortcut.register('CommandOrControl+R', () => {
|
globalShortcut.register('CommandOrControl+R', () =>
|
||||||
if (recording) {
|
{
|
||||||
|
if (streamState.rec)
|
||||||
|
{
|
||||||
stopRecording();
|
stopRecording();
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
startRecording();
|
startRecording();
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const playback = (audio: Int16Array, source: string) => {
|
/* Will override an existing playback process */
|
||||||
|
function playback(audio: Int16Array, uid: string)
|
||||||
/* If existing playback is happening, we kill it */
|
{
|
||||||
if (playback) nodeAudio.core.ClearOutputBuffer();
|
playbackId = uid; /* uid of message */
|
||||||
|
// nodeAudio.core.CancelPlayback(); /* terminate any current playback */
|
||||||
const playbackEl = uid;
|
|
||||||
|
|
||||||
nodeAudio.core.WriteToOutputStream(audio.buffer);
|
nodeAudio.core.WriteToOutputStream(audio.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const terminateAudio = () => {
|
function TerminateAudio()
|
||||||
|
{
|
||||||
clearInterval(setStreamsId);
|
clearInterval(setStreamsId);
|
||||||
nodeAudio.core.Terminate();
|
nodeAudio.core.Terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStreamState = () => {
|
export { InitAudio, TerminateAudio, playback, streamState };
|
||||||
let busy = false;
|
|
||||||
if (playback || recording) {
|
|
||||||
busy = true;
|
|
||||||
}
|
|
||||||
return busy;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
|
||||||
};
|
|
||||||
|
|
@ -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.")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
12
src/composables/useStore.ts
Normal file
12
src/composables/useStore.ts
Normal 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"
|
||||||
|
});
|
||||||
|
|
@ -8,17 +8,15 @@ const _connectionCheckTimeout = 4000;
|
||||||
const _reconnectTimeout = 1000;
|
const _reconnectTimeout = 1000;
|
||||||
let _connectionCheckInterval: ReturnType<typeof setTimeout>;
|
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(
|
export default function useWebSockets(emit: (eventName: string | symbol, [...args]: any) => boolean)
|
||||||
messageCallback: (message: string) => void,
|
{
|
||||||
connectionStatusCallback: (status: string) => void,
|
|
||||||
statusOptions?: {
|
|
||||||
openMessage: string;
|
|
||||||
pongMessage: string;
|
|
||||||
closeMessage: string;
|
|
||||||
pingErrorMessage: string;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
|
|
||||||
let socket: WebSocket;
|
let socket: WebSocket;
|
||||||
|
|
||||||
|
|
@ -47,7 +45,7 @@ export default function useWebSockets(
|
||||||
|
|
||||||
socket.send(JSON.stringify({key: secret}));
|
socket.send(JSON.stringify({key: secret}));
|
||||||
|
|
||||||
connectionStatusCallback(statusOptions ? statusOptions.openMessage : "Connection Opened");
|
emit("ws-status", 0);
|
||||||
|
|
||||||
// ping server
|
// ping server
|
||||||
_connectionCheckInterval = setInterval(() => {
|
_connectionCheckInterval = setInterval(() => {
|
||||||
|
|
@ -55,7 +53,7 @@ export default function useWebSockets(
|
||||||
socket.ping(null, true, (e: Error) => {
|
socket.ping(null, true, (e: Error) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
socket.close();
|
socket.close();
|
||||||
connectionStatusCallback(statusOptions ? statusOptions.pingErrorMessage : "Connection Lost");
|
emit("ws-status", 3);
|
||||||
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
|
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -66,11 +64,11 @@ export default function useWebSockets(
|
||||||
|
|
||||||
socket.on("message", (event: WebSocket.MessageEvent) => {
|
socket.on("message", (event: WebSocket.MessageEvent) => {
|
||||||
console.log("message received", event);
|
console.log("message received", event);
|
||||||
messageCallback(event.toString())
|
emit("ws-message", event.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("close", (code: number, reason: string) => {
|
socket.on("close", (code: number, reason: string) => {
|
||||||
connectionStatusCallback(statusOptions ? statusOptions.closeMessage : "Connection Closed");
|
emit("ws-status", 2);
|
||||||
clearInterval(_connectionCheckInterval);
|
clearInterval(_connectionCheckInterval);
|
||||||
if (code !== 1000 || reason !== 'session-logout') {
|
if (code !== 1000 || reason !== 'session-logout') {
|
||||||
setTimeout(() => {
|
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', () => {});
|
socket.on('error', () => {});
|
||||||
|
|
||||||
|
|
|
||||||
52
src/init.ts
52
src/init.ts
|
|
@ -6,11 +6,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { app, protocol, globalShortcut } from "electron";
|
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 { backgroundMitt } from '@/composables/useEmitter';
|
||||||
import { setWindowOpen, getWindowOpen } from './store';
|
|
||||||
import { terminateAudio } from './audio';
|
|
||||||
|
|
||||||
console.log('Starting Crimata electron app.');
|
console.log('Starting Crimata electron app.');
|
||||||
|
|
||||||
|
|
@ -21,37 +21,33 @@ protocol.registerSchemesAsPrivileged([
|
||||||
|
|
||||||
const isDev = require('electron-is-dev');
|
const isDev = require('electron-is-dev');
|
||||||
|
|
||||||
// Listen for window creation.
|
|
||||||
backgroundMitt.on('window-active', (state: boolean) => {
|
|
||||||
setWindowOpen(state)
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Start main process on ready */
|
/* Start main process on ready */
|
||||||
app.on("ready", async () => {
|
app.on("ready", async () =>
|
||||||
|
{
|
||||||
await main();
|
await main();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
app.on("will-quit", () => {
|
app.on("will-quit", () =>
|
||||||
terminateAudio();
|
{
|
||||||
|
TerminateAudio();
|
||||||
globalShortcut.unregisterAll();
|
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)
|
// When user clicks app icon (re-open)
|
||||||
app.on("activate", () => {
|
app.on("activate", () =>
|
||||||
if (!getWindowOpen()) createWindow();
|
{
|
||||||
});
|
if (!winState.open) CreateWindow();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Exit cleanly on request from parent process in development mode.
|
// Exit cleanly on request from parent process in development mode.
|
||||||
if (isDev) {
|
if (isDev)
|
||||||
process.on("SIGTERM", () => {
|
{
|
||||||
|
process.on("SIGTERM", () =>
|
||||||
|
{
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
src/io.ts
Normal file
34
src/io.ts
Normal 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 };
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
import { sendMessage } from '@/io';
|
||||||
import { playback } from "@/audio";
|
import { playback } from "@/audio";
|
||||||
import { onNavBar } from "@/window";
|
import { winState, onNavBar } from "@/window";
|
||||||
import { sendMessage } from '@/session';
|
|
||||||
import { setWindowFocus } from '@/store';
|
|
||||||
import { updateAppState } from "@/account";
|
import { updateAppState } from "@/account";
|
||||||
import { IpcListener } from "@/composables/useIpcMain"
|
import { IpcListener } from "@/composables/useIpcMain"
|
||||||
|
|
||||||
|
|
@ -26,12 +25,12 @@ export const navBarListener = new IpcListener({
|
||||||
listenerCallback: onNavBar
|
listenerCallback: onNavBar
|
||||||
});
|
});
|
||||||
|
|
||||||
export const windowFocusListener = new IpcListener<FocusPayload>({
|
export const windowFocusListener = new IpcListener<boolean>({
|
||||||
channel: POST_WINDOW_FOCUS,
|
channel: POST_WINDOW_FOCUS,
|
||||||
listenerCallback: ({ isFocused }) => setWindowFocus(isFocused)
|
listenerCallback: (state) => winState.focus = state
|
||||||
});
|
});
|
||||||
|
|
||||||
export const playbackListener = new IpcListener({
|
// export const playbackListener = new IpcListener({
|
||||||
channel: PLAYBACK_CHANNEL,
|
// channel: PLAYBACK_CHANNEL,
|
||||||
listenerCallback: playback
|
// listenerCallback: playback
|
||||||
});
|
// });
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { initTray } from '@/tray';
|
import { initTray } from '@/tray';
|
||||||
import createWindow from "@/window";
|
import { CreateWindow } from "@/window";
|
||||||
import initIpcMain from "@/ipc/index";
|
import initIpcMain from "@/ipc/index";
|
||||||
import { accountAuth } from "@/account";
|
import { accountAuth } from "@/account";
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default async function main() {
|
||||||
initTray();
|
initTray();
|
||||||
|
|
||||||
/* launch browser window */
|
/* launch browser window */
|
||||||
await createWindow();
|
await CreateWindow();
|
||||||
|
|
||||||
/* try to authenticate with token */
|
/* try to authenticate with token */
|
||||||
accountAuth();
|
accountAuth();
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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.
|
// Given index and length of content, return message child status.
|
||||||
export function calcChild (index: number, len: number) {
|
export function calcChild (index: number, len: number)
|
||||||
if (len === 1) {
|
{
|
||||||
|
if (len === 1)
|
||||||
|
{
|
||||||
return "none";
|
return "none";
|
||||||
} else if (index === 0) {
|
}
|
||||||
|
else if (index === 0)
|
||||||
|
{
|
||||||
return "first-child";
|
return "first-child";
|
||||||
} else if (index === len - 1) {
|
}
|
||||||
|
else if (index === len - 1)
|
||||||
|
{
|
||||||
return "last-child";
|
return "last-child";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return "middle-child";
|
return "middle-child";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="inputItem"
|
id="input-item"
|
||||||
class="input-item"
|
|
||||||
:class="{ playing: recording }"
|
:class="{ playing: recording }"
|
||||||
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
|
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- show the profile photo in the center of the input item -->
|
<!-- Show the profile photo in the center of the input item -->
|
||||||
<div>{{ profile.photo }}</div>
|
<!-- Background shadow animates on record -->
|
||||||
|
<div id="profile">{{ profile.photo }}</div>
|
||||||
<!-- Recording animation on space bar -->
|
|
||||||
<span v-if="recording" class="play"></span>
|
|
||||||
<span v-if="recording" class="pause"></span>
|
|
||||||
|
|
||||||
<!-- Show text input on key-down -->
|
<!-- Show text input on key-down -->
|
||||||
<input
|
<input
|
||||||
|
|
@ -30,9 +26,7 @@ import { profile } from "@/render/shared/profile";
|
||||||
import { recording } from "@/render/shared/audio";
|
import { recording } from "@/render/shared/audio";
|
||||||
import { animateAudioInput } from "@/render/components/controllers/helpers";
|
import { animateAudioInput } from "@/render/components/controllers/helpers";
|
||||||
|
|
||||||
import useTextInputController from
|
import useTextInputController from "@/render/components/controllers/inputItemControl";
|
||||||
"@/render/components/controllers/inputItem.control.text";
|
|
||||||
"@/render/components/controllers/inputItem.control.audio";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "InputItem",
|
name: "InputItem",
|
||||||
|
|
@ -67,7 +61,8 @@ export default defineComponent({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.input-item {
|
|
||||||
|
#input-item {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -93,51 +88,6 @@ export default defineComponent({
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
|
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 {
|
.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 {
|
#textInput {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,20 @@
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Message",
|
name: "Message",
|
||||||
|
|
||||||
props: ["modifier", "content", "context", "seen", "uid"],
|
props: ["modifier", "content", "context", "seen", "uid", "playback"],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Bubble,
|
Bubble,
|
||||||
Context
|
Context
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup(props) {
|
||||||
|
|
||||||
|
watch(props.playback, (val, _oldval) =>
|
||||||
|
{
|
||||||
|
boxShadow = val/200;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
|
|
||||||
const onFocus = () => {
|
const onFocus = () => {
|
||||||
postWindowFocus({ isFocused: true });
|
postWindowFocus(true);
|
||||||
postMessage({
|
postMessage({
|
||||||
intent: "focus",
|
intent: "focus",
|
||||||
params: { "focus": true },
|
params: { "focus": true },
|
||||||
|
|
@ -56,7 +56,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
postWindowFocus({ isFocused: false });
|
postWindowFocus(false);
|
||||||
postMessage({
|
postMessage({
|
||||||
intent: "focus",
|
intent: "focus",
|
||||||
params: { "focus": false },
|
params: { "focus": false },
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ export const invokeLogout = async (): Promise<void> => (
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const postPlayback = (payload: string): void => (
|
// export const postPlayback = (audio: Int16Array, uid: string): void => (
|
||||||
post('post-playback', payload)
|
// 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)
|
post('post-window-focus', payload)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import {
|
||||||
setMessages,
|
setMessages,
|
||||||
addMessage,
|
addMessage,
|
||||||
updateMessage,
|
updateMessage,
|
||||||
deleteMessage
|
deleteMessage,
|
||||||
|
animatePlayback
|
||||||
} from "./shared/messages";
|
} from "./shared/messages";
|
||||||
import { setAccount } from "./shared/account";
|
import { setAccount } from "./shared/account";
|
||||||
import { setProfile } from "./shared/profile";
|
import { setProfile } from "./shared/profile";
|
||||||
|
|
@ -23,8 +24,11 @@ const INIT_MESSAGES_CHANNEL = "init-messages";
|
||||||
const ADD_MESSAGE_CHANNEL = "add-message";
|
const ADD_MESSAGE_CHANNEL = "add-message";
|
||||||
const UPDATE_MESSAGE_CHANNEL = "update-message";
|
const UPDATE_MESSAGE_CHANNEL = "update-message";
|
||||||
const DELETE_MESSAGE_CHANNEL = "delete-message";
|
const DELETE_MESSAGE_CHANNEL = "delete-message";
|
||||||
|
|
||||||
const CONNECTION_STATUS_CHANNEL = "set-connection-status";
|
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({
|
export const setAccountListener = new IpcRendererListener({
|
||||||
channel: SET_ACCOUNT_CHANNEL,
|
channel: SET_ACCOUNT_CHANNEL,
|
||||||
|
|
@ -46,7 +50,6 @@ export const initMessagesListener = new IpcRendererListener({
|
||||||
listenerCallback: setMessages
|
listenerCallback: setMessages
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const addMessagesListener = new IpcRendererListener({
|
export const addMessagesListener = new IpcRendererListener({
|
||||||
channel: ADD_MESSAGE_CHANNEL,
|
channel: ADD_MESSAGE_CHANNEL,
|
||||||
listenerCallback: addMessage
|
listenerCallback: addMessage
|
||||||
|
|
@ -67,12 +70,12 @@ export const connectionStatusListener = new IpcRendererListener({
|
||||||
listenerCallback: setConnectionStatus
|
listenerCallback: setConnectionStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
export const recordingStatusListener = new IpcRendererListener({
|
export const recordingListener = new IpcRendererListener({
|
||||||
channel: RECORDING_STATUS_CHANNEL,
|
channel: RECORDING_CHANNEL,
|
||||||
listenerCallback: setRecording
|
listenerCallback: setRecording
|
||||||
});
|
});
|
||||||
|
|
||||||
export const animatePlaybackListener = new IpcRendererListener({
|
export const playbackListener = new IpcRendererListener({
|
||||||
channel: PLAYBACK_LISTENER,
|
channel: PLAYBACK_CHANNEL,
|
||||||
listenerCallback: animatePlayback
|
listenerCallback: animatePlayback
|
||||||
});
|
});
|
||||||
|
|
@ -4,7 +4,8 @@ import { ref } from "vue";
|
||||||
export const recording = ref();
|
export const recording = ref();
|
||||||
|
|
||||||
export const setRecording: IpcListenerCallback<boolean> = (payload) => {
|
export const setRecording: IpcListenerCallback<boolean> = (payload) => {
|
||||||
recording.value = payload;
|
console.log("Recording");
|
||||||
|
// recording.value = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
export const deleteMessage: IpcListenerCallback<string> = (payload) => {
|
||||||
const uid = payload as string;
|
const uid = payload as string;
|
||||||
|
|
||||||
|
|
@ -56,5 +74,6 @@ export default {
|
||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
addMessage,
|
addMessage,
|
||||||
updateMessage
|
updateMessage,
|
||||||
|
animatePlayback
|
||||||
};
|
};
|
||||||
|
|
|
||||||
111
src/session.ts
111
src/session.ts
|
|
@ -1,112 +1,97 @@
|
||||||
import { Notification } from 'electron';
|
import { Notification } from 'electron';
|
||||||
|
|
||||||
import UIState from "@/uiState";
|
import UIState from "@/ui";
|
||||||
import { config } from "@/config";
|
import { winState } from "@/window";
|
||||||
import { updateTray } from "@/tray";
|
import { ConnectToPlatform, DisconnectFromPlatform } from "@/io";
|
||||||
import { getWindowFocus, getWindowOpen } from '@/store';
|
|
||||||
import useWebsockets from "@/composables/useWebsockets";
|
|
||||||
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
|
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 contentQueue: Update[] = [];
|
||||||
|
|
||||||
const uiState = new UIState();
|
const uiState = new UIState();
|
||||||
|
|
||||||
const onMessageCallback = async (payload: string) =>
|
backgroundMitt.on("ws-message", (payload: string) =>
|
||||||
{
|
{
|
||||||
const message = JSON.parse(payload) as ClientProtocol;
|
const message = JSON.parse(payload) as ClientProtocol;
|
||||||
|
|
||||||
if (message.header == "init") {
|
if (message.header == "init")
|
||||||
uiState.set(message.body as Init);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content)
|
|
||||||
{
|
{
|
||||||
contentQueue.push(message.body)
|
uiState.set(message.body as Init);
|
||||||
}
|
}
|
||||||
else
|
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) */
|
/* launch a new session (the main process for authenticated users) */
|
||||||
export function launchSession(platformKey: string)
|
function LaunchSession(platformKey: string)
|
||||||
{
|
{
|
||||||
/* connect to the platform */
|
/* connect to the platform */
|
||||||
connect(config.PLATFORM_URL, platformKey);
|
ConnectToPlatform(platformKey);
|
||||||
|
|
||||||
initAudio();
|
InitAudio();
|
||||||
|
|
||||||
|
/* Process new content every 100ms */
|
||||||
processContentId = setInterval(() =>
|
processContentId = setInterval(() =>
|
||||||
{
|
{
|
||||||
if (getStreamState())
|
if (!streamState.rec || !streamState.pb)
|
||||||
{
|
{
|
||||||
if (contentQueue.length > 0)
|
if (contentQueue.length > 0)
|
||||||
{
|
{
|
||||||
const update = contentQueue.shift() as Update;
|
const update = contentQueue.shift() as Update;
|
||||||
|
|
||||||
/* Update the UI */
|
|
||||||
uiState.update(update);
|
uiState.update(update);
|
||||||
|
|
||||||
/* Spawn notification if window not in focus */
|
if (!winState.focus)
|
||||||
if (!getWindowFocus())
|
|
||||||
{
|
{
|
||||||
new Notification({
|
new Notification(update.notify as NotifyOptions).show();
|
||||||
title: "New Message",
|
|
||||||
subtitle: data.context.text,
|
|
||||||
body: data.content[0].text
|
|
||||||
}).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Play audio if audio */
|
let uid: string;
|
||||||
if (update.data.content[-1].audio)
|
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);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function endSession()
|
function EndSession()
|
||||||
{
|
{
|
||||||
terminateAudio();
|
TerminateAudio();
|
||||||
|
|
||||||
close(1000, 'session-logout');
|
DisconnectFromPlatform(1000, 'session-logout');
|
||||||
|
|
||||||
clearInterval(processContentId)
|
clearInterval(processContentId)
|
||||||
|
|
||||||
uiState.reset();
|
uiState.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { LaunchSession, EndSession };
|
||||||
|
|
|
||||||
58
src/store.ts
58
src/store.ts
|
|
@ -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");
|
|
||||||
}
|
|
||||||
20
src/types.ts
20
src/types.ts
|
|
@ -21,6 +21,13 @@ interface Init {
|
||||||
interface Update {
|
interface Update {
|
||||||
name: string;
|
name: string;
|
||||||
data: Profile | Message[] | Message | Annotation | 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 {
|
interface WindowState {
|
||||||
|
open: boolean;
|
||||||
|
focus: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WindowPosition {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
x: number | null;
|
x: number | null;
|
||||||
|
|
@ -89,6 +101,10 @@ interface AccountCredentials {
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FocusPayload {
|
||||||
|
isFocused: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Electron Ipc
|
* Electron Ipc
|
||||||
*/
|
*/
|
||||||
|
|
@ -125,7 +141,3 @@ interface IpcRendererEvent<T> {
|
||||||
payload: T | null;
|
payload: T | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FocusPayload {
|
|
||||||
isFocused: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class Profile implements Profile
|
||||||
photo = "";
|
photo = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UIState
|
export default class UI
|
||||||
{
|
{
|
||||||
profile: Profile;
|
profile: Profile;
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
209
src/window.ts
209
src/window.ts
|
|
@ -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 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";
|
import { config } from "@/config";
|
||||||
const { autoUpdater } = require('electron-updater');
|
import { store } from "@/composables/useStore";
|
||||||
|
import { backgroundMitt } from '@/composables/useEmitter';
|
||||||
|
|
||||||
let win: BrowserWindow | null;
|
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;
|
let winState: WindowState;
|
||||||
|
|
||||||
const loadWinState = (fileName: string): WindowState => {
|
function updateWindowPosition()
|
||||||
let state: WindowState;
|
{
|
||||||
|
if (win)
|
||||||
|
{
|
||||||
|
const bounds = win.getBounds();
|
||||||
|
const pos = win.getPosition();
|
||||||
|
|
||||||
try {
|
winPosition.width = bounds.width;
|
||||||
state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString());
|
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.
|
// Called when a NavBar button is pressed.
|
||||||
export const onNavBar: IpcListenerCallback<string> = (payload): void => {
|
function onNavBar(payload: string): void
|
||||||
if (win) {
|
{
|
||||||
if (payload === "close") {
|
if (win)
|
||||||
|
{
|
||||||
|
if (payload === "close")
|
||||||
|
{
|
||||||
win.close();
|
win.close();
|
||||||
} else {
|
backgroundMitt.removeAllListeners("ipc-renderer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
win.minimize();
|
win.minimize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util function to render message on ipc-renderer event.
|
// Util function to render message on ipc-renderer event.
|
||||||
const postToWindow = <T>(e: IpcRendererEvent<T>): void => {
|
const postToWindow = <T>(e: IpcRendererEvent<T>): void =>
|
||||||
if (win) {
|
{
|
||||||
|
if (win)
|
||||||
|
{
|
||||||
win.webContents.send(e.channel, e.payload);
|
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.
|
// function used by run.ts to create the main window.
|
||||||
export default async function createWindow(): Promise<void> {
|
async function CreateWindow(): Promise<void>
|
||||||
return new Promise((resolve, _reject) => {
|
{
|
||||||
|
return new Promise((resolve, _reject) =>
|
||||||
|
{
|
||||||
|
if (win) resolve(); /* no duplicate */
|
||||||
|
|
||||||
// avoid creating duplicate windows.
|
const pos = store.get("window-position");
|
||||||
if (win) resolve();
|
if (pos) winPosition = pos;
|
||||||
|
|
||||||
// Load the saved window state.
|
win = new BrowserWindow(
|
||||||
winState = loadWinState("window.json")
|
{
|
||||||
|
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.
|
if (win) winState.open = true;
|
||||||
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")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load the URL.
|
// Load the URL.
|
||||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
if (process.env.WEBPACK_DEV_SERVER_URL)
|
||||||
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); // dev
|
{
|
||||||
}
|
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); // dev
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
createProtocol("app");
|
||||||
|
win.loadURL("app://./index.html"); // prod
|
||||||
|
}
|
||||||
|
|
||||||
else {
|
// Handle window close.
|
||||||
createProtocol("app");
|
win.on("closed", () => win = null);
|
||||||
win.loadURL("app://./index.html"); // prod
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle window close.
|
// Save window state on resize and move.
|
||||||
win.on("closed", onWindowDismount);
|
win.on("moved", updateWindowPosition);
|
||||||
|
win.on("resize", updateWindowPosition);
|
||||||
|
|
||||||
// Save window state on resize and move.
|
// For showing/hiding the splash screen.
|
||||||
win.on("moved", saveWindowState);
|
win.webContents.on('did-finish-load', () =>
|
||||||
win.on("resize", saveWindowState);
|
{
|
||||||
|
if (win)
|
||||||
|
win.webContents.send('window-ready', { message: true });
|
||||||
|
|
||||||
win.once('ready-to-show', () => {
|
backgroundMitt.on("ipc-renderer", postToWindow);
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
|
||||||
if (win) win.show()
|
|
||||||
})
|
|
||||||
|
|
||||||
// For showing/hiding the splash screen.
|
resolve();
|
||||||
win.webContents.on('did-finish-load', () => {
|
}
|
||||||
if (win) win.webContents.send('window-ready', {
|
);
|
||||||
message: true
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
onWindowMount();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater.on('update-available', () => {
|
export { CreateWindow, onNavBar, winState };
|
||||||
if (win) win.webContents.send('update_available');
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on('update-downloaded', () => {
|
|
||||||
if (win) win.webContents.send('update_downloaded');
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue