Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
b6402dbdd7
17 changed files with 273 additions and 67 deletions
81
src/App.vue
81
src/App.vue
|
|
@ -1,26 +1,89 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
|
||||
<button class="titlebar">
|
||||
|
||||
</button>
|
||||
|
||||
<!-- Menu -->
|
||||
<span class="menu">
|
||||
<button class="menuButton exitButton"></button>
|
||||
<button class="menuButton minimizeButton"></button>
|
||||
</span>
|
||||
|
||||
<!-- Windows -->
|
||||
<router-view/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
font-family: "SF Pro Text";
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
background-color: #EBEBEB;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
position: fixed;
|
||||
|
||||
width: 100vw;
|
||||
height: 54px;
|
||||
|
||||
opacity: 0.75;
|
||||
|
||||
background-color: #EBEBEB;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
border: none;
|
||||
outline:none;
|
||||
min-width: 14px;
|
||||
min-height: 14px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.exitButton {
|
||||
background-color: #FF6157;
|
||||
}
|
||||
|
||||
.exitButton:active {
|
||||
background: #c14645;
|
||||
}
|
||||
|
||||
.minimizeButton {
|
||||
background-color: #FFC12F;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.minimizeButton:active {
|
||||
background-color: #c08e38;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export async function main() {
|
|||
// create main window.
|
||||
await createWindow({
|
||||
width: 350,
|
||||
height: 525,
|
||||
height: 500,
|
||||
resizable: true
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ interface TextMessage {
|
|||
content: string;
|
||||
}
|
||||
|
||||
const ip = 'ws://127.0.0.1';
|
||||
const port = 8081;
|
||||
const ip = 'ws://localhost';
|
||||
const port = 8760;
|
||||
const reconnectTimeout = 3000; //ms
|
||||
let socket: WebSocket;
|
||||
let success = false;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ export const createWindow = async (options: WindowSettings): Promise<void> => {
|
|||
width: options.width,
|
||||
height: options.height,
|
||||
resizable: options.resizable,
|
||||
frame: false,
|
||||
minWidth: 350,
|
||||
minHeight: 500,
|
||||
webPreferences: {
|
||||
// Use pluginOptions.nodeIntegration, leave this alone
|
||||
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
:viewBox="scrollView"
|
||||
@click="emittMessage"
|
||||
>
|
||||
<g id="messageView" transform="translate(0, 20)">
|
||||
<g v-for="(item, index) in renderArr" :key="index" preserverAspectRatio="none">
|
||||
|
|
@ -21,24 +22,33 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import TextBubble from "@/components/textBubble/index.vue";
|
||||
|
||||
import { Message } from "@/types/message/index";
|
||||
import useTransitions from "./viewDetails/transitions";
|
||||
import useScrollView from "@/modules/scrollView";
|
||||
import useMitt from "@/modules/mitt";
|
||||
import { useIpc } from "@/modules/ipc";
|
||||
import { Queue } from '@/modules/queue';
|
||||
import { defineComponent, reactive, onMounted, onUnmounted, watch } from "vue";
|
||||
|
||||
import useTransitions from "./viewDetails/transitions";
|
||||
import useScrollView from "@/modules/scrollView";
|
||||
|
||||
import useMitt from "@/modules/mitt";
|
||||
|
||||
import { defineComponent, reactive, onMounted, onUnmounted, watch, ref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
name: "MessageView",
|
||||
components: { TextBubble },
|
||||
|
||||
setup() {
|
||||
const { emitter } = useMitt();
|
||||
const renderArr: Message[] = reactive([]);
|
||||
const renderQ = new Queue();
|
||||
|
||||
const { invoke } = useIpc("message");
|
||||
|
||||
// Each method will be called on their respective lifecycle event.
|
||||
const { beforeEnter, enter, afterEnter, rendering } = useTransitions();
|
||||
const { scrollView } = useScrollView({
|
||||
targetId: "messageView",
|
||||
|
|
@ -60,6 +70,8 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
// Call render function on event "render-message."
|
||||
window.ipcRenderer.on("render-message", (event, payload) => {
|
||||
const m = payload.message;
|
||||
if (m.content) render(m);
|
||||
|
|
@ -90,3 +102,13 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
#messageView {
|
||||
width: 1000px;
|
||||
height: 100vh;
|
||||
color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ref } from "vue";
|
|||
export default function useOffsetCalculator() {
|
||||
const xOffset = ref(0);
|
||||
const yOffset = ref(0);
|
||||
const firstChildMarginTop = 0;
|
||||
const firstChildMarginTop = 45;
|
||||
let previousMessageHeight = 0;
|
||||
const messagePadding = 15;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,12 @@ export default function useTransitions() {
|
|||
|
||||
const animateMessage = async (el: SVGGElement) => {
|
||||
|
||||
// Where the message gets translated to.
|
||||
const elTransform = `translate(${messageOffset.x} ${messageOffset.y})`;
|
||||
|
||||
const viewTransform = `translate(0 ${-vB})`;
|
||||
|
||||
// Shift of view height > window height.
|
||||
if (shift) {
|
||||
await shiftAnimate(el, elTransform, messageView, viewTransform);
|
||||
} else {
|
||||
|
|
@ -44,6 +47,7 @@ export default function useTransitions() {
|
|||
rendering.value = false;
|
||||
}
|
||||
|
||||
// Get height of messageView.
|
||||
const beforeEnter: VueTransitionCallback = () => {
|
||||
rendering.value = true;
|
||||
// query DOM for messageView element
|
||||
|
|
|
|||
|
|
@ -1,17 +1,33 @@
|
|||
<template>
|
||||
<audio-stream v-if="visualizeStream" :bubbleWidth="audioBubbleWidth" :paths="paths" />
|
||||
<text-input v-else :input="input" :textXoffset="textInputXoffset" />
|
||||
|
||||
<!-- render audio or text input-->
|
||||
<AudioStream
|
||||
v-if="visualizeStream"
|
||||
:bubbleWidth="audioBubbleWidth"
|
||||
:paths="paths"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
v-else
|
||||
:input="input"
|
||||
:textXoffset="textInputXoffset"
|
||||
:textYoffset="textInputYoffset"
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// input child components
|
||||
|
||||
// import child components
|
||||
import TextInput from "./textDetails/text.vue";
|
||||
import AudioStream from "./audioDetails/audio.vue";
|
||||
|
||||
import useInputController from "./inputController";
|
||||
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
name: "TextAudioInput",
|
||||
components: { TextInput, AudioStream },
|
||||
setup() {
|
||||
|
|
@ -24,14 +40,20 @@ export default defineComponent({
|
|||
const {
|
||||
input,
|
||||
visualizeStream,
|
||||
|
||||
textInputXoffset,
|
||||
textInputYoffset,
|
||||
|
||||
audioBubbleWidth,
|
||||
paths,
|
||||
} = useInputController();
|
||||
|
||||
return {
|
||||
input,
|
||||
|
||||
textInputXoffset,
|
||||
textInputYoffset,
|
||||
|
||||
visualizeStream,
|
||||
audioBubbleWidth,
|
||||
paths,
|
||||
|
|
|
|||
|
|
@ -32,9 +32,18 @@ export default function useInputController() {
|
|||
emitter.emit("user-message", message);
|
||||
}
|
||||
|
||||
const { visualizeStreamAsPaths, expandBubble } = useAudioVisualizer(visualizeStream);
|
||||
const {
|
||||
visualizeStreamAsPaths,
|
||||
expandBubble
|
||||
} = useAudioVisualizer(visualizeStream);
|
||||
|
||||
const { keyDownHandler, input } = useKeyDownHandler(enterCallback);
|
||||
const { textInputXoffset, renderTextInput } = useTextVisualizer(input);
|
||||
|
||||
const {
|
||||
textInputXoffset,
|
||||
textInputYoffset,
|
||||
renderTextInput
|
||||
} = useTextVisualizer(input);
|
||||
|
||||
// stops stream recording on space key up
|
||||
function keyupHandler(e: any) {
|
||||
|
|
@ -83,6 +92,7 @@ export default function useInputController() {
|
|||
input,
|
||||
visualizeStream,
|
||||
textInputXoffset,
|
||||
textInputYoffset,
|
||||
audioBubbleWidth,
|
||||
paths
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,52 @@
|
|||
<template>
|
||||
<foreignObject class="inputContainer" :x="textXoffset" y="450" width="55%" height="50%">
|
||||
|
||||
<foreignObject
|
||||
class="inputContainer"
|
||||
:x="textXoffset"
|
||||
:y="textYoffset"
|
||||
width="55%"
|
||||
height="50%"
|
||||
>
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div class="inputBubble" contenteditable v-show="input.length > 0">
|
||||
<div class="inputBubble" v-show="input.length > 0">
|
||||
<p>{{ input }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</foreignObject>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
name: "TextInput",
|
||||
props: {
|
||||
input: String,
|
||||
textXoffset: String,
|
||||
textYoffset: String,
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
div[contenteditable] {
|
||||
width: auto;
|
||||
.inputBubble {
|
||||
|
||||
max-width: 180px;
|
||||
height: auto;
|
||||
min-height: 36px;
|
||||
min-height: 32px;
|
||||
|
||||
display: table;
|
||||
justify-items: center;
|
||||
border: 0;
|
||||
|
||||
border-radius: 10px;
|
||||
color: #000;
|
||||
background-color: #b9b9b9;
|
||||
font-size: 14;
|
||||
text-align: left;
|
||||
background-color: #B9B9B9;
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
p {
|
||||
max-width: 150px;
|
||||
overflow-wrap: break-word;
|
||||
|
|
|
|||
|
|
@ -50,8 +50,13 @@ export default function useTextVisualizer(input: Ref<string>) {
|
|||
return `calc(50% - ${inputWidth.value / 2})`;
|
||||
});
|
||||
|
||||
const textInputYoffset = computed(() => {
|
||||
return `calc(97% - ${inputHeight.value})`;
|
||||
});
|
||||
|
||||
return {
|
||||
textInputXoffset,
|
||||
textInputYoffset,
|
||||
renderTextInput
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { computed, onMounted, ref } from "vue";
|
|||
export default function useComputedBubble(m: string, id: string) {
|
||||
const x = ref(0)
|
||||
const paddingLeft = 30;
|
||||
|
||||
const winW = 350;
|
||||
const messageWidth = ref(0);
|
||||
const messageMarginTop = 75;
|
||||
|
|
@ -39,7 +40,7 @@ export default function useComputedBubble(m: string, id: string) {
|
|||
const calculateXoffset = async () => {
|
||||
switch (m) {
|
||||
case "sf":
|
||||
x.value = winW - messageWidth.value - selfMessageMarginRight;
|
||||
x.value = 800 - messageWidth.value - selfMessageMarginRight;
|
||||
break;
|
||||
default:
|
||||
x.value = paddingLeft;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<g :id="item.id" :class="item.modifier" :transform="startingOffset">
|
||||
<rect ref="messageRect" x="30" y="10" width="196" height="63" rx="10" :fill="bubbleFill" />
|
||||
<text font-size="14" font-family="SF Pro" ref="messageText" :fill="textFill">
|
||||
<text font-size="14" font-family="SF Pro Text" ref="messageText" :fill="textFill">
|
||||
<tspan dy="16" x="0" v-for="(item, index) in textArr" :key="index">{{ item }}</tspan>
|
||||
</text>
|
||||
<g :transform="signatureTranslate">
|
||||
|
|
|
|||
|
|
@ -12,30 +12,44 @@ export default function useScrollView({ targetId }: { targetId: string }) {
|
|||
let topBound: number;
|
||||
const scrollStartHeight = 445;
|
||||
|
||||
const clampedScroll = computed(() => {
|
||||
if (targetHeight.value === 0) { return 0; }
|
||||
topBound = -targetHeight.value + 450;
|
||||
return Math.max(topBound, Math.min(scroll.value, 0)) as number;
|
||||
})
|
||||
|
||||
const scrollView = computed(() => {
|
||||
|
||||
if (clampedScroll.value === 0) {
|
||||
scroll.value = 0;
|
||||
} else if (clampedScroll.value === topBound) {
|
||||
scroll.value = topBound;
|
||||
}
|
||||
|
||||
// x, y, width, height
|
||||
// width and height should be dynamic
|
||||
return `0 ${clampedScroll.value} 350 500`;
|
||||
});
|
||||
|
||||
const clampedScroll = computed(() => {
|
||||
if (targetHeight.value === 0) { return 0; }
|
||||
topBound = -targetHeight.value + 450;
|
||||
return Math.max(topBound, Math.min(scroll.value, 0)) as number;
|
||||
})
|
||||
|
||||
const getHeight = () => {
|
||||
if (scrollTarget) {
|
||||
return scrollTarget.getBoundingClientRect().height as number;
|
||||
}
|
||||
}
|
||||
|
||||
const updateView = (verticalScroll: number) => {
|
||||
const getWidth = () => {
|
||||
if (scrollTarget) {
|
||||
return scrollTarget.getBoundingClientRect().width as number;
|
||||
}
|
||||
}
|
||||
|
||||
const updateView = (verticalScroll: number) => {
|
||||
|
||||
if (scrollTarget) {
|
||||
|
||||
const heightResult = getHeight();
|
||||
const widthResult = getWidth();
|
||||
|
||||
if (heightResult) {
|
||||
// only scroll if renderer scrollStart
|
||||
if (heightResult > scrollStartHeight) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
@click="emittMessage"
|
||||
>
|
||||
<MessageView />
|
||||
<TextAudioInput />
|
||||
|
|
@ -11,22 +12,26 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
// Home consists on MessageView and Text/Audio imput component.
|
||||
import MessageView from "@/components/messageView/index.vue";
|
||||
import TextAudioInput from "@/components/taInput/index.vue";
|
||||
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Messenger",
|
||||
components: { MessageView, TextAudioInput },
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#messenger {
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
background-color: #e6e6e6;
|
||||
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
// border-radius: 20px;
|
||||
}
|
||||
|
||||
#messenger {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #EBEBEB;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<form id="login" @submit.prevent="submit">
|
||||
|
||||
<!-- login title -->
|
||||
<div id="loginTitle">
|
||||
<div>Login</div>
|
||||
|
|
@ -8,19 +9,27 @@
|
|||
<!-- username and password forms -->
|
||||
<div id="loginBox">
|
||||
<!-- username -->
|
||||
<input class="input" type="text" v-model="email" placeholder="username" />
|
||||
<div class="inputBox">
|
||||
<input class="input" type="text" v-model="email" />
|
||||
<div class="inputModifier">Email</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- password -->
|
||||
<input class="input" type="password" v-model="password" placeholder="Password" />
|
||||
<div class="inputBox">
|
||||
<input class="input" type="password" v-model="password" />
|
||||
<div class="inputModifier">Password</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="checkbox" v-model="rememberMe" />
|
||||
<label for="checkbox">Remember Me</label>
|
||||
|
||||
<!-- submit button; position: fixed -->
|
||||
<button class="button" @click.prevent="submit">Submit</button>
|
||||
<button class="submitButton button" @click.prevent="submit">Submit</button>
|
||||
|
||||
<!-- back to login button: position: fixed -->
|
||||
<button class="createAccountButton button" @click.prevent="switchView">Create account</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
|
@ -80,33 +89,44 @@ export default defineComponent({
|
|||
|
||||
<style lang="scss" scoped>
|
||||
#login {
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
#loginTitle {
|
||||
font-family: "SF Pro Text";
|
||||
min-width: 181px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#loginBox {
|
||||
font-family: "SF Compact Display";
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
background-color: #B9B9B9;
|
||||
border: none;
|
||||
color: white;
|
||||
outline: none;
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
margin: 4px 2px;
|
||||
|
|
@ -114,25 +134,49 @@ export default defineComponent({
|
|||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
.inputModifier {
|
||||
min-width: 181px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
font-family: "SF Compact Display";
|
||||
position: fixed;
|
||||
margin-top: 141px;
|
||||
background-color: #58C4FD;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
padding: 10px 30px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button2 {
|
||||
position: fixed;
|
||||
margin-top: 225px;
|
||||
.submitButton:active {
|
||||
background-color: #4296C3;
|
||||
}
|
||||
|
||||
.createAccountButton {
|
||||
position: absolute;
|
||||
border: none;
|
||||
background-color: #e6e6e6;
|
||||
background-color: #EBEBEB;
|
||||
text-decoration: none;
|
||||
color: #58C4FD;
|
||||
font-weight: bold;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -59,14 +59,13 @@ export default defineComponent({
|
|||
<style lang="scss" scoped>
|
||||
|
||||
#splash {
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #EBEBEB;
|
||||
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
// border-radius: 20px;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
Loading…
Reference in a new issue