"electron-is-dev": "^2.0.0",
"electron-store": "^8.0.0",
"electron-updater": "^4.3.8",
- "iohook": "^0.9.3",
"mitt": "^2.1.0",
"update-electron-app": "^2.0.1",
"vue": "^3.0.0-0",
ipcEmit("set-version", app.getVersion());
- updateAppUI();
-
}
-const nodeAudio = require('@crimata/nodeaudio');
import { globalShortcut } from "electron";
+const nodeAudio = require('@crimata/nodeaudio');
+
+import { updateTray } from "@/tray";
+import { sendMessage } from "@/session";
import { backgroundMitt, ipcEmit } from '@/composables/useEmitter';
let inputDevice: number;
let setStreamsId: ReturnType<typeof setTimeout>;
+let playbackEl;
+let writeToOBuffer = false;
let recording = false;
const chunks: Int16Array[] = [];
-// backgroundMitt.on("data", (int16Arr: Int16Array) => {
-// if (recording) {
-// chunks.push(int16Arr);
-// ipcEmit("recording", int16Arr); // for recording animation
-// }
-// });
-
-// const setStreams = () => {
-
-// const defaultInput = nodeAudio.GetDefaultInputDevice();
-// const defaultOutput = nodeAudio.GetDefaultOutputDevice();
-
-// if (inputDevice !== defaultInput) {
-// inputDevice = defaultInput;
-// nodeAudio.CloseInputStream(inputDevice);
-// nodeAudio.OpenInputStream(inputDevice);
-// }
-
-// if (outputDevice !== defaultOutput) {
-// outputDevice = defaultOutput;
-// nodeAudio.CloseOutputStream(outputDevice);
-// nodeAudio.OpenOutputStream(outputDevice);
-// }
-// }
-
-// const startRecording = () => {
-// recording = true;
-// }
-
-// const stopRecording = () => {
-// recording = false;
-// const audio: Int16Array = nodeAudio.MergeChunks(chunks);
-// backgroundMitt.emit("audio", audio); // emit audio event
-// chunks.length = 0;
-// }
+backgroundMitt.on("data", (int16Arr: Int16Array) => {
+ if (recording) {
+ chunks.push(int16Arr);
+ ipcEmit("recording", int16Arr);
+ }
+});
-export const initAudio = () => {
- // nodeAudio.Initialize(backgroundMitt.emit.bind(backgroundMitt));
- // setStreamsId = setInterval(setStreams, 2000); // sense device
+backgroundMitt.on("playback", (uid: string, aplitude: number) => {
+ ipcEmit("animate-playback", { uid: uid, aplitude: aplitude });
+});
- const ret = globalShortcut.register('CommandOrControl+R', () => {
- console.log('Toggle record...');
- });
+backgroundMitt.on("write", (status: boolean) => {
+ writeToOBuffer = status;
+});
+
+const setStreams = () => {
- if (!ret) {
- console.log('registration failed');
+ const defaultInput = nodeAudio.core.GetDefaultInputDevice();
+ const defaultOutput = nodeAudio.core.GetDefaultOutputDevice();
+
+ if (inputDevice !== defaultInput) {
+ inputDevice = defaultInput;
+ nodeAudio.core.CloseInputStream(inputDevice);
+ nodeAudio.core.OpenInputStream(inputDevice);
+ }
+
+ if (outputDevice !== defaultOutput) {
+ outputDevice = defaultOutput;
+ nodeAudio.core.CloseOutputStream(outputDevice);
+ nodeAudio.core.OpenOutputStream(outputDevice);
}
+}
- // Check whether a shortcut is registered.
- console.log(globalShortcut.isRegistered('CommandOrControl+R'));
+const startRecording = () => {
+ /* If playback is happening, we kill it */
+ if (playback) nodeAudio.core.ClearOutputBuffer();
+
+ recording = true;
+ updateTray("recording", recording);
}
-// export const playback = (audio: Int16Array) => {
-// console.log("Playing audio");
-// // nodeAudio.WriteToOutputStream(audio);
-// }
+const stopRecording = () => {
+ recording = false;
+ updateTray("recording", recording);
+
+ /* Send the data to the platform right away */
+ sendMessage({
+ text: false,
+ audio: nodeAudio.utils.mergeChunks(chunks)
+ } as Raw);
+
+ chunks.length = 0;
+}
-// export const terminateAudio = () => {
-// console.log("Terminating audio");
-// // clearInterval(setStreamsId);
-// }
\ No newline at end of file
+export const initAudio = () => {
+ nodeAudio.core.Initialize(backgroundMitt.emit.bind(backgroundMitt));
+ setStreamsId = setInterval(setStreams, 2000); // sense device
+
+ /* Toggle recording switch with global shortcut */
+ globalShortcut.register('CommandOrControl+R', () => {
+ if (recording) {
+ stopRecording();
+ } else {
+ startRecording();
+ };
+ });
+
+}
+
+export const playback = (audio: Int16Array, source: string) => {
+
+ /* If existing playback is happening, we kill it */
+ if (playback) nodeAudio.core.ClearOutputBuffer();
+
+ const playbackEl = uid;
+
+ nodeAudio.core.WriteToOutputStream(audio.buffer);
+}
+
+export const terminateAudio = () => {
+ clearInterval(setStreamsId);
+ nodeAudio.core.Terminate();
+}
+
+export const getStreamState = () => {
+ let busy = false;
+ if (playback || recording) {
+ busy = true;
+ }
+ return busy;
+}
import main from "./main";
import { backgroundMitt } from '@/composables/useEmitter';
import { setWindowOpen, getWindowOpen } from './store';
+import { terminateAudio } from './audio';
console.log('Starting Crimata electron app.');
});
app.on("will-quit", () => {
+ terminateAudio();
globalShortcut.unregisterAll();
});
-import { IpcListener } from "@/composables/useIpcMain"
-import { sendMessage } from '@/session';
-import { updateAppState } from "@/account";
+import { playback } from "@/audio";
import { onNavBar } from "@/window";
+import { sendMessage } from '@/session';
import { setWindowFocus } from '@/store';
+import { updateAppState } from "@/account";
+import { IpcListener } from "@/composables/useIpcMain"
const CLIENT_MESSAGE_CHANNEL = "post-session-send"
const APP_MOUNT_CHANNEL = "post-app-mount";
const NAV_BAR_CHANNEL = "post-nav-bar";
const POST_WINDOW_FOCUS = 'post-window-focus';
+const PLAYBACK_CHANNEL = "post-playback;"
export const messageListener = new IpcListener<Raw | Request>({
channel: CLIENT_MESSAGE_CHANNEL,
channel: POST_WINDOW_FOCUS,
listenerCallback: ({ isFocused }) => setWindowFocus(isFocused)
});
+
+export const playbackListener = new IpcListener({
+ channel: PLAYBACK_CHANNEL,
+ listenerCallback: playback
+});
-/**
- * Where the background logic really begins, gets called by app.onReady().
- *
- * Handles authentication. If profile is set, we launch a session, which consis
- * of opening a connection with the platform, initializing the audio streams.
- *
- * The session is primarily an interface between the frontend and the platform,
- * relaying messages from one to the other.
- *
- */
-
+import { initTray } from '@/tray';
+import createWindow from "@/window";
import initIpcMain from "@/ipc/index";
-import { accountAuth } from "./account";
-import createWindow from "./window";
-import { initTray } from './tray';
+import { accountAuth } from "@/account";
export default async function main() {
--- /dev/null
+class AudioVisualizer {
+ constructor( audioContext, processFrame, processError ) {
+ this.audioContext = audioContext;
+ this.processFrame = processFrame;
+ this.connectStream = this.connectStream.bind( this );
+ navigator.mediaDevices.getUserMedia( { audio: true, video: false } )
+ .then( this.connectStream )
+ .catch( ( error ) => {
+ if ( processError ) {
+ processError( error );
+ }
+ } );
+ }
+
+ connectStream( stream ) {
+ this.analyser = this.audioContext.createAnalyser();
+ const source = this.audioContext.createMediaStreamSource( stream );
+ source.connect( this.analyser );
+ this.analyser.smoothingTimeConstant = 0.5;
+ this.analyser.fftSize = 32;
+
+ this.initRenderLoop( this.analyser );
+ }
+
+ initRenderLoop() {
+ const frequencyData = new Uint8Array( this.analyser.frequencyBinCount );
+ const processFrame = this.processFrame || ( () => {} );
+
+ const renderFrame = () => {
+ this.analyser.getByteFrequencyData( frequencyData );
+ processFrame( frequencyData );
+
+ requestAnimationFrame( renderFrame );
+ };
+ requestAnimationFrame( renderFrame );
+ }
+}
+
+const visualMainElement = document.querySelector( 'main' );
+const visualValueCount = 16;
+let visualElements;
+const createDOMElements = () => {
+ let i;
+ for ( i = 0; i < visualValueCount; ++i ) {
+ const elm = document.createElement( 'div' );
+ visualMainElement.appendChild( elm );
+ }
+
+ visualElements = document.querySelectorAll( 'main div' );
+};
+createDOMElements();
+
+const init = () => {
+ // Creating initial DOM elements
+ const audioContext = new AudioContext();
+ const initDOM = () => {
+ visualMainElement.innerHTML = '';
+ createDOMElements();
+ };
+ initDOM();
+
+ // Swapping values around for a better visual effect
+ const dataMap = { 0: 15, 1: 10, 2: 8, 3: 9, 4: 6, 5: 5, 6: 2, 7: 1, 8: 0, 9: 4, 10: 3, 11: 7, 12: 11, 13: 12, 14: 13, 15: 14 };
+ const processFrame = ( data ) => {
+ const values = Object.values( data );
+ let i;
+ for ( i = 0; i < visualValueCount; ++i ) {
+ const value = values[ dataMap[ i ] ] / 255;
+ const elmStyles = visualElements[ i ].style;
+ elmStyles.transform = `scaleY( ${ value } )`;
+ elmStyles.opacity = Math.max( .25, value );
+ }
+ };
+
+ const processError = () => {
+ visualMainElement.classList.add( 'error' );
+ visualMainElement.innerText = 'Please allow access to your microphone in order to see this demo.\nNothing bad is going to happen... hopefully :P';
+ }
+
+ const a = new AudioVisualizer( audioContext, processFrame, processError );
+};
\ No newline at end of file
<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-if="context.img" class="avatar" @click="clickMethod">
+ <img v-click="" v-if="context.img === 'crimata'" :src="require('@/render/assets/crimataAvatar.svg')">
<div v-else>{{ context.img }}</div>
</div>
}
+/* TODO: animation function that takes in a message ID, and data about the
+ * playedback audio. We target the element with the UID and animate it according
+ * to the data. - still very hypothetical...
+ */
+export function animatePlayback (uid: string, meta: number) {
+ console.log("animating playback!")
+}
+
// Given index and length of content, return message child status.
export function calcChild (index: number, len: number) {
if (len === 1) {
+++ /dev/null
-import { onMounted, onUnmounted, ref, Ref } from "vue";
-import { postMessage } from "@/render/ipc";
-import { animateAudioInput } from "./helpers";
-import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
-
-export default function useAudioInputController (typing: Ref) {
-
- const recording = ref(false);
- let mediaRecorder: MediaRecorder;
-
- const { show, hide } = animateAudioInput();
-
- // initialize audio
- const conf = {audio: true, video: false}
- navigator.mediaDevices.getUserMedia(conf).then((stream: MediaStream) => {
-
- const options = {mimeType: 'audio/webm'};
- mediaRecorder = new MediaRecorder(stream, options);
-
- // post any mew audio to backend
- mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => {
- e.data.arrayBuffer().then((buff: ArrayBuffer) => {
- postAudioChunk(buff);
- });
- });
-
- // get audio and post new message to backend
- mediaRecorder.addEventListener('stop', (_e: Event) => {
- invokeReturnAudio().then((audio: ArrayBuffer[] | Error) => {
- console.log(audio);
- // postMessage(newMessage({audio: audio}));
- });
- });
-
- });
-
- // start recording on space bar
- const record = () => {
- console.log("INPT:Capturing audio...")
- mediaRecorder.start();
- recording.value = true;
- show()
- }
-
- // stop recording and send on release
- const stop = () => {
- console.log("INPT:Stopping record.")
- mediaRecorder.stop();
- recording.value = false;
- hide();
- }
-
- const onKeyDown = (e: KeyboardEvent) => {
- if (e.keyCode == 32 && !typing.value) record();
- }
-
- const onKeyUp = (e: KeyboardEvent) => {
- if (e.keyCode == 32 && recording.value) stop();
- }
-
- //-----------------------------------------------------------
-
- onMounted(() => {
- window.addEventListener("keydown", onKeyDown);
- window.addEventListener("keyup", onKeyUp);
- });
-
- onUnmounted(() => {
- window.removeEventListener("keydown", onKeyDown);
- window.removeEventListener("keyup", onKeyUp);
- })
-
- return {
- recording
- }
-
-}
type="text"
/>
- <!-- Show suggestions menu on click -->
- <!-- <Menu /> -->
-
</div>
</template>
<script lang="ts">
-import { defineComponent } from "vue";
+import { defineComponent, watch } from "vue";
import draggify from "@/render/composables/useDraggify";
import { profile } from "@/render/shared/profile";
+import { recording } from "@/render/shared/audio";
+import { animateAudioInput } from "@/render/components/controllers/helpers";
import useTextInputController from
"@/render/components/controllers/inputItem.control.text";
-// import useAudioInputController from
"@/render/components/controllers/inputItem.control.audio";
export default defineComponent({
// Controllers for text and audio.
const { typing } = useTextInputController(elementX)
- // const { recording } = useAudioInputController(typing)
- const recording = false;
+
+ // Control rec icon (red dot upper right of UI)
+ const { show, hide } = animateAudioInput();
+ watch(recording, (val, _oldval) => {
+ recording ? show() : hide();
+ });
return {
profile,
*
*/
-
export const invokeLogin = async (
payload: AccountCredentials
): Promise<Profile | Error> => (
*
*/
-export const postAudioChunk = (chunk: ArrayBuffer): void => (
- post("post-audio-collect", chunk)
-);
-
-export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
- await invoke("invoke-audio-flush", null)
+export const postPlayback = (payload: string): void => (
+ post('post-playback', payload)
);
/**
import { setProfile } from "./shared/profile";
import { setVersion } from "./shared/version";
import { setConnectionStatus } from "./shared/connectionStatus";
+import { setRecording } from "./shared/audio";
const SET_ACCOUNT_CHANNEL = "set-account";
const SET_PROFILE_CHANNEL = "set-profile";
const UPDATE_MESSAGE_CHANNEL = "update-message";
const DELETE_MESSAGE_CHANNEL = "delete-message";
const CONNECTION_STATUS_CHANNEL = "set-connection-status";
+const RECORDING_STATUS_CHANNEL = "recording-status";
export const setAccountListener = new IpcRendererListener({
channel: SET_ACCOUNT_CHANNEL,
export const connectionStatusListener = new IpcRendererListener({
channel: CONNECTION_STATUS_CHANNEL,
listenerCallback: setConnectionStatus
+});
+
+export const recordingStatusListener = new IpcRendererListener({
+ channel: RECORDING_STATUS_CHANNEL,
+ listenerCallback: setRecording
+});
+
+export const animatePlaybackListener = new IpcRendererListener({
+ channel: PLAYBACK_LISTENER,
+ listenerCallback: animatePlayback
});
\ No newline at end of file
--- /dev/null
+// shared
+import { ref } from "vue";
+
+export const recording = ref();
+
+export const setRecording: IpcListenerCallback<boolean> = (payload) => {
+ recording.value = payload;
+}
+
+export default {
+ recording
+}
\ No newline at end of file
+import { Notification } from 'electron';
-// import useAudio from "@/audio";
import UIState from "@/uiState";
import { config } from "@/config";
-import useWebsockets from "./composables/useWebsockets";
+import { updateTray } from "@/tray";
+import { getWindowFocus, getWindowOpen } from '@/store';
+import useWebsockets from "@/composables/useWebsockets";
import { backgroundMitt, ipcEmit } from "@/composables/useEmitter";
-import { Notification } from 'electron';
-import { getWindowFocus, getWindowOpen } from './store';
-import { initAudio, terminateAudio, playback } from "./audio";
+import { initAudio, terminateAudio, playback, getStreamState } from "@/audio";
-export const CONNECTION_STATUS = {
- connected: 'Connected',
- nominal: 'Nominal',
- reconnecting: 'Reconnecting',
- lost: 'Connection Lost',
-};
+const contentQueue: Update[] = [];
const uiState = new UIState();
-const onMessageCallback = (payload: string) => {
+const onMessageCallback = async (payload: string) =>
+{
+ const message = JSON.parse(payload) as ClientProtocol;
- if (payload === "CLOSE_AUTH_FAIL") {
- backgroundMitt.emit("close-auth-fail");
+ if (message.header == "init") {
+ uiState.set(message.body as Init);
return;
}
- const message = JSON.parse(payload) as ClientProtocol;
-
- if (message.header === "init")
+ if (content)
{
- uiState.set(message.body as Init);
+ contentQueue.push(message.body)
}
else
{
- const body = message.body as Update;
- uiState.update(body);
-
- if (body.name === "add")
- {
- const data = body.data as Message;
-
- // Show a notification if new content and window is not focused.
- if (!getWindowFocus())
- {
- new Notification({
- title: 'New Message',
- subtitle: data.context.text as string,
- body: data.content[0].text,
- })
- .show();
- }
-
- // Playback audio if message has audio.
- // TODO: Should we worry about playback and record at the same time?
- // if (data.content.audio)
- // {
- // playback(data.content.audio);
- // }
- }
+ uiState.update(message.body as Update);
}
}
+const statusOptions = { openMessage: 'Connected',
+ pongMessage: 'Nominal',
+ closeMessage: 'Reconnecting',
+ pingErrorMessage: 'Connection Lost' };
-const connectionStatusCallback = (status: string) => {
- // backgroundMitt.emit("update-icon-status", status);
- ipcEmit('set-connection-status', status);
-}
-
-const statusOptions = {
- openMessage: CONNECTION_STATUS.connected,
- pongMessage: CONNECTION_STATUS.nominal,
- closeMessage: CONNECTION_STATUS.reconnecting,
- pingErrorMessage: CONNECTION_STATUS.lost,
-}
+const connectionStatusCallback = (status: string) =>
+{
+ /* Simplify connectionStatus for tray state */
+ let connectionStatusSimple = false;
+ if (status == statusOptions.pingErrorMessage || statusOptions.closeMessage)
+ {
+ connectionStatusSimple = true;
+ }
-const { connect, send, close } = useWebsockets(
- onMessageCallback,
- connectionStatusCallback,
- statusOptions
-);
+ updateTray("disconnect", connectionStatusSimple);
-export const updateAppUI = (): void => {
- uiState.emit();
+ ipcEmit('set-connection-status', status);
}
-// Listen for audio recordings and send them to backend.
-backgroundMitt.on("audio", (audio: Int16Array) => {
- sendMessage({
- text: false,
- audio: audio
- } as Raw);
-});
+const { connect, send, close } = useWebsockets(onMessageCallback,
+ connectionStatusCallback, statusOptions);
-export function sendMessage(message: Raw | Request): void {
+export function sendMessage(message: Raw | Request): void
+{
send(message);
}
-/* launch a new session (the main process for authenticated users) */
-export function launchSession(platformKey: string) {
+let processContentId: ReturnType<typeof setTimeout>;
+/* launch a new session (the main process for authenticated users) */
+export function launchSession(platformKey: string)
+{
/* connect to the platform */
connect(config.PLATFORM_URL, platformKey);
initAudio();
+ processContentId = setInterval(() =>
+ {
+ if (getStreamState())
+ {
+ if (contentQueue.length > 0)
+ {
+ const update = contentQueue.shift() as Update;
+
+ /* Update the UI */
+ uiState.update(update);
+
+ /* Spawn notification if window not in focus */
+ if (!getWindowFocus())
+ {
+ new Notification({
+ title: "New Message",
+ subtitle: data.context.text,
+ body: data.content[0].text
+ }).show();
+ }
+
+ /* Play audio if audio */
+ if (update.data.content[-1].audio)
+ {
+ playback(message.audio, message.uid);
+ }
+ }
+ }
+ }, 100);
}
-export function endSession() {
-
+export function endSession()
+{
terminateAudio();
close(1000, 'session-logout');
- uiState.reset();
+ clearInterval(processContentId)
+ uiState.reset();
}
-
-"use strict";
-
import fs from 'fs'
import path from 'path'
import { Tray } from 'electron';
-const nativeImage = require('electron').nativeImage;
-import { NativeImage } from 'electron'
import { backgroundMitt } from "@/composables/useEmitter";
-import { getIconState, setIconState } from "@/store";
-import { CONNECTION_STATUS } from '@/session';
-
-declare const __static: string;
-
-interface Icons {
- [name: string]: NativeImage;
-}
-let tray: Tray | null = null;
-const TRAY_ICON_DIR = 'trayIcon';
+let tray: Tray;
+const ICON_PATH = "./build/tray";
-const ICON_NAMES = {
- default: 'icon.png',
- // TODO: need icons for different states
- // TODO: need higher density icons @3x, @4x, @5x
- // TODO: need to test with 20x20 & 24x24 icon sizes
- recording: 'recording.png',
- playback: 'playback.png',
- connected: 'connected.png',
- nominal: 'nominal.png',
- reconnecting: 'reconnecting.png',
- lost: 'lost.png',
+/* Current state of recording, playback, and ws connection */
+const state = {
+ recording: false,
+ playback: false,
+ disconnect: false // whether we're experiencing connection issues
}
-const TRAY_STATUS = {
- playback: 'playback',
- recording: 'recording',
- ...CONNECTION_STATUS
-};
+export function updateTray(attribute: "recording" | "playback" | "disconnect", newState: boolean) {
+ state[attribute] = newState;
-const ICONS: Icons = {
- default: iconFactory(ICON_NAMES.default),
- [TRAY_STATUS.recording]: iconFactory(ICON_NAMES.recording),
- [TRAY_STATUS.playback]: iconFactory(ICON_NAMES.playback),
- [TRAY_STATUS.connected]: iconFactory(ICON_NAMES.connected),
- [TRAY_STATUS.nominal]: iconFactory(ICON_NAMES.nominal),
- [TRAY_STATUS.reconnecting]: iconFactory(ICON_NAMES.reconnecting),
- [TRAY_STATUS.lost]: iconFactory(ICON_NAMES.lost),
-};
+ // convert boolean to number
+ const bi = (b: boolean) => b ? 1 : 0;
-backgroundMitt.on('update-icon-status', (payload: string) => {
- updateTray(payload);
-});
+ // Make sure we have the icon we're looking for.
+ tray.setImage(
+ ICON_PATH +
+ `${bi(state.recording)}${bi(state.playback)}${bi(state.disconnect)}` +
+ ".png"
+ );
-const changeIcon = (icon: NativeImage): void => tray ? tray.setImage(icon): undefined;
-
-function iconFactory(iconName: string): NativeImage {
- if (iconName !== 'icon.png') {
- console.log('ERROR: Missing Icons: ', iconName);
- }
- // TODO: remove hard code, need icons for different states
- const iconBuffer = fs.readFileSync(path.join(__static, TRAY_ICON_DIR, 'icon.png'));
- const icon = nativeImage.createFromBuffer(iconBuffer);
- return icon;
-}
-
-function updateTray(status: string): void {
- // read icon state
- const current = getIconState();
- if (current !== status) {
- setIconState(status);
- if (status in ICONS) {
- changeIcon(ICONS[status]);
- } else {
- changeIcon(ICONS.default);
- }
- }
}
export function initTray(): void {
- if (!tray) {
- try {
- tray = new Tray(ICONS.default);
- } catch(e) {
- console.log('Error initilizing tray', e);
- }
- }
-}
-
+ tray = new Tray(ICON_PATH + "000.png");
+}
\ No newline at end of file
interface Content {
text: string;
- audio: string | boolean;
+ audio: Int16Array | false;
}
interface Context {
import { ipcEmit } from "@/composables/useEmitter";
-class Profile implements Profile{
+class Profile implements Profile
+{
crimata_id = "";
name = "";
photo = "";
}
-export default class UIState {
-
+export default class UIState
+{
profile: Profile;
messages: Message[];
- constructor() {
+ constructor()
+ {
this.profile = new Profile();
this.messages = [];
}
- emit() {
+ emit()
+ {
ipcEmit("set-profile", this.profile);
ipcEmit("init-messages", this.messages);
}
- set (message: Init) {
+ set (message: Init)
+ {
this.profile = message.profile;
this.messages = message.messages;
this.emit();
}
- update(update: Update) {
-
- if (update.name === "profile") {
+ update(update: Update)
+ {
+ if (update.name === "profile")
+ {
this.profile = update.data as Profile;
ipcEmit("set-profile", this.profile);
}
-
- else if (update.name === "add") {
+ else if (update.name === "add")
+ {
this.messages.push(update.data as Message);
ipcEmit("add-message", update.data);
}
-
- else if (update.name === "annotate") {
+ else if (update.name === "annotate")
+ {
this._annotate(update.data as Annotation);
ipcEmit("update-message", update.data);
}
-
- else {
+ else
+ {
this._delete(update.data as string);
ipcEmit("delete-message", update.data);
}
-
}
- reset() {
+ 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") {
+ _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") {
+ if (annotation.name == "context")
+ {
this.messages[i].context = annotation.data as Context;
}
- if (annotation.name == "seen") {
+ 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) {
+ _delete(uid: string)
+ {
+ for (let i = 0; i < this.messages.length; i++)
+ {
+ if (this.messages[i].uid === uid)
+ {
this.messages.splice(i, 1);
break;
}
}
}
-
}
\ No newline at end of file
optionalDependencies:
fsevents "~2.1.2"
-chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2:
+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==
dependencies:
mimic-fn "^3.0.0"
-debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, 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==
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"
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"
errno "^0.1.3"
readable-stream "^2.0.1"
-meow@^3.1.0, meow@^3.7.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=
dependencies:
brace-expansion "^1.1.7"
-minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
+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==
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"
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"
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"
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"
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"
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"
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
-request@^2.45.0, request@^2.83.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
+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==
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"
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"
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"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
-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:
+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==
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"
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"
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"