Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e49e3f6a | ||
|
|
01f31a6c05 | ||
|
|
5811f89942 | ||
|
|
29fc9cc000 | ||
|
|
5a4c1a86f6 | ||
|
|
f2c69516df | ||
|
|
fa24a8d46a | ||
|
|
cda3bab739 | ||
|
|
531a32a5a5 | ||
|
|
21687f4b7f | ||
|
|
7283e3f6f9 | ||
|
|
b6b1f3e894 | ||
|
|
a791f973dc | ||
|
|
dd91a9ae9e | ||
|
|
468bb760a4 | ||
|
|
af421e6bfb | ||
|
|
cb40a811fa | ||
|
|
51c4ad8ed7 | ||
|
|
4afa716251 | ||
|
|
e13f90a6cc | ||
|
|
81b4d018fb | ||
|
|
308dbe2b4e | ||
|
|
d00fb0f3be |
BIN
build/icon.icns
Normal file
BIN
build/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
build/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
build/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
build/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
build/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
build/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
build/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
build/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
build/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
build/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
48
package.json
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "Crimata",
|
||||
"version": "0.9.9",
|
||||
"version": "1.0.0-beta.0",
|
||||
"private": true,
|
||||
"description": "Cross-platform messenger application built with electron, vue3, and TS.",
|
||||
"author": {
|
||||
"name": "Enrique Hernandez"
|
||||
"name": "Andrew Gundersen"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vue-cli-service electron:build",
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"main": "init.js",
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/speech": "^4.2.0",
|
||||
"@types/animejs": "^3.1.2",
|
||||
|
|
@ -27,8 +27,10 @@
|
|||
"electron-is-dev": "^2.0.0",
|
||||
"electron-store": "^8.0.0",
|
||||
"electron-updater": "^4.3.8",
|
||||
"iohook": "^0.9.3",
|
||||
"mitt": "^2.1.0",
|
||||
"naudiodon": "^2.3.2",
|
||||
"node-abi": "^2.30.0",
|
||||
"node-record-lpcm16": "^1.0.1",
|
||||
"update-electron-app": "^2.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
|
|
@ -66,25 +68,8 @@
|
|||
"spectron": "11.0.0",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-cli-plugin-electron-builder": "~2.0.0-rc.6",
|
||||
"vue-jest": "^5.0.0-0"
|
||||
},
|
||||
"vue": {
|
||||
"lintOnSave": false,
|
||||
"pluginOptions": {
|
||||
"electronBuilder": {
|
||||
"mainProcessFile": "./src/init.ts",
|
||||
"rendererProcessFile": "./src/render/main.ts",
|
||||
"preload": "./src/render/preload.ts",
|
||||
"builderOptions": {
|
||||
"appId": "com.crimata.ElectronUpdaterApp",
|
||||
"artifactName": "${productName}-${version}.${ext}",
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://gitlab.com/api/v4/projects/25637892/jobs/artifacts/main/raw/dist_electron?job=build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"vue-jest": "^5.0.0-0",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
|
|
@ -100,5 +85,24 @@
|
|||
"type": "git",
|
||||
"url": "https://gitlab.com/crimata/electron-app.git",
|
||||
"release": "latest"
|
||||
},
|
||||
"build": {
|
||||
"asarUnpack": "**/*.node"
|
||||
},
|
||||
"iohook": {
|
||||
"targets": [
|
||||
"node-88",
|
||||
"electron-80"
|
||||
],
|
||||
"platforms": [
|
||||
"win32",
|
||||
"darwin",
|
||||
"linux"
|
||||
],
|
||||
"arches": [
|
||||
"x64",
|
||||
"ia32",
|
||||
"i386"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
100
src/account.ts
|
|
@ -1,66 +1,79 @@
|
|||
|
||||
import { app } from "electron";
|
||||
import { parseAuthRes } from "@/auth";
|
||||
import { postAuth, postLogin, postLogout } from "@/api/account";
|
||||
import { endSession, launchSession } from "@/session";
|
||||
import { getToken, setToken, setProfile, getProfile, clearStore } from "./store";
|
||||
import { parseAuthRes } from "./auth";
|
||||
import { ipcEmit } from "@/composables/useEmitter";
|
||||
import { updateAppUI, launchSession, endSession } from "@/session";
|
||||
import { getToken, setToken, clearToken } from "./store";
|
||||
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
|
||||
|
||||
export const accountAuth = async (): Promise<Error | AuthState> => {
|
||||
|
||||
/* Either null or a crimataId */
|
||||
let account: string | null = null;
|
||||
|
||||
|
||||
export const accountAuth = async (): Promise<void> => {
|
||||
|
||||
/* attempt to get a login token from the store */
|
||||
const token = getToken();
|
||||
|
||||
/* try to login with it, returns platform secret and new token on success */
|
||||
if (token) {
|
||||
try {
|
||||
try {
|
||||
|
||||
if (token) {
|
||||
|
||||
// attempt to login with token
|
||||
const res = await postAuth(token);
|
||||
|
||||
const parsed = parseAuthRes(res);
|
||||
|
||||
setToken(parsed.token)
|
||||
setProfile(parsed.profile);
|
||||
// save jwt token and profile
|
||||
setToken(parsed.token);
|
||||
account = parsed.crimataId
|
||||
|
||||
return {
|
||||
profile: parsed.profile,
|
||||
token: parsed.token
|
||||
};
|
||||
// launch session
|
||||
launchSession(parsed.token);
|
||||
|
||||
} catch(e) {
|
||||
console.log('[ACCOUNT]', e);
|
||||
clearStore();
|
||||
throw(new Error('Failed to authenticate.'));
|
||||
} else throw(new Error('Failed to authenticate (no token).'));
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
clearToken();
|
||||
|
||||
} finally {
|
||||
|
||||
// push state changes to the frontend
|
||||
updateAppState();
|
||||
|
||||
}
|
||||
} else {
|
||||
throw(new Error('Unable to authenticate.'));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export const accountLogin: IpcHandlerCallback<AccountCredentials, Profile> = async (payload) => {
|
||||
const account = payload as AccountCredentials;
|
||||
export const accountLogin = async (payload: any): Promise<Error | void> => {
|
||||
|
||||
const creds = payload as AccountCredentials;
|
||||
|
||||
try {
|
||||
|
||||
// attempt login with email password
|
||||
const res = await postLogin(account.email, account.password);
|
||||
const res = await postLogin(creds.email, creds.password);
|
||||
const parsed = parseAuthRes(res);
|
||||
|
||||
// save jwt token and profile
|
||||
setToken(parsed.token);
|
||||
setProfile(parsed.profile);
|
||||
account = parsed.crimataId;
|
||||
|
||||
// launch session
|
||||
launchSession(parsed.token);
|
||||
|
||||
// return profile to renderer
|
||||
return parsed.profile;
|
||||
// push state changes to the frontend
|
||||
updateAppState();
|
||||
|
||||
return;
|
||||
|
||||
} catch(e) {
|
||||
clearStore();
|
||||
clearToken();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const accountLogout = async (): Promise<Error | void> => {
|
||||
|
||||
|
|
@ -68,8 +81,12 @@ export const accountLogout = async (): Promise<Error | void> => {
|
|||
// post logout to backend
|
||||
await postLogout();
|
||||
|
||||
// remove key and crimataId
|
||||
clearStore();
|
||||
// remove key and account
|
||||
clearToken();
|
||||
account = null;
|
||||
|
||||
// push account state to browser
|
||||
ipcEmit("set-account", account);
|
||||
|
||||
// kill crimata platform session
|
||||
endSession();
|
||||
|
|
@ -83,13 +100,24 @@ export const accountLogout = async (): Promise<Error | void> => {
|
|||
|
||||
}
|
||||
|
||||
export const updateAppState = (): void => {
|
||||
// logout when auth fails on platform end
|
||||
backgroundMitt.on("close-auth-fail", async (_payload: any) => {
|
||||
|
||||
const profile = getProfile();
|
||||
await accountLogout();
|
||||
|
||||
ipcEmit("set-profile", profile);
|
||||
});
|
||||
|
||||
// ipcEmit('messages') etc
|
||||
// emits state of account, version, and UI
|
||||
export const updateAppState = () => {
|
||||
|
||||
ipcEmit("set-account", account);
|
||||
|
||||
ipcEmit("set-version", app.getVersion());
|
||||
|
||||
updateAppUI();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
187
src/audio.ts
|
|
@ -1,187 +0,0 @@
|
|||
/* eslint @typescript-eslint/no-var-requires: "off" */
|
||||
|
||||
"use strict";
|
||||
|
||||
// where the audio goes
|
||||
let buffer: ArrayBuffer[] = [];
|
||||
|
||||
// place audio data in buffer
|
||||
export const collect: IpcListenerCallback<ArrayBuffer> = (chunk) => {
|
||||
if (chunk) buffer.push(chunk);
|
||||
}
|
||||
|
||||
// return audio and clear buffer
|
||||
export const flush = async () => {
|
||||
const bufferCopy = buffer;
|
||||
buffer = [];
|
||||
return bufferCopy;
|
||||
}
|
||||
|
||||
// import { backgroundMitt } from '@/modules/emitter';
|
||||
// const portAudio = require('naudiodon');
|
||||
|
||||
// // Audio in and out stream objects.
|
||||
// let ai: typeof portAudio.AudioIO | boolean = false;
|
||||
// let ao: typeof portAudio.AudioIO | boolean = false;
|
||||
|
||||
// // Whether activly recording.
|
||||
// let record = false;
|
||||
|
||||
// const audioContainer = {
|
||||
// input: '',
|
||||
// }
|
||||
|
||||
// const audioOptions = {
|
||||
// channelCount: 1,
|
||||
// sampleFormat: 16,
|
||||
// sampleRate: 16000,
|
||||
// deviceId: -1,
|
||||
// closeOnError: false,
|
||||
// }
|
||||
|
||||
// export const toggleRecord = (): void => { record = !record };
|
||||
|
||||
|
||||
// export const fetchAudioInput = (): Promise<Error | string> => (
|
||||
|
||||
// new Promise((resolve, reject) => {
|
||||
|
||||
// try {
|
||||
// resolve(audioContainer.input);
|
||||
// toggleRecord();
|
||||
// } catch (e) {
|
||||
// reject(new Error('Failed to fetch the audio.'))
|
||||
// }
|
||||
|
||||
// })
|
||||
// )
|
||||
|
||||
|
||||
// // Main audio function run by run.ts module.
|
||||
// export function initAudioIO(): void {
|
||||
// console.log("AUDIO:Starting io streams.")
|
||||
|
||||
// if (!ai) {
|
||||
|
||||
// // Initialize and start input stream.
|
||||
// ai = new portAudio.AudioIO({ inOptions: audioOptions });
|
||||
// ai.setEncoding("hex");
|
||||
// ai.start();
|
||||
|
||||
// // On each data chunk...
|
||||
// ai.on('data', (chunk: string) => {
|
||||
|
||||
// // If recording, we capture the data.
|
||||
// if (record) {
|
||||
// console.log('AUDIO:Recording...')
|
||||
// audioContainer.input += chunk;
|
||||
// }
|
||||
|
||||
// // Else, we don't capture and also clear audioContainer.
|
||||
// else {
|
||||
// if (audioContainer.input.length) {
|
||||
// audioContainer.input = "";
|
||||
// }
|
||||
// }
|
||||
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (!ao) {
|
||||
|
||||
// // Initialize and start input stream.
|
||||
// ao = new portAudio.AudioIO({ outOptions: audioOptions });
|
||||
// ao.start();
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// // ---Audio playback--------------------------------------------
|
||||
|
||||
// // Split Buffer into an array of len-sized Buffers.
|
||||
// function bufSplit(buf: Buffer, len: number): Array<Buffer> {
|
||||
// const chunks = [];
|
||||
// let i = 0;
|
||||
// let L = len;
|
||||
|
||||
// while(i < buf.byteLength) {
|
||||
// chunks.push(buf.slice(i, L));
|
||||
// i = L;
|
||||
// L += len;
|
||||
// }
|
||||
|
||||
// return chunks;
|
||||
// }
|
||||
|
||||
// // Audio playback.
|
||||
// export function play(input: string): void {
|
||||
|
||||
// // Format the audio.
|
||||
// const audio = bufSplit(
|
||||
// Buffer.from(input as string, 'hex'),
|
||||
// 8192
|
||||
// );
|
||||
|
||||
// // Called on end of write.
|
||||
// const callback = () => {
|
||||
|
||||
// // We stop audio playback anim.
|
||||
// backgroundMitt.emit('ipc-renderer', {
|
||||
// endpoint: 'stop-playback-anim'
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
// write();
|
||||
|
||||
// // Iterate through audio array and write buffers to portAudio writable.
|
||||
// function write() {
|
||||
// let chunk: Buffer;
|
||||
// let ok = true;
|
||||
// let i = 0;
|
||||
|
||||
// do {
|
||||
// chunk = audio[i];
|
||||
// if (i === audio.length - 1) {
|
||||
// // write last chunk.
|
||||
// ao.write(chunk, null, callback);
|
||||
// } else {
|
||||
// // check for backpreassure.
|
||||
// ok = ao.write(chunk, null);
|
||||
// }
|
||||
// i++;
|
||||
// } while (i < audio.length && ok);
|
||||
|
||||
// if (i < audio.length) {
|
||||
// // Had to stop early!
|
||||
// // Write some more once it drains.
|
||||
// ao.once('drain', write);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // -------------------------------------------------------------
|
||||
|
||||
// // Get's called on window close.
|
||||
// export async function stopStream() {
|
||||
// console.log("AUDIO:Stopping audio stream.")
|
||||
// if (ai) {
|
||||
// try {
|
||||
// await ai.quit()
|
||||
// } catch(e){
|
||||
// console.log('AUDIO: Failed to shutdown audio input.');
|
||||
// throw e;
|
||||
// }
|
||||
// }
|
||||
// if (ao) {
|
||||
// try {
|
||||
// await ao.quit()
|
||||
// } catch(e){
|
||||
// console.log('AUDIO: Failed to shutdown audio output.');
|
||||
// throw e;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
14
src/auth.ts
|
|
@ -1,13 +1,5 @@
|
|||
|
||||
export const parseAuthRes = (authRes: any) => {
|
||||
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
|
||||
const profile = authRes.data as Profile;
|
||||
return {
|
||||
token,
|
||||
profile
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const crimataId = authRes.data as string;
|
||||
return {token, crimataId}
|
||||
};
|
||||
|
|
@ -71,12 +71,10 @@ export class IpcListener<InputParam> implements IIpcListener<InputParam> {
|
|||
ipcMain.removeAllListeners(this.channel);
|
||||
}
|
||||
|
||||
private _onPost = (_e: IpcMainEvent, payload?: string | null): void => {
|
||||
private _onPost = (_e: IpcMainEvent, payload: InputParam): void => {
|
||||
|
||||
console.log(`[IPC] Post: ${this.channel}`);
|
||||
|
||||
const params = payload ? JSON.parse(payload) : null;
|
||||
this._listenerCallback(params);
|
||||
this._listenerCallback(payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
|
||||
|
||||
|
||||
export default class Canvas {
|
||||
|
||||
messages: Message[];
|
||||
|
||||
/* seed canvas with messages on init */
|
||||
constructor(messages: Message[]) {
|
||||
this.messages = messages;
|
||||
ipcEmit("seed-view", this.messages);
|
||||
}
|
||||
|
||||
/* add a new message to the canvas */
|
||||
add(message: Message) {
|
||||
this.messages.push(message);
|
||||
ipcEmit("update-view", message);
|
||||
}
|
||||
|
||||
/* update an existing message */
|
||||
update(message: Message) {
|
||||
|
||||
/* get the target message */
|
||||
let target_message = this.messages.filter((m: Message) => {
|
||||
return m.uid = message.uid;
|
||||
})[0];
|
||||
|
||||
/* replace the target message */
|
||||
if (target_message) {
|
||||
target_message = message;
|
||||
ipcEmit("update-view", message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,7 +11,13 @@ let _connectionCheckInterval: ReturnType<typeof setTimeout>;
|
|||
|
||||
export default function useWebSockets(
|
||||
messageCallback: (message: string) => void,
|
||||
connectionStatusCallback: (alive: boolean) => void,
|
||||
connectionStatusCallback: (status: string) => void,
|
||||
statusOptions?: {
|
||||
openMessage: string;
|
||||
pongMessage: string;
|
||||
closeMessage: string;
|
||||
pingErrorMessage: string;
|
||||
},
|
||||
) {
|
||||
|
||||
let socket: WebSocket;
|
||||
|
|
@ -41,13 +47,15 @@ export default function useWebSockets(
|
|||
|
||||
socket.send(JSON.stringify({key: secret}));
|
||||
|
||||
connectionStatusCallback(statusOptions ? statusOptions.openMessage : "Connection Opened");
|
||||
|
||||
// ping server
|
||||
_connectionCheckInterval = setInterval(() => {
|
||||
|
||||
socket.ping(null, true, (e: Error) => {
|
||||
if (e) {
|
||||
socket.close();
|
||||
connectionStatusCallback(false);
|
||||
connectionStatusCallback(statusOptions ? statusOptions.pingErrorMessage : "Connection Lost");
|
||||
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
|
||||
}
|
||||
});
|
||||
|
|
@ -61,24 +69,28 @@ export default function useWebSockets(
|
|||
messageCallback(event.toString())
|
||||
});
|
||||
|
||||
socket.on("close", (event: WebSocket.CloseEvent) => {
|
||||
connectionStatusCallback(false);
|
||||
socket.on("close", (code: number, reason: string) => {
|
||||
connectionStatusCallback(statusOptions ? statusOptions.closeMessage : "Connection Closed");
|
||||
clearInterval(_connectionCheckInterval);
|
||||
if (!event.wasClean) {
|
||||
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
|
||||
if (code !== 1000 || reason !== 'session-logout') {
|
||||
setTimeout(() => {
|
||||
connect(socketUrl, secret), _reconnectTimeout
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
socket.on("pong", () => connectionStatusCallback(true));
|
||||
socket.on("pong", () => connectionStatusCallback(statusOptions ? statusOptions.pongMessage : "Pong"));
|
||||
|
||||
socket.on('error', () => {});
|
||||
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
const close = (code: number, reason: string) => {
|
||||
if (socket) {
|
||||
socket.close();
|
||||
socket.close(code, reason);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
connect,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ backgroundMitt.on('window-active', (state: boolean) => {
|
|||
win = state;
|
||||
});
|
||||
|
||||
|
||||
/* Start main process on ready */
|
||||
app.on("ready", async () => {
|
||||
await main();
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import { accountLogin, accountLogout, accountProfile } from "@/account";
|
||||
import { accountLogin, accountLogout } from "@/account";
|
||||
import { IpcHandler } from "@/composables/useIpcMain";
|
||||
import { flush } from "@/audio";
|
||||
|
||||
|
||||
const LOGIN_CHANNEL = "invoke-account-login";
|
||||
const LOGOUT_CHANNEL = "invoke-account-logout";
|
||||
const GET_AUDIO_CHANNEL = "invoke-audio-flush";
|
||||
|
||||
export const loginHandler = new IpcHandler({
|
||||
channel: LOGIN_CHANNEL,
|
||||
|
|
@ -19,10 +17,3 @@ export const logoutHandler = new IpcHandler({
|
|||
channel: LOGOUT_CHANNEL,
|
||||
handlerCallback: accountLogout
|
||||
});
|
||||
|
||||
export const getAudioHandler = new IpcHandler({
|
||||
channel: GET_AUDIO_CHANNEL,
|
||||
handlerCallback: flush
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
|
||||
import { IpcListener } from "@/composables/useIpcMain"
|
||||
import { sendMessage } from '@/session';
|
||||
import { collect } from "@/audio";
|
||||
import { updateAppState } from "@/account";
|
||||
import { onNavBar } from "@/window";
|
||||
|
||||
const CLIENT_MESSAGE_CHANNEL = "post-session-send"
|
||||
const GET_AUDIO_CHANNEL = "post-audio-collect";
|
||||
const APP_MOUNT_CHANNEL = "post-app-mount";
|
||||
const NAV_BAR_CHANNEL = "post-nav-bar";
|
||||
|
||||
export const messageListener = new IpcListener<Message>({
|
||||
export const messageListener = new IpcListener<Raw | Request>({
|
||||
channel: CLIENT_MESSAGE_CHANNEL,
|
||||
listenerCallback: sendMessage
|
||||
});
|
||||
|
||||
export const audioChunkListener = new IpcListener<ArrayBuffer>({
|
||||
channel: GET_AUDIO_CHANNEL,
|
||||
listenerCallback: collect
|
||||
});
|
||||
|
||||
export const appMountListener = new IpcListener<null>({
|
||||
channel: APP_MOUNT_CHANNEL,
|
||||
listenerCallback: updateAppState
|
||||
});
|
||||
|
||||
export const navBarListener = new IpcListener({
|
||||
channel: NAV_BAR_CHANNEL,
|
||||
listenerCallback: onNavBar
|
||||
});
|
||||
|
|
|
|||
49
src/main.ts
|
|
@ -10,30 +10,51 @@
|
|||
*/
|
||||
|
||||
import initIpcMain from "@/ipc/index";
|
||||
import { accountAuth, updateAppState } from "./account";
|
||||
import { launchSession } from "./session";
|
||||
import { accountAuth } from "./account";
|
||||
import createWindow from "./window";
|
||||
|
||||
let authState: AuthState | null;
|
||||
// Short cut functionality dependencies
|
||||
import { globalShortcut } from 'electron'
|
||||
const nodeAbi = require('node-abi')
|
||||
// const ioHook = require('iohook');
|
||||
// import ioHook from '../node_modules/iohook/builds/electron-v80-darwin-x64/build/Release/iohook.node'
|
||||
// import ioHook from 'iohook'
|
||||
|
||||
// TODO: load from store
|
||||
const recordShortcut = 'CommandOrControl+S';
|
||||
|
||||
export default async function main() {
|
||||
|
||||
// need to get abi for iohook keyboard event listener
|
||||
console.log('node abi', nodeAbi.getAbi('7.2.0', 'node'))
|
||||
// '51'
|
||||
console.log('electron abi', nodeAbi.getAbi('1.4.10', 'electron'))
|
||||
|
||||
// listen for keyup event
|
||||
// ioHook.on('keyup', (event: any) => {
|
||||
// // emit pause recording
|
||||
// // send audio message
|
||||
// console.log('KEYUP')
|
||||
// console.log(event);
|
||||
// });
|
||||
|
||||
// start keyup hook; pass true for DEBUG mode.
|
||||
// ioHook.start(true);
|
||||
|
||||
globalShortcut.register(recordShortcut, () => {
|
||||
// emit begin recording
|
||||
console.log('begin recording.')
|
||||
});
|
||||
|
||||
|
||||
|
||||
/* initiate controls for frontend to use when needed */
|
||||
initIpcMain();
|
||||
|
||||
/* launch browser window */
|
||||
await createWindow();
|
||||
|
||||
try {
|
||||
authState = await accountAuth() as AuthState;
|
||||
} catch(e) {
|
||||
console.log('AUTH:', e);
|
||||
authState = null;
|
||||
} finally {
|
||||
if (authState) {
|
||||
launchSession(authState.token as string);
|
||||
}
|
||||
updateAppState();
|
||||
}
|
||||
/* try to authenticate with token */
|
||||
await accountAuth();
|
||||
|
||||
}
|
||||
|
|
|
|||
22
src/profile.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { ipcEmit } from "@/composables/useEmitter";
|
||||
|
||||
|
||||
export default class ProfileHandler {
|
||||
|
||||
profile: Profile;
|
||||
|
||||
constructor(profile: Profile) {
|
||||
this.profile = profile;
|
||||
this.emit();
|
||||
}
|
||||
|
||||
emit() {
|
||||
ipcEmit("set-profile", this.profile);
|
||||
}
|
||||
|
||||
update(update: Update) {
|
||||
this.profile = update.data as Profile;
|
||||
this.emit()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,10 @@
|
|||
<template>
|
||||
<!-- Only render when profile has been set -->
|
||||
<main id="app" v-if="authComplete">
|
||||
<main id="app" v-if="ready">
|
||||
|
||||
<Header />
|
||||
|
||||
<Messenger
|
||||
v-if="profile"
|
||||
/>
|
||||
|
||||
<!-- Main Components
|
||||
-->
|
||||
<Messenger v-if="account"/>
|
||||
<Login v-else />
|
||||
|
||||
</main>
|
||||
|
|
@ -22,14 +17,13 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { defineComponent, onMounted } from "vue";
|
||||
import { IpcRendererEvent } from "electron";
|
||||
|
||||
import Splash from "@/render/components/splash.vue";
|
||||
import Messenger from "@/render/components/messenger.vue";
|
||||
import Login from "@/render/components/login.vue";
|
||||
import Header from "@/render/components/header.vue";
|
||||
|
||||
import { profile, authComplete } from "@/render/composables/useProfile";
|
||||
import { ready, account, setAccount, clearAccount } from "@/render/shared/account";
|
||||
import { postAppMount } from "@/render/ipc";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
@ -43,11 +37,15 @@ export default defineComponent({
|
|||
|
||||
setup() {
|
||||
|
||||
onMounted(() => postAppMount());
|
||||
onMounted(() => {
|
||||
|
||||
postAppMount()
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
authComplete,
|
||||
profile
|
||||
account,
|
||||
ready
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
14
src/render/assets/connectLogo.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24.87" height="23.001" viewBox="0 0 24.87 23.001">
|
||||
<g id="Group_541" data-name="Group 541" transform="translate(-4127 -507)">
|
||||
<g id="Group_33" data-name="Group 33" transform="translate(4127 507)">
|
||||
<g id="Group_32" data-name="Group 32" transform="translate(0 0)">
|
||||
<g id="Group_31" data-name="Group 31" transform="translate(0 11.948)">
|
||||
<circle id="Ellipse_50" data-name="Ellipse 50" cx="5.527" cy="5.527" r="5.527" transform="translate(0 0)" fill="#383838"/>
|
||||
<circle id="Ellipse_51" data-name="Ellipse 51" cx="5.527" cy="5.527" r="5.527" transform="translate(13.817 0)" fill="#383838"/>
|
||||
</g>
|
||||
<circle id="Ellipse_52" data-name="Ellipse 52" cx="5.527" cy="5.527" r="5.527" transform="translate(6.898 0)" fill="#383838"/>
|
||||
<path id="Path_150" data-name="Path 150" d="M36.27,83.67" transform="translate(-30.733 -66.196)" fill="#ff0"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
10
src/render/assets/crimataAvatar.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
|
||||
<g id="Group_604" data-name="Group 604" transform="translate(-366 -464)">
|
||||
<circle id="Ellipse_69" data-name="Ellipse 69" cx="15" cy="15" r="15" transform="translate(366 464)" fill="#fff"/>
|
||||
<g id="Group_603" data-name="Group 603">
|
||||
<circle id="Ellipse_50" data-name="Ellipse 50" cx="3.116" cy="3.116" r="3.116" transform="translate(374 479.252)" fill="#383838"/>
|
||||
<circle id="Ellipse_51" data-name="Ellipse 51" cx="3.116" cy="3.116" r="3.116" transform="translate(381.789 479.252)" fill="#383838"/>
|
||||
<circle id="Ellipse_52" data-name="Ellipse 52" cx="3.116" cy="3.116" r="3.116" transform="translate(377.889 472.517)" fill="#383838"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 766 B |
7
src/render/assets/settingsIcon.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="5" viewBox="0 0 25 5">
|
||||
<g id="Group_611" data-name="Group 611" transform="translate(-4032 499)">
|
||||
<circle id="Ellipse_71" data-name="Ellipse 71" cx="2.5" cy="2.5" r="2.5" transform="translate(4032 -499)" fill="#9b9b9b"/>
|
||||
<circle id="Ellipse_72" data-name="Ellipse 72" cx="2.5" cy="2.5" r="2.5" transform="translate(4042 -499)" fill="#9b9b9b"/>
|
||||
<circle id="Ellipse_73" data-name="Ellipse 73" cx="2.5" cy="2.5" r="2.5" transform="translate(4052 -499)" fill="#9b9b9b"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 553 B |
|
|
@ -1,53 +1,18 @@
|
|||
<template>
|
||||
<div :class="`${type}-message`">
|
||||
|
||||
<!-- message bubble -->
|
||||
<div :class="`${type}-${child}-bubble`">
|
||||
<div class="notification-dot"/>
|
||||
<div class="error-dot"/>
|
||||
{{ text }}
|
||||
</div>
|
||||
|
||||
<!-- message context -->
|
||||
<span class="context">
|
||||
<div :class="`${type}-icon`"/>
|
||||
{{ context }}
|
||||
</span>
|
||||
|
||||
<div :class="`bubble ${modifier}-bubble ${modifier}-${child}`">
|
||||
<div v-show="!seen" class="notify"></div>
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
|
||||
import { defineComponent, ref, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "Bubble",
|
||||
|
||||
props: ["text", "context", "type", "position"],
|
||||
|
||||
setup() {
|
||||
|
||||
const seen = ref(false);
|
||||
/* initialize seen state */
|
||||
if (document.visibilityState === "visible") {
|
||||
seen.value = true;
|
||||
} else seen.value = false;
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
if(seen.value)
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
seen.value = true
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
seen
|
||||
};
|
||||
|
||||
}
|
||||
props: ["modifier", "text", "child", "seen"],
|
||||
|
||||
})
|
||||
|
||||
|
|
@ -55,277 +20,79 @@
|
|||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.message {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
.bubble {
|
||||
position: relative;
|
||||
max-width: 66vw;
|
||||
font-family: "SF Pro Text";
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-radius: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.ai-bubble {
|
||||
background-color: #FFFFFF;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.client-bubble {
|
||||
color: white;
|
||||
background-color: #58C4FD;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.ai-first-child {
|
||||
border-bottom-left-radius: 9px;
|
||||
}
|
||||
|
||||
.ai-middle-child {
|
||||
border-top-left-radius: 9px;
|
||||
border-bottom-left-radius: 9px;
|
||||
}
|
||||
|
||||
.ai-last-child {
|
||||
border-top-left-radius: 9px;
|
||||
}
|
||||
|
||||
.client-first-child {
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
|
||||
.client-middle-child {
|
||||
border-top-right-radius: 9px;
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
|
||||
.client-last-child {
|
||||
border-top-right-radius: 9px;
|
||||
}
|
||||
|
||||
.ai-none-child, .client-none-child {
|
||||
border-top-right-radius: 9px;
|
||||
}
|
||||
|
||||
.notify {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: #58D9FF;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
border: 2px solid #EBEBEB;
|
||||
// transform: scale(0);
|
||||
|
||||
// animation-name: notify-anim;
|
||||
// animation-duration: 5s;
|
||||
}
|
||||
|
||||
@keyframes notify-anim {
|
||||
0%, 90% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.message:first-child {
|
||||
margin-top: 55px;
|
||||
}
|
||||
|
||||
.message:last-child {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.sf-message {
|
||||
@extend .message;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.ai-message, .fr-message {
|
||||
@extend .message;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
position: relative;
|
||||
max-width: 66vw;
|
||||
font-family: "SF Pro Text";
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.sf-bubble {
|
||||
@extend .bubble;
|
||||
background-color: #58c4fd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.firstChildMessage {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.middleChildMessage {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.lastChildMessage {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#aiMessage {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#sfMessage {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
#frMessage {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.messageBox {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// Animate on render.
|
||||
animation-name: appear;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
#aiMessageBox {
|
||||
margin-left: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#sfMessageBox {
|
||||
margin-right: 20px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
#frMessageBox {
|
||||
margin-left: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
position: relative;
|
||||
|
||||
max-width: 66vw;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
font-family: "SF Pro Text";
|
||||
font-size: 14px;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
10% {
|
||||
transform: scale(0.3);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
#aiBubble {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#sfBubble {
|
||||
background-color: #58c4fd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#frBubble {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.sf-firstChildBubble {
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
|
||||
.sf-middleChildBubble {
|
||||
border-top-right-radius: 9px;
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
|
||||
.sf-lastChildBubble {
|
||||
border-top-right-radius: 9px;
|
||||
}
|
||||
|
||||
.ai-firstChildBubble, .fr-firstChildBubble {
|
||||
border-bottom-left-radius: 9px;
|
||||
}
|
||||
|
||||
.ai-middleChildBubble, .fr-middleChildBubble {
|
||||
border-top-left-radius: 9px;
|
||||
border-bottom-left-radius: 9px;
|
||||
}
|
||||
|
||||
.ai-lastChildBubble, .fr-lastChildBubble {
|
||||
border-top-left-radius: 9px;
|
||||
}
|
||||
|
||||
.notify {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: #58D9FF;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
border: 2px solid #EBEBEB;
|
||||
100% {
|
||||
transform: scale(0);
|
||||
|
||||
animation-name: notify-anim;
|
||||
animation-duration: 5s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notify-anim {
|
||||
0%, 90% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
.context {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-family: "SF Compact Display";
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.photo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
margin-right: 5px;
|
||||
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
// Apply for audio message playback.
|
||||
.playing {
|
||||
animation-name: circle1;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes circle1 {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.contentLoader {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.contentLoaderDot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
|
||||
margin: 2px;
|
||||
border-radius: 2.5px;
|
||||
background-color: #357CA2;
|
||||
}
|
||||
|
||||
.questionMark {
|
||||
font-weight: 900;
|
||||
color: #357CA2;
|
||||
}
|
||||
|
||||
// .divider {
|
||||
// width: 100vw;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
|
||||
// font-family: "SF Compact Display";
|
||||
// font-size: 12px;
|
||||
// font-weight: bold;
|
||||
// color: #9B9B9B;
|
||||
|
||||
// margin-bottom: 18px;
|
||||
// }
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</style>
|
||||
103
src/render/components/connectionStatus.vue
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div id="container">
|
||||
<img id="logo" src="@/render/assets/connectLogo.svg">
|
||||
<div id="connect">
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent, onMounted, watch } from 'vue'
|
||||
import { hiddenState, useAnim, status, show, hide } from "@/render/shared/connectionStatus";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ConnectionStatus",
|
||||
|
||||
setup() {
|
||||
|
||||
watch(status, (val, _oldval) => {
|
||||
if (val === "Connected" || val === "Nominal") {
|
||||
if (!hiddenState) hide();
|
||||
} else {
|
||||
if (hiddenState)
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(useAnim);
|
||||
|
||||
return {
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
#container {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 54px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#connect {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
shape {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: #48E065;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@extend shape;
|
||||
position: relative;
|
||||
transform: translateX(-15px);
|
||||
animation: flashing 1s infinite linear alternate;
|
||||
animation-delay: .25s;
|
||||
}
|
||||
|
||||
.dot::before, .dot::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.dot::before {
|
||||
@extend shape;
|
||||
left: -9px;
|
||||
animation: flashing 1s infinite alternate;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.dot::after {
|
||||
@extend shape;
|
||||
left: 9px;
|
||||
animation: flashing 1s infinite alternate;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
@keyframes flashing {
|
||||
0% {
|
||||
background-color: #48E065;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
background-color: #9B9B9B;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
83
src/render/components/context.vue
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
|
||||
<span :class="`context ${modifier}-context`">
|
||||
|
||||
<div v-if="context.img" class="avatar">
|
||||
<img v-if="context.img === 'crimata'" :src="require('@/render/assets/crimataAvatar.svg')">
|
||||
<div v-else>{{ context.img }}</div>
|
||||
</div>
|
||||
|
||||
{{ context.text == false ? " " : context.text }}
|
||||
|
||||
</span>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "Context",
|
||||
|
||||
props: ["modifier", "context", "uid"],
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.context {
|
||||
position: relative;
|
||||
min-height: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: "SF Compact Display";
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.ai-context {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.client-context {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.playing {
|
||||
animation-name: message-playback-anim;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes message-playback-anim {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { ref } from "vue";
|
||||
import { ref, Ref } from "vue";
|
||||
import anime from "animejs";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
|
||||
export function animateTextInput () {
|
||||
|
|
@ -86,17 +85,15 @@ export function animateAudioInput () {
|
|||
|
||||
}
|
||||
|
||||
|
||||
export function newMessage ({
|
||||
text=false,
|
||||
audio=false,
|
||||
context=false,
|
||||
uid=uuidv4()
|
||||
}) {
|
||||
return {
|
||||
text: text,
|
||||
audio: audio,
|
||||
context: context,
|
||||
uid: uid
|
||||
};
|
||||
}
|
||||
// Given index and length of content, return message child status.
|
||||
export function calcChild (index: number, len: number) {
|
||||
if (len === 1) {
|
||||
return "none";
|
||||
} else if (index === 0) {
|
||||
return "first-child";
|
||||
} else if (index === len - 1) {
|
||||
return "last-child";
|
||||
} else {
|
||||
return "middle-child";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { onMounted, onUnmounted, ref, Ref } from "vue";
|
||||
import { postMessage } from "@/render/ipc";
|
||||
import { newMessage, animateAudioInput } from "./helpers";
|
||||
import { animateAudioInput } from "./helpers";
|
||||
import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
|
||||
|
||||
export default function useAudioInputController (typing: Ref) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
|
||||
import { postMessage } from "@/render/ipc";
|
||||
import { newMessage, animateTextInput } from "./helpers";
|
||||
import { animateTextInput } from "./helpers";
|
||||
|
||||
|
||||
export default function useTextInputController(elementX: Ref) {
|
||||
|
|
@ -36,11 +36,12 @@ export default function useTextInputController(elementX: Ref) {
|
|||
if (textInput) {
|
||||
|
||||
// Send it to the backend for processing.
|
||||
const message = newMessage({
|
||||
text: false
|
||||
});
|
||||
const message: Raw = {
|
||||
text: textInput.value,
|
||||
audio: false
|
||||
};
|
||||
|
||||
// postMessage(message);
|
||||
postMessage(message);
|
||||
|
||||
clearInput()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { ref } from 'vue';
|
||||
import useScroll from "@/render/composables/useScroll";
|
||||
|
||||
const messagesRef = ref();
|
||||
|
||||
/* seed the canvas with messages */
|
||||
const seedCanvas = (messages: Message[]) => {
|
||||
messagesRef.value = messages;
|
||||
}
|
||||
|
||||
const addMessage = (message: Message) => {
|
||||
messagesRef.value.push(message);
|
||||
}
|
||||
|
||||
const updateMessage = (message: Message) => {
|
||||
|
||||
let target_message = messagesRef.value.filter((m: Message) => {
|
||||
return m.uid = message.uid;
|
||||
})[0];
|
||||
|
||||
if (target_message) {
|
||||
target_message = message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function useMessages() {
|
||||
|
||||
const { updateScrollRef, adjustScroll } = useScroll("messenger");
|
||||
|
||||
return {
|
||||
messagesRef,
|
||||
seedCanvas,
|
||||
addMessage,
|
||||
updateMessage
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -16,16 +16,17 @@
|
|||
|
||||
// import { postNavBarExit, postNavBarMin } from "@/render/ipc";
|
||||
import { defineComponent } from "vue";
|
||||
import { postNavBar } from "@/render/ipc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Header",
|
||||
setup() {
|
||||
const postNavBarExit = () => {};
|
||||
const postNavBarMin = () => {};
|
||||
return {
|
||||
postNavBarExit,
|
||||
postNavBarMin
|
||||
}
|
||||
const postNavBarExit = () => postNavBar('close');
|
||||
const postNavBarMin = () => postNavBar('min');
|
||||
return {
|
||||
postNavBarExit,
|
||||
postNavBarMin
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
:class="{ playing: recording }"
|
||||
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
|
||||
>
|
||||
<div>{{ initials }}</div>
|
||||
|
||||
<!-- show the profile photo in the center of the input item -->
|
||||
<div>{{ profile.photo }}</div>
|
||||
|
||||
<!-- Recording animation on space bar -->
|
||||
<span v-if="recording" class="play"></span>
|
||||
|
|
@ -28,18 +29,16 @@
|
|||
|
||||
import { defineComponent } from "vue";
|
||||
import draggify from "@/render/composables/useDraggify";
|
||||
|
||||
import { profile } from "@/render/shared/profile";
|
||||
|
||||
import useTextInputController from
|
||||
"@/render/components/controllers/inputItem.control.text";
|
||||
import useAudioInputController from
|
||||
// import useAudioInputController from
|
||||
"@/render/components/controllers/inputItem.control.audio";
|
||||
|
||||
export default defineComponent({
|
||||
name: "InputItem",
|
||||
|
||||
props: ["initials"],
|
||||
|
||||
setup() {
|
||||
|
||||
// Default values for position.
|
||||
|
|
@ -51,9 +50,11 @@ export default defineComponent({
|
|||
|
||||
// Controllers for text and audio.
|
||||
const { typing } = useTextInputController(elementX)
|
||||
const { recording } = useAudioInputController(typing)
|
||||
// const { recording } = useAudioInputController(typing)
|
||||
const recording = false;
|
||||
|
||||
return {
|
||||
profile,
|
||||
elementX,
|
||||
elementY,
|
||||
recording
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
<template>
|
||||
<form id="login" @submit.prevent="submitForm">
|
||||
|
||||
|
||||
<!-- login title -->
|
||||
<div id="loginTitle">
|
||||
<div>Login</div>
|
||||
</div>
|
||||
|
||||
<!-- username and password forms -->
|
||||
<div id="loginBox">
|
||||
<div id="form">
|
||||
<!-- username -->
|
||||
<div class="inputBox">
|
||||
<input class="input" type="text" v-model="usr" />
|
||||
|
|
@ -24,18 +18,15 @@
|
|||
</div>
|
||||
|
||||
<!-- submit button; position: fixed -->
|
||||
<button class="submitButton button" type="submit">Submit</button>
|
||||
<button class="submit button" type="submit">Login</button>
|
||||
|
||||
</form>
|
||||
|
||||
<!-- back to login button: position: fixed -->
|
||||
<!-- <button class="createAccountButton button" @click.prevent="switchView">Create account</button> -->
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { setProfile } from '@/render/composables/useProfile';
|
||||
import { invokeLogin } from "@/render/ipc";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
@ -50,21 +41,26 @@ export default defineComponent({
|
|||
const submitForm = async () => {
|
||||
|
||||
try {
|
||||
|
||||
const profile = await invokeLogin({
|
||||
email: usr.value,
|
||||
password: pwd.value
|
||||
}) as Profile;
|
||||
|
||||
setProfile(profile)
|
||||
|
||||
|
||||
await invokeLogin({email: usr.value, password: pwd.value});
|
||||
} catch(e) {
|
||||
shake("form");
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const shake = (elementId: string) => {
|
||||
const el = document.getElementById(elementId);
|
||||
|
||||
if (el)
|
||||
el.classList.remove("shake");
|
||||
|
||||
setTimeout(() => {
|
||||
if (el) el.classList.add("shake");
|
||||
}, 250);
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
usr,
|
||||
pwd,
|
||||
|
|
@ -87,16 +83,7 @@ export default defineComponent({
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#loginTitle {
|
||||
font-family: "SF Pro Text";
|
||||
min-width: 181px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#loginBox {
|
||||
#form {
|
||||
font-family: "SF Compact Display";
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -132,7 +119,7 @@ export default defineComponent({
|
|||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
.submit {
|
||||
font-family: "SF Compact Display";
|
||||
position: fixed;
|
||||
margin-top: 141px;
|
||||
|
|
@ -144,21 +131,10 @@ export default defineComponent({
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submitButton:active {
|
||||
.submit:active {
|
||||
background-color: #4296C3;
|
||||
}
|
||||
|
||||
.createAccountButton {
|
||||
position: absolute;
|
||||
border: none;
|
||||
background-color: #EBEBEB;
|
||||
text-decoration: none;
|
||||
color: #58C4FD;
|
||||
font-weight: bold;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
outline: none;
|
||||
|
|
@ -169,12 +145,29 @@ export default defineComponent({
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
margin-bottom: 30px;
|
||||
font-family: "SF Compact Display";
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: #F53737;
|
||||
.shake {
|
||||
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
99
src/render/components/message.vue
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div :id="uid" :class="`${modifier}-message`">
|
||||
|
||||
<Bubble
|
||||
v-for="(val, index) in content"
|
||||
:modifier="modifier"
|
||||
:text="val.text"
|
||||
:child="calcChild(index, content.length)"
|
||||
:seen="seen"
|
||||
/>
|
||||
|
||||
<Context
|
||||
:modifier="modifier"
|
||||
:context="context"
|
||||
:uid="uid"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import Bubble from "@/render/components/bubble.vue";
|
||||
import Context from "@/render/components/context.vue";
|
||||
import { calcChild } from "@/render/components/controllers/helpers";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Message",
|
||||
|
||||
props: ["modifier", "content", "context", "seen", "uid"],
|
||||
|
||||
components: {
|
||||
Bubble,
|
||||
Context
|
||||
},
|
||||
|
||||
setup() {
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
const messengerEl = document.getElementById("messenger");
|
||||
|
||||
if (messengerEl)
|
||||
messengerEl.dispatchEvent(new CustomEvent('adjust-scroll'))
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
calcChild
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.message {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
animation-name: message-init-anim;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
@keyframes message-init-anim {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.message:first-child {
|
||||
margin-top: 55px;
|
||||
}
|
||||
|
||||
.message:last-child {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.client-message {
|
||||
@extend .message;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
@extend .message;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
|
@ -1,17 +1,20 @@
|
|||
<template>
|
||||
|
||||
<!-- Position fixed items -->
|
||||
<InputItem :initials="profile.initials"/>
|
||||
<Settings />
|
||||
<ConnectionStatus />
|
||||
<div id="recIcon" />
|
||||
<InputItem />
|
||||
|
||||
<!-- List of message bubbles. -->
|
||||
<div id="messenger">
|
||||
<Bubble
|
||||
<Message
|
||||
v-for="message in messages"
|
||||
:text="message.content.text"
|
||||
:modifier="message.modifier"
|
||||
:content="message.content"
|
||||
:context="message.context"
|
||||
:key="message[0]"
|
||||
:seen="message.seen"
|
||||
:uid="message.uid"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -22,21 +25,60 @@
|
|||
import { defineComponent, onMounted, onUnmounted } from "vue";
|
||||
import InputItem from "@/render/components/inputItem.vue";
|
||||
import Settings from "@/render/components/settings.vue";
|
||||
import Bubble from "@/render/components/bubble.vue";
|
||||
import Message from "@/render/components/message.vue";
|
||||
import ConnectionStatus from "./connectionStatus.vue";
|
||||
|
||||
import { profile } from "@/render/composables/useProfile";
|
||||
import { messages } from "@/render/composables/useMessages";
|
||||
import { profile } from "@/render/shared/profile";
|
||||
import { messages } from "@/render/shared/messages";
|
||||
import useScroll from "@/render/composables/useScroll";
|
||||
import { postMessage } from "@/render/ipc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Messenger",
|
||||
|
||||
components: {
|
||||
ConnectionStatus,
|
||||
InputItem,
|
||||
Settings,
|
||||
Bubble
|
||||
Message,
|
||||
},
|
||||
|
||||
setup() {
|
||||
|
||||
const onFocus = () => {
|
||||
postMessage({
|
||||
intent: "focus",
|
||||
params: { "focus": true },
|
||||
epic: false,
|
||||
confidence: 1.0
|
||||
} as PlatformRequest);
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
postMessage({
|
||||
intent: "focus",
|
||||
params: { "focus": false },
|
||||
epic: false,
|
||||
confidence: 1.0
|
||||
} as PlatformRequest);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
useScroll("messenger");
|
||||
|
||||
window.addEventListener('blur', onBlur);
|
||||
window.addEventListener('focus', onFocus);
|
||||
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
window.removeEventListener("blur", onBlur);
|
||||
window.removeEventListener("focus", onFocus);
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
messages,
|
||||
profile
|
||||
|
|
|
|||
|
|
@ -1,35 +1,38 @@
|
|||
<template>
|
||||
|
||||
<!-- Settings icon. -->
|
||||
<button
|
||||
class="settingsIcon"
|
||||
v-if="toggleSettings == false"
|
||||
@click="onActive"
|
||||
>
|
||||
<div class="settingsButtonDot"></div>
|
||||
<div class="settingsButtonDot"></div>
|
||||
<div class="settingsButtonDot"></div>
|
||||
</button>
|
||||
<!-- Settings content -->
|
||||
<div v-if="toggleSettings" id="settings" >
|
||||
|
||||
<!-- Settings div. -->
|
||||
<div id="settings" v-show="toggleSettings">
|
||||
</div>
|
||||
<p class="account">{{ `Hi, ${account}` }}</p>
|
||||
|
||||
<button
|
||||
v-if="toggleSettings"
|
||||
class="settingsOption"
|
||||
class="logout"
|
||||
@click="onLogout"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
|
||||
<p class="version">{{ `Version ${version}` }}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Icon -->
|
||||
<button
|
||||
v-else
|
||||
class="icon"
|
||||
@click="onActive"
|
||||
>
|
||||
<img src="@/render/assets/settingsIcon.svg">
|
||||
</button>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { clearProfile } from "@/render/composables/useProfile"
|
||||
import { invokeLogout } from "@/render/ipc";
|
||||
import { version } from "@/render/shared/version";
|
||||
import { ready, account, setAccount, clearAccount } from "@/render/shared/account";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Settings",
|
||||
|
|
@ -58,7 +61,6 @@
|
|||
|
||||
try {
|
||||
await invokeLogout();
|
||||
clearProfile();
|
||||
} catch(e) {
|
||||
console.log('error')
|
||||
}
|
||||
|
|
@ -66,6 +68,8 @@
|
|||
}
|
||||
|
||||
return {
|
||||
account,
|
||||
version,
|
||||
onActive,
|
||||
toggleSettings,
|
||||
onLogout
|
||||
|
|
@ -79,67 +83,52 @@
|
|||
|
||||
#settings {
|
||||
position: fixed;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(235, 235, 235, 0.75);
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background-color: #EBEBEB;
|
||||
|
||||
opacity: 0.75;
|
||||
|
||||
z-index: 4;
|
||||
|
||||
animation-name: appear;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
from {
|
||||
opacity: 0
|
||||
background-color: rgba(235, 235, 235, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 0.75
|
||||
background-color: rgba(235, 235, 235, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
.settingsIcon {
|
||||
.icon {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
padding: 5px;
|
||||
margin-right: 20px;
|
||||
margin-top: 19px;
|
||||
|
||||
z-index: 2;
|
||||
background-color: Transparent;
|
||||
}
|
||||
|
||||
.settingsIcon:hover {
|
||||
.icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settingsButtonDot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
|
||||
margin: 2px;
|
||||
border-radius: 2.5px;
|
||||
background-color: #9B9B9B;
|
||||
|
||||
.account {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.settingsOption {
|
||||
.logout {
|
||||
font-family: "SF Compact Display";
|
||||
background-color: #B7B7B7;
|
||||
padding: 10px 30px;
|
||||
|
|
@ -149,16 +138,21 @@
|
|||
border: none;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.settingsOption:hover {
|
||||
.logout:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.version {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 75%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export class IpcRendererListener<InputParam> implements IIpcListener<InputParam>
|
|||
}
|
||||
|
||||
private _onPost = (_e: IpcRendererEvent, payload: InputParam): void => {
|
||||
console.log(`[IPC] Post: ${this.channel}`);
|
||||
console.log(`[IPC] Post: ${this.channel}`, payload);
|
||||
this._listenerCallback(payload);
|
||||
}
|
||||
|
||||
|
|
@ -34,16 +34,16 @@ export class IpcRendererListener<InputParam> implements IIpcListener<InputParam>
|
|||
|
||||
export default function useIpcRenderer () {
|
||||
|
||||
const invoke = async (endpoint: string, payload: any) => {
|
||||
const invoke = async <T>(endpoint: string, payload: T) => {
|
||||
try {
|
||||
const res = await window.ipcRenderer.invoke(endpoint, payload);
|
||||
const res = await window.ipcRenderer.invoke(endpoint, payload? JSON.stringify(payload) : null);
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const post = (endpoint: string, payload: any) => {
|
||||
const post = <T>(endpoint: string, payload: T) => {
|
||||
window.ipcRenderer.send(endpoint, payload);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
// shared
|
||||
import { ref, Ref } from "vue";
|
||||
import useScroll from "@/render/composables/useScroll";
|
||||
|
||||
export const messages: Ref<Array<Message>> = ref([]);
|
||||
|
||||
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
|
||||
messages.value = payload as Array<Message>;
|
||||
}
|
||||
|
||||
export const addMessage: IpcListenerCallback<Message> = (payload) => {
|
||||
messages.value.push(payload as Message);
|
||||
}
|
||||
|
||||
export const updateMessage: IpcListenerCallback<Message> = (payload) => {
|
||||
const message = payload as Message;
|
||||
|
||||
let targetMessage = messages.value.filter((m: Message) => {
|
||||
return m.uid = message.uid;
|
||||
})[0];
|
||||
|
||||
if (targetMessage) {
|
||||
targetMessage = message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
messages,
|
||||
setMessages,
|
||||
addMessage,
|
||||
updateMessage
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// shared
|
||||
import { ref } from "vue";
|
||||
|
||||
export const profile = ref();
|
||||
|
||||
export const authComplete = ref(false);
|
||||
|
||||
export const setProfile: IpcListenerCallback<Profile | null> = (payload) => {
|
||||
payload ? profile.value = payload : clearProfile();
|
||||
showRender();
|
||||
};
|
||||
|
||||
export const clearProfile = () => {
|
||||
profile.value = null;
|
||||
};
|
||||
|
||||
export const showRender = () => {
|
||||
authComplete.value = true;
|
||||
};
|
||||
|
||||
export default {
|
||||
setProfile,
|
||||
clearProfile,
|
||||
profile,
|
||||
showRender,
|
||||
authComplete,
|
||||
};
|
||||
|
|
@ -1,29 +1,25 @@
|
|||
|
||||
export default function useScroll(elementId: string) {
|
||||
|
||||
export default function useScroll(element: string) {
|
||||
const el = document.getElementById(elementId);
|
||||
|
||||
let isScrolledToBottom: boolean;
|
||||
const view = document.getElementById(element)
|
||||
|
||||
// Update isScrolledToBottom
|
||||
const updateScrollRef = () => {
|
||||
if (view) isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1;
|
||||
return isScrolledToBottom;
|
||||
const isBottom = () => {
|
||||
if (el) {
|
||||
return el.scrollHeight - el.clientHeight <= el.scrollTop + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust scroll after we add content to the messenger.
|
||||
const adjustScroll = () => {
|
||||
if (view) {
|
||||
view.scrollTo({
|
||||
top: view.scrollHeight - view.clientHeight,
|
||||
if (el) {
|
||||
el.scrollTo({
|
||||
top: el.scrollHeight - el.clientHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updateScrollRef,
|
||||
adjustScroll,
|
||||
};
|
||||
if (el)
|
||||
el.addEventListener('adjust-scroll', adjustScroll);
|
||||
window.addEventListener('resize', adjustScroll);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ const { post, invoke } = useIpc();
|
|||
|
||||
|
||||
export const invokeLogin = async (
|
||||
payload: LoginPayload
|
||||
payload: AccountCredentials
|
||||
): Promise<Profile | Error> => (
|
||||
await invoke('invoke-account-login', JSON.stringify(payload))
|
||||
await invoke('invoke-account-login', payload)
|
||||
);
|
||||
|
||||
export const invokeLogout = async (): Promise<void> => (
|
||||
|
|
@ -41,11 +41,7 @@ export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
|
|||
*
|
||||
*/
|
||||
|
||||
export const invokeSession = async (cid: string): Promise<Profile | Error> => (
|
||||
await invoke("messenger-init", cid)
|
||||
);
|
||||
|
||||
export const postMessage = (payload: Message): void => (
|
||||
export const postMessage = (payload: Raw | PlatformRequest): void => (
|
||||
post('post-session-send', payload)
|
||||
);
|
||||
|
||||
|
|
@ -53,6 +49,25 @@ export const postAppMount = (): void => (
|
|||
post('post-app-mount', null)
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Nav bar endpoints
|
||||
*
|
||||
*/
|
||||
|
||||
export const postNavBar = (payload: string): void => (
|
||||
post('post-nav-bar', payload)
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Window focus endpoint
|
||||
*
|
||||
*/
|
||||
|
||||
export const postWindowBlur = (payload: { isFocused: boolean }): void => (
|
||||
post('post-window-focus', payload)
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,13 +1,38 @@
|
|||
|
||||
import { IpcRendererListener } from "./composables/useIpcRend"
|
||||
import { setProfile } from "./composables/useProfile";
|
||||
import { setMessages, addMessage, updateMessage } from "./composables/useMessages";
|
||||
/*
|
||||
* Shared state imports
|
||||
* */
|
||||
import {
|
||||
setMessages,
|
||||
addMessage,
|
||||
updateMessage,
|
||||
deleteMessage
|
||||
} from "./shared/messages";
|
||||
import { setAccount } from "./shared/account";
|
||||
import { setProfile } from "./shared/profile";
|
||||
import { setVersion } from "./shared/version";
|
||||
import { setConnectionStatus } from "./shared/connectionStatus";
|
||||
|
||||
const SET_ACCOUNT_CHANNEL = "set-account";
|
||||
const SET_PROFILE_CHANNEL = "set-profile";
|
||||
const SET_VERSION_CHANNEL = "set-version";
|
||||
|
||||
const INIT_MESSAGES_CHANNEL = "init-messages";
|
||||
const ADD_MESSAGE_CHANNEL = "add-message";
|
||||
const UPDATE_MESSAGE_CHANNEL = "update-message";
|
||||
const DELETE_MESSAGE_CHANNEL = "delete-message";
|
||||
const CONNECTION_STATUS_CHANNEL = "set-connection-status";
|
||||
|
||||
export const setAccountListener = new IpcRendererListener({
|
||||
channel: SET_ACCOUNT_CHANNEL,
|
||||
listenerCallback: setAccount
|
||||
});
|
||||
|
||||
export const setVersionListener = new IpcRendererListener({
|
||||
channel: SET_VERSION_CHANNEL,
|
||||
listenerCallback: setVersion
|
||||
});
|
||||
|
||||
export const setProfileListener = new IpcRendererListener({
|
||||
channel: SET_PROFILE_CHANNEL,
|
||||
|
|
@ -30,3 +55,12 @@ export const updateMessagesListener = new IpcRendererListener({
|
|||
listenerCallback: updateMessage
|
||||
});
|
||||
|
||||
export const deleteMessagesListener = new IpcRendererListener({
|
||||
channel: DELETE_MESSAGE_CHANNEL,
|
||||
listenerCallback: deleteMessage
|
||||
});
|
||||
|
||||
export const connectionStatusListener = new IpcRendererListener({
|
||||
channel: CONNECTION_STATUS_CHANNEL,
|
||||
listenerCallback: setConnectionStatus
|
||||
});
|
||||
26
src/render/shared/account.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// shared
|
||||
import { ref } from "vue";
|
||||
|
||||
export const ready = ref(false);
|
||||
export const account = ref();
|
||||
|
||||
export const setAccount: IpcListenerCallback<string | null> = (payload) => {
|
||||
payload ? account.value = payload : clearAccount();
|
||||
showRender();
|
||||
};
|
||||
|
||||
export const clearAccount = () => {
|
||||
console.log("Clearing account");
|
||||
account.value = null;
|
||||
}
|
||||
|
||||
const showRender = () => {
|
||||
ready.value = true;
|
||||
}
|
||||
|
||||
export default {
|
||||
ready,
|
||||
account,
|
||||
setAccount,
|
||||
clearAccount
|
||||
};
|
||||
65
src/render/shared/connectionStatus.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// shared
|
||||
import { ref } from "vue";
|
||||
import anime from "animejs";
|
||||
|
||||
export const status = ref("");
|
||||
|
||||
export let hiddenState = true;
|
||||
|
||||
let animateLogo: anime.AnimeInstance;
|
||||
let animateConnect: anime.AnimeInstance;
|
||||
|
||||
export function useAnim() {
|
||||
|
||||
animateLogo = anime({
|
||||
targets: '#logo',
|
||||
translateX: [0, 15],
|
||||
duration: 500,
|
||||
autoplay: false,
|
||||
easing: 'easeInOutBack'
|
||||
});
|
||||
|
||||
animateConnect = anime({
|
||||
targets: '#connect',
|
||||
translateX: [0, -15],
|
||||
opacity: [0, 1],
|
||||
duration: 500,
|
||||
autoplay: false,
|
||||
easing: 'easeInOutBack'
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function show() {
|
||||
if (animateLogo && animateConnect) {
|
||||
animateLogo.direction = "normal";
|
||||
animateConnect.direction = "normal";
|
||||
animateLogo.play();
|
||||
animateConnect.play();
|
||||
hiddenState = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function hide() {
|
||||
if (animateLogo && animateConnect) {
|
||||
animateLogo.direction = "reverse";
|
||||
animateConnect.direction = "reverse";
|
||||
animateLogo.play();
|
||||
animateConnect.play();
|
||||
hiddenState = true;
|
||||
}
|
||||
}
|
||||
|
||||
export const setConnectionStatus: IpcListenerCallback<string> = (payload) => {
|
||||
status.value = payload;
|
||||
};
|
||||
|
||||
export default {
|
||||
hiddenState,
|
||||
useAnim,
|
||||
status,
|
||||
show,
|
||||
hide
|
||||
};
|
||||
|
||||
|
||||
60
src/render/shared/messages.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// shared
|
||||
import { ref, Ref } from "vue";
|
||||
|
||||
export const messages: Ref<Array<Message>> = ref([]);
|
||||
|
||||
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
|
||||
messages.value = payload;
|
||||
}
|
||||
|
||||
export const addMessage: IpcListenerCallback<Message> = (payload) => {
|
||||
messages.value.push(payload as Message);
|
||||
}
|
||||
|
||||
export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
|
||||
const annotation = payload as Annotation;
|
||||
|
||||
for (const i in messages.value) {
|
||||
|
||||
if (messages.value[i].uid == annotation.uid) {
|
||||
|
||||
if (annotation.name == "content") {
|
||||
messages.value[i].content = annotation.data as Content[];
|
||||
}
|
||||
|
||||
if (annotation.name == "context") {
|
||||
messages.value[i].context = annotation.data as Context;
|
||||
}
|
||||
|
||||
if (annotation.name == "seen") {
|
||||
messages.value[i].seen = annotation.data as boolean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const deleteMessage: IpcListenerCallback<string> = (payload) => {
|
||||
const uid = payload as string;
|
||||
|
||||
for (let i = 0; i < messages.value.length; i++) {
|
||||
if (messages.value[i].uid === uid) {
|
||||
messages.value.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const resetMessages = () => {
|
||||
messages.value = [];
|
||||
}
|
||||
|
||||
export default {
|
||||
messages,
|
||||
setMessages,
|
||||
addMessage,
|
||||
updateMessage
|
||||
};
|
||||
19
src/render/shared/profile.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// shared
|
||||
import { ref } from "vue";
|
||||
|
||||
class Profile implements Profile{
|
||||
crimata_id = "";
|
||||
name = "";
|
||||
photo = "";
|
||||
}
|
||||
|
||||
export const profile = ref(new Profile());
|
||||
|
||||
export const setProfile: IpcListenerCallback<Profile> = (payload) => {
|
||||
profile.value = payload;
|
||||
};
|
||||
|
||||
export default {
|
||||
profile,
|
||||
setProfile
|
||||
};
|
||||
12
src/render/shared/version.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// shared
|
||||
import { ref } from "vue";
|
||||
|
||||
export const version = ref();
|
||||
|
||||
export const setVersion: IpcListenerCallback<Profile | null> = (payload) => {
|
||||
version.value = payload;
|
||||
};
|
||||
|
||||
export default {
|
||||
setVersion
|
||||
}
|
||||
|
|
@ -1,84 +1,55 @@
|
|||
|
||||
|
||||
|
||||
|
||||
// import useAudio from "@/audio";
|
||||
import { ipcEmit } from "@/composables/useEmitter";
|
||||
import UIState from "@/uiState";
|
||||
import { config } from "@/config";
|
||||
import useWebsockets from "./composables/useWebsockets";
|
||||
import {config} from "@/config";
|
||||
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
|
||||
|
||||
/* data structure of messages that's tied to the UI */
|
||||
const uiState: any | null = null;
|
||||
// UI state
|
||||
const uiState = new UIState()
|
||||
|
||||
/* start and stop audio functionality */
|
||||
// const { initAudio, closeAudio } = useAudio();
|
||||
|
||||
const isInitMessage = (message: any): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const deauthenticate = (): void => {
|
||||
console.log('deauthenticating')
|
||||
};
|
||||
|
||||
const isAddMessage = (message: any): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Controls for interfacing with the platform.
|
||||
* Takes an onMessage callback which we define below.
|
||||
*/
|
||||
// let audio = new Audio();
|
||||
|
||||
|
||||
const onMessageCallback = (payload: string) => {
|
||||
|
||||
const message = JSON.parse(payload);
|
||||
console.log(typeof message);
|
||||
|
||||
/* if the platform fails to authenticate, we must back down */
|
||||
if (message === "CLOSE_AUTH_FAIL") {
|
||||
deauthenticate();
|
||||
return;
|
||||
if (payload === "CLOSE_AUTH_FAIL") {
|
||||
backgroundMitt.emit("close-auth-fail");
|
||||
return
|
||||
}
|
||||
|
||||
ipcEmit("add-message", message)
|
||||
return
|
||||
const message = JSON.parse(payload) as ClientProtocol;
|
||||
|
||||
/* on init, platform sends state, used to init canvas */
|
||||
if (isInitMessage(message)) {
|
||||
ipcEmit("init-messages", message)
|
||||
}
|
||||
|
||||
|
||||
else if (isAddMessage(message)) {
|
||||
ipcEmit("add-messages", message)
|
||||
}
|
||||
|
||||
else {
|
||||
ipcEmit("update-messages", message)
|
||||
}
|
||||
if (message.header === "init") {
|
||||
uiState.set(message.body as Init);
|
||||
} else uiState.update(message.body as Update);
|
||||
|
||||
}
|
||||
|
||||
const onConnectionStatusCallback = (alive: boolean) => {
|
||||
// console.log('[Session]: Connection Alive: ', alive);
|
||||
// ipcEmit('connection-state', alive);
|
||||
const connectionStatusCallback = (status: string) => {
|
||||
ipcEmit('set-connection-status', status);
|
||||
}
|
||||
|
||||
const statusOptions = {
|
||||
openMessage: "Connected",
|
||||
pongMessage: "Nominal",
|
||||
closeMessage: "Reconnecting",
|
||||
pingErrorMessage: "Connection Lost",
|
||||
}
|
||||
|
||||
const { connect, send, close } = useWebsockets(
|
||||
onMessageCallback,
|
||||
onConnectionStatusCallback
|
||||
connectionStatusCallback,
|
||||
statusOptions
|
||||
);
|
||||
|
||||
/* send a message to the platform */
|
||||
export function sendMessage<Message>(message: Message): void {
|
||||
|
||||
/* socket send */
|
||||
send(message);
|
||||
|
||||
export const updateAppUI = (): void => {
|
||||
uiState.emit();
|
||||
}
|
||||
|
||||
export function sendMessage<Message>(message: Raw | Request): void {
|
||||
send(message);
|
||||
}
|
||||
|
||||
/* launch a new session (the main process for authenticated users) */
|
||||
export function launchSession(platformKey: string) {
|
||||
|
|
@ -86,16 +57,15 @@ export function launchSession(platformKey: string) {
|
|||
/* connect to the platform */
|
||||
connect(config.PLATFORM_URL, platformKey);
|
||||
|
||||
/* initialize the audio streams */
|
||||
// initAudio();
|
||||
// initialize the audio streams
|
||||
// audio.record();
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function endSession() {
|
||||
|
||||
// closeAudio();
|
||||
close(1000, 'session-logout');
|
||||
|
||||
close();
|
||||
uiState.reset();
|
||||
|
||||
}
|
||||
|
|
|
|||
25
src/store.ts
|
|
@ -3,8 +3,7 @@ const Store = require('electron-store');
|
|||
const schema = {
|
||||
token: {
|
||||
type: 'string',
|
||||
},
|
||||
profile: {}
|
||||
}
|
||||
};
|
||||
|
||||
const store = new Store({
|
||||
|
|
@ -12,20 +11,14 @@ const store = new Store({
|
|||
encryptionKey: "super user test"
|
||||
});
|
||||
|
||||
export const getToken = (): string | undefined => (store.get("token"));
|
||||
export const setToken = (token: string): void => {
|
||||
store.set("token", token);
|
||||
}
|
||||
|
||||
export const clearToken = (): void => (store.delete("token"));
|
||||
|
||||
export const setToken = (token: string): void => (store.set('token', token));
|
||||
|
||||
export const setProfile = (profile: Profile): Profile => (store.set('profile', profile));
|
||||
|
||||
export const getProfile = (): Profile => (store.get('profile'));
|
||||
|
||||
export const clearProfile = (): void => (store.delete('profile'));
|
||||
|
||||
export const clearStore = (): void => {
|
||||
clearToken();
|
||||
clearProfile();
|
||||
export const getToken = (): string | undefined => {
|
||||
return store.get("token");
|
||||
}
|
||||
|
||||
export const clearToken = (): void => {
|
||||
store.delete("token");
|
||||
}
|
||||
90
src/types.ts
|
|
@ -1,14 +1,74 @@
|
|||
|
||||
/**
|
||||
* Messages from platform except for CLOSE_AUTH_FAIL
|
||||
*/
|
||||
interface ClientProtocol {
|
||||
header: string;
|
||||
body: Init | Update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial message from platform to seed uiState
|
||||
*/
|
||||
interface Init {
|
||||
profile: Profile;
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update something in uiState
|
||||
*/
|
||||
interface Update {
|
||||
name: string;
|
||||
data: Profile | Message[] | Message | Annotation | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard message in Messenger
|
||||
*/
|
||||
interface Message {
|
||||
text: boolean | string;
|
||||
context: boolean | string;
|
||||
audio: boolean | string;
|
||||
type: 1 | 2 | 3;
|
||||
modifier: string;
|
||||
content: Content[];
|
||||
context: Context;
|
||||
seen: boolean;
|
||||
uid: string;
|
||||
}
|
||||
|
||||
interface ViewMessage extends Message {
|
||||
child: string;
|
||||
interface Content {
|
||||
text: string;
|
||||
audio: string | boolean;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
img: string | boolean;
|
||||
text: string | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific attribute in a given message
|
||||
*/
|
||||
interface Annotation {
|
||||
name: string;
|
||||
data: Context | Content[] | boolean;
|
||||
uid: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send raw, uncategorized data to the platform
|
||||
*/
|
||||
interface Raw {
|
||||
text: string | boolean;
|
||||
audio: string | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The oppisite of Raw, when you know exactly what needs to get done
|
||||
*/
|
||||
interface PlatformRequest {
|
||||
intent: string;
|
||||
params: any;
|
||||
epic: string | boolean;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
interface WindowState {
|
||||
|
|
@ -19,19 +79,9 @@ interface WindowState {
|
|||
}
|
||||
|
||||
interface Profile {
|
||||
crimataId: string;
|
||||
alias: string;
|
||||
initials: string;
|
||||
}
|
||||
|
||||
interface LoginPayload {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
profile: Profile | null;
|
||||
token: string | null;
|
||||
crimata_id: string;
|
||||
name: string;
|
||||
photo: string;
|
||||
}
|
||||
|
||||
interface AccountCredentials {
|
||||
|
|
@ -47,7 +97,7 @@ interface IpcHandlerCallback<I, O> {
|
|||
}
|
||||
|
||||
interface IpcListenerCallback<T> {
|
||||
(payload: T | null): void;
|
||||
(payload: T): void;
|
||||
}
|
||||
|
||||
interface IIpcHandler<I, O> {
|
||||
|
|
|
|||
95
src/uiState.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { ipcEmit } from "@/composables/useEmitter";
|
||||
|
||||
class Profile implements Profile{
|
||||
crimata_id = "";
|
||||
name = "";
|
||||
photo = "";
|
||||
}
|
||||
|
||||
export default class UIState {
|
||||
|
||||
profile: Profile;
|
||||
messages: Message[];
|
||||
|
||||
constructor() {
|
||||
this.profile = new Profile();
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
emit() {
|
||||
ipcEmit("set-profile", this.profile);
|
||||
ipcEmit("init-messages", this.messages);
|
||||
}
|
||||
|
||||
set (message: Init) {
|
||||
this.profile = message.profile;
|
||||
this.messages = message.messages;
|
||||
this.emit();
|
||||
}
|
||||
|
||||
update(update: Update) {
|
||||
|
||||
if (update.name === "profile") {
|
||||
this.profile = update.data as Profile;
|
||||
ipcEmit("set-profile", this.profile);
|
||||
}
|
||||
|
||||
else if (update.name === "add") {
|
||||
this.messages.push(update.data as Message);
|
||||
ipcEmit("add-message", update.data);
|
||||
}
|
||||
|
||||
else if (update.name === "annotate") {
|
||||
this._annotate(update.data as Annotation);
|
||||
ipcEmit("update-message", update.data);
|
||||
}
|
||||
|
||||
else {
|
||||
this._delete(update.data as string);
|
||||
ipcEmit("delete-message", update.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.profile = new Profile();
|
||||
this.messages = [];
|
||||
this.emit();
|
||||
}
|
||||
|
||||
_annotate(annotation: Annotation) {
|
||||
|
||||
for (const i in this.messages) {
|
||||
|
||||
if (this.messages[i].uid == annotation.uid) {
|
||||
|
||||
if (annotation.name == "content") {
|
||||
this.messages[i].content = annotation.data as Content[];
|
||||
}
|
||||
|
||||
if (annotation.name == "context") {
|
||||
this.messages[i].context = annotation.data as Context;
|
||||
}
|
||||
|
||||
if (annotation.name == "seen") {
|
||||
this.messages[i].seen = annotation.data as boolean;
|
||||
}
|
||||
|
||||
ipcEmit("update-message", annotation);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_delete(uid: string) {
|
||||
for (let i = 0; i < this.messages.length; i++) {
|
||||
if (this.messages[i].uid === uid) {
|
||||
this.messages.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,20 +33,20 @@ const loadWinState = (fileName: string): WindowState => {
|
|||
}
|
||||
|
||||
// Called when a NavBar button is pressed.
|
||||
const onNavBar = (_event: any, action: string): void => {
|
||||
export const onNavBar: IpcListenerCallback<string> = (payload): void => {
|
||||
if (win) {
|
||||
if (action === "close") {
|
||||
win.close()
|
||||
if (payload === "close") {
|
||||
win.close();
|
||||
} else {
|
||||
win.minimize()
|
||||
win.minimize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Util function to render message on ipc-renderer event.
|
||||
const postToWindow = <T>(event: IpcRendererEvent<T>): void => {
|
||||
const postToWindow = <T>(e: IpcRendererEvent<T>): void => {
|
||||
if (win) {
|
||||
win.webContents.send(event.channel, event.payload);
|
||||
win.webContents.send(e.channel, e.payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,10 +75,6 @@ const onWindowMount = (): void => {
|
|||
// Must tell initApp that window exists.
|
||||
backgroundMitt.emit('window-active', true);
|
||||
|
||||
// Handle win nav-bar event.
|
||||
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", postToWindow);
|
||||
|
|
|
|||
29
vue.config.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const path = require("path");
|
||||
const nodeExternals = require('webpack-node-externals');
|
||||
|
||||
module.exports = {
|
||||
lintOnSave: false,
|
||||
pluginOptions: {
|
||||
electronBuilder: {
|
||||
|
||||
mainProcessFile: "./src/init.ts",
|
||||
rendererProcessFile: "./src/render/main.ts",
|
||||
preload: "./src/render/preload.ts",
|
||||
|
||||
builderOptions: {
|
||||
"appId": "com.crimata.ElectronUpdaterApp",
|
||||
"artifactName": "${productName}-${version}.${ext}",
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://gitlab.com/api/v4/projects/25637892/jobs/artifacts/main/raw/dist_electron?job=build"
|
||||
}
|
||||
},
|
||||
|
||||
chainWebpackMainProcess: (config) => {
|
||||
// Chain webpack config for electron main process only
|
||||
config.externals([nodeExternals({})])
|
||||
config.resolve.extensions.add('.ts')
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
143
yarn.lock
|
|
@ -3641,7 +3641,7 @@ chokidar@^3.0.2, chokidar@^3.3.0, chokidar@^3.4.1:
|
|||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
chownr@^1.1.1, chownr@^1.1.2:
|
||||
chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
|
@ -4527,7 +4527,7 @@ debounce-fn@^4.0.0:
|
|||
dependencies:
|
||||
mimic-fn "^3.0.0"
|
||||
|
||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
|
|
@ -4898,11 +4898,6 @@ dotenv-expand@^5.1.0:
|
|||
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
|
||||
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
|
||||
|
||||
dotenv@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
dotenv@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
|
||||
|
|
@ -7081,6 +7076,16 @@ invariant@^2.2.2, invariant@^2.2.4:
|
|||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
iohook@^0.9.3:
|
||||
version "0.9.3"
|
||||
resolved "https://registry.yarnpkg.com/iohook/-/iohook-0.9.3.tgz#3beae68daeac09bad3a7fde290f595ac7172b6e7"
|
||||
integrity sha512-O6xHAyRfJvVUYu87sZH5CgBPLo/f70NaZSIbm3o6U4L/I9PdntfjlNYrJ7hbv5nPUwfE3k5TGDH1R1bFVdDBZg==
|
||||
dependencies:
|
||||
nugget "^2.0.1"
|
||||
pump "^1.0.3"
|
||||
rc "^1.2.8"
|
||||
tar-fs "^1.16.3"
|
||||
|
||||
ip-regex@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||
|
|
@ -7473,6 +7478,11 @@ is-yarn-global@^0.3.0:
|
|||
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
|
||||
integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
|
@ -8737,7 +8747,7 @@ memory-fs@^0.5.0:
|
|||
errno "^0.1.3"
|
||||
readable-stream "^2.0.1"
|
||||
|
||||
meow@^3.7.0:
|
||||
meow@^3.1.0, meow@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
|
||||
integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
|
||||
|
|
@ -8894,7 +8904,7 @@ minimatch@^3.0.4, minimatch@~3.0.2:
|
|||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
|
||||
minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
|
@ -9097,6 +9107,13 @@ no-case@^2.2.0:
|
|||
dependencies:
|
||||
lower-case "^1.1.1"
|
||||
|
||||
node-abi@^2.30.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
|
||||
integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==
|
||||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-addon-api@^1.7.1:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
|
||||
|
|
@ -9349,6 +9366,19 @@ nth-check@^1.0.2, nth-check@~1.0.1:
|
|||
dependencies:
|
||||
boolbase "~1.0.0"
|
||||
|
||||
nugget@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
|
||||
integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=
|
||||
dependencies:
|
||||
debug "^2.1.3"
|
||||
minimist "^1.1.0"
|
||||
pretty-bytes "^1.0.2"
|
||||
progress-stream "^1.1.0"
|
||||
request "^2.45.0"
|
||||
single-line-log "^1.1.2"
|
||||
throttleit "0.0.2"
|
||||
|
||||
num2fraction@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
|
||||
|
|
@ -9406,6 +9436,11 @@ object-keys@^1.0.12, object-keys@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
||||
|
||||
object-keys@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
||||
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
|
||||
|
||||
object-visit@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
|
||||
|
|
@ -10337,6 +10372,14 @@ prettier@^1.18.2:
|
|||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
pretty-bytes@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
|
||||
integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
meow "^3.1.0"
|
||||
|
||||
pretty-error@^2.0.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6"
|
||||
|
|
@ -10374,6 +10417,14 @@ process@^0.11.10:
|
|||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
|
||||
|
||||
progress-stream@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77"
|
||||
integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c=
|
||||
dependencies:
|
||||
speedometer "~0.1.2"
|
||||
through2 "~0.2.3"
|
||||
|
||||
progress@^2.0.0, progress@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
|
|
@ -10451,6 +10502,14 @@ public-encrypt@^4.0.0:
|
|||
randombytes "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
pump@^1.0.0, pump@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
|
||||
integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
pump@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
|
||||
|
|
@ -10667,6 +10726,16 @@ readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1, readable
|
|||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readdirp@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
|
||||
|
|
@ -10845,7 +10914,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.7:
|
|||
stealthy-require "^1.1.1"
|
||||
tough-cookie "^2.3.3"
|
||||
|
||||
request@^2.83.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
|
||||
request@^2.45.0, request@^2.83.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
||||
|
|
@ -11406,6 +11475,13 @@ simple-swizzle@^0.2.2:
|
|||
dependencies:
|
||||
is-arrayish "^0.3.1"
|
||||
|
||||
single-line-log@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364"
|
||||
integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=
|
||||
dependencies:
|
||||
string-width "^1.0.1"
|
||||
|
||||
sisteransi@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
|
|
@ -11615,6 +11691,11 @@ spectron@11.0.0:
|
|||
split "^1.0.0"
|
||||
webdriverio "^4.13.0"
|
||||
|
||||
speedometer@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
|
||||
integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=
|
||||
|
||||
split-string@^3.0.1, split-string@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
|
||||
|
|
@ -11856,6 +11937,11 @@ string_decoder@^1.0.0, string_decoder@^1.1.1:
|
|||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
|
|
@ -12047,7 +12133,17 @@ tapable@^1.0.0, tapable@^1.1.3:
|
|||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||
|
||||
tar-stream@^1.5.0:
|
||||
tar-fs@^1.16.3:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509"
|
||||
integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==
|
||||
dependencies:
|
||||
chownr "^1.0.1"
|
||||
mkdirp "^0.5.1"
|
||||
pump "^1.0.0"
|
||||
tar-stream "^1.1.2"
|
||||
|
||||
tar-stream@^1.1.2, tar-stream@^1.5.0:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
|
|
@ -12202,6 +12298,11 @@ throat@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
|
||||
integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=
|
||||
|
||||
throttleit@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
|
||||
integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8=
|
||||
|
||||
through2-filter@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
|
||||
|
|
@ -12226,6 +12327,14 @@ through2@^2.0.0, through2@~2.0.0:
|
|||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
through2@~0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
|
||||
integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=
|
||||
dependencies:
|
||||
readable-stream "~1.1.9"
|
||||
xtend "~2.1.1"
|
||||
|
||||
through@2, through@^2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
|
@ -13157,6 +13266,11 @@ webpack-merge@^4.2.2:
|
|||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
webpack-node-externals@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917"
|
||||
integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==
|
||||
|
||||
webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
||||
|
|
@ -13412,6 +13526,13 @@ xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
xtend@~2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"
|
||||
integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os=
|
||||
dependencies:
|
||||
object-keys "~0.4.0"
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
|
|
|
|||