},
template: `
- <main id="app" v-if="account !== null">
+ <div id="app" v-if="account !== null">
<Header />
<Messenger v-if="account"/>
<Login v-else />
- </main>
+ </div>
<Splash v-else />`
}
const saveLocation = "input_item_position";
-const defaultPosition = { x: 15, y: window.innerHeight - 300 };
+const defaultPosition = { x: 15, y: 400 };
//---Dragabble Helper Funcs--------------------------------------
return elementPosition
}
-function draggify(elementId, margin) {
+function draggify(elementId, parentId, margin) {
let element;
+ let parent;
/* only compatible with elements having equal width and height */
let elementLength;
let xShort;
let yShort;
- // Keep track of window size;
- let winW = window.innerWidth;
- let winH = window.innerHeight;
-
//---Reposition Anime-----------------------------------------------
// Move element to target smoothly.
const repositionAnime = (xChange, yChange) => {
+
const xStep = xChange / 6000;
const yStep = yChange / 6000;
const onMouseMove = (e) => {
e.preventDefault();
- const inputItemRect = element.getBoundingClientRect();
- elementX.value = inputItemRect.left + e.movementX;
- elementY.value = inputItemRect.top + e.movementY;
+ elementX.value = element.offsetLeft + e.movementX;
+ elementY.value = element.offsetTop + e.movementY;
}
// Add an event listener for dragging.
let x = 0; // vector change
let y = 0;
+ const winW = parent.clientWidth;
+ const winH = parent.clientHeight;
+
if (elementX.value < 0) {
x = (elementX.value - margin)*-1
}
// Update position of targetEl on windowResize.
const onWindowResize = (_e) => {
- // Update window dimensions.
- winW = window.innerWidth;
- winH = window.innerHeight;
-
- elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, winW);
- elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, winH);
+ elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, parent.clientWidth);
+ elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, parent.clientHeight);
savePosition();
}
Vue.onMounted(() => {
element = document.getElementById(elementId);
+ parent = document.getElementById(parentId);
elementLength = element.offsetWidth;
// Initialize the positional references.
- percentX = elementX.value / window.innerWidth;
- percentY = elementY.value / window.innerHeight;
+ percentX = elementX.value / parent.clientWidth;
+ percentY = elementY.value / parent.clientHeight;
- xShort = calcSideProximity(elementX.value, elementLength, window.innerWidth);
- yShort = calcSideProximity(elementX.value, elementLength, window.innerHeight);
+ xShort = calcSideProximity(elementX.value, elementLength, parent.clientWidth);
+ yShort = calcSideProximity(elementX.value, elementLength, parent.clientHeight);
// Then, we can listen for window resize (and mousedown).
element.addEventListener("mousedown", onMouseDown);
- window.addEventListener('resize', onWindowResize)
+ parent.addEventListener('resize', onWindowResize)
});
// remove event listeners on component dismount.
Vue.onUnmounted(() => {
element.removeEventListener('mousedown', onMouseDown);
- window.removeEventListener('resize', onWindowResize)
- window.removeEventListener('mouseup', onMouseUp)
- window.removeEventListener('mousemove', onMouseMove);
+ parent.removeEventListener('resize', onWindowResize)
+ parent.removeEventListener('mouseup', onMouseUp)
+ parent.removeEventListener('mousemove', onMouseMove);
});
// Try loading initPosition, otherwise set default values
--- /dev/null
+const debounce = (fn, delay) => {
+
+ let timeoutId;
+
+ const wrapper = (...args) => {
+
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ timeoutId = setTimeout(() => {
+ fn(...args);
+ }, delay);
+
+ }
+
+ return wrapper;
+}
+
+
+
+const SmartForm =
+{
+ props: ["fields", "channel", "modifier"],
+
+ setup(props)
+ {
+ let logsEl;
+
+ const submit = debounce(async (data) => {
+
+ const err = await fetch(props.channel, data);
+
+ if (err) {
+ logsEl.style.color == "red";
+ logsEl.innerText = err;
+ return;
+ }
+
+ logsEl.style.color = "green";
+ logsEl.innerText = props.modifier;
+
+ }, 2000);
+
+ Vue.onMounted(() => {
+
+ const el = document.getElementById("smart-form");
+
+ logsEl = el.querySelector(".smart-form-logs");
+
+ logsEl.innerText = props.modifier;
+
+ el.addEventListener("input", () => {
+ submit(new FormData(el))
+ });
+
+ });
+ },
+
+ template: `
+ <form id="smart-form" class="smart-form">
+
+ <div v-for="field in fields" style="display:flex">
+
+ <input type="text" class="text-field"
+ :name="field.name"
+ :value="field.value"
+ :placeholder="field.placeholder"
+ />
+
+ </div>
+
+ <div class="smart-form-logs"></div>
+
+ </form>`
+}
+
+export default SmartForm;
const recording = Vue.ref(false);
- const { elementX, elementY } = draggify("input", 15);
+ const { elementX, elementY } = draggify("input", "app", 15);
const { leftSide, show } = useText("input-text", elementX);
+import SmartForm from "./form.js";
+
const Login =
{
+ components: {
+ SmartForm
+ },
+
setup()
{
- const usr = Vue.ref("");
- const pwd = Vue.ref("");
-
- const submitForm = async () => {
- const success = await window.mainApi.invoke("login", usr.value, pwd.value);
- if (!success) shake("login-form");
- }
-
- return { usr, pwd, submitForm };
+ return;
},
template: `
- <form id="login" @submit.prevent="submitForm">
-
- <!-- username and password forms -->
- <div id="login-form">
- <!-- username -->
- <div class="login-input-box">
- <input class="login-input" type="text" v-model="usr" />
- <div class="login-input-modifier">Email</div>
- </div>
-
- <!-- password -->
- <div class="login-input-box">
- <input class="login-input" type="password" v-model="pwd" />
- <div class="login-input-modifier">Password</div>
- </div>
- </div>
-
- <!-- submit button; position: fixed -->
- <button
- class="login-submit-button button"
- type="submit"
- >
- Login
- </button>
-
- </form> `
-}
-
-/** Shake a div to indicate incorrect credentials */
-const shake = (elementId) =>
-{
- const el = document.getElementById(elementId);
-
- if (el)
- el.classList.remove("shake");
-
- setTimeout(() => {
- if (el) el.classList.add("shake");
- }, 250);
+ <div id="login">
+
+ <!-- <SmartForm
+ :fields="[{
+ name: 'email',
+ value: null,
+ placeholder: 'Email',
+ },
+ {
+ name: 'password',
+ value: null,
+ placeholder: 'Password'
+ },
+ {
+ name: 'repassword',
+ value: null,
+ placeholder: 'Re-Password'
+ }]"
+ :channel="'auth'"
+ :modifier="'Login or re-enter password to register.'"
+ /> -->
+
+ </div>`
}
export default Login;
\ No newline at end of file
template: `
<div :id="id" :class="'message ' + modifier + '-message'">
- <Bubble
+ <Bubble
v-for="(c, index) in content"
+ :id="'bubble-' + id + '-' + index"
:category="c.category"
:text="c.text"
:html="c.html"
@dragover.prevent
@dragenter.prevent
>
- <Message
- v-for="message in messages"
- :modifier="message.modifier"
- :content="message.content"
- :context="message.context"
- :avatar="message.avatar"
- :id="message.id"
+
+ <Message
+ v-for="message in messages"
+ v-bind="message"
/>
+
</div> `
}
await window.mainApi.send("logout");
}
- return { showSettings, active, logout };
+ return { showSettings, active, logout, window };
},
template: `
</div>
<button v-else class="settings-icon" @click="showSettings">
- <img src="./assets/settingsIcon.svg">
+ <img :src="window.path + 'assets/settingsIcon.svg'">
</button>`
}
});
- return { status }
+ return { status, window }
},
template: `
<div id="ws">
- <img id="ws-logo" src="./assets/connectLogo.svg"
+ <img id="ws-logo" :src="window.path + 'assets/connectLogo.svg'"
:class="{ move: status }"
>
import App from "./components/app.js";
+
+window.path = "./";
+
Vue.createApp(App).mount("#app");
+@font-face {
+ font-family: "Default";
+ src: url("assets/fonts/SF-Pro-Text-Regular.otf");
+}
+
+@font-face {
+ font-family: "Compact";
+ src: url("assets/fonts/SF-Compact-Display-Bold.otf");
+}
+
html, body {
margin: 0;
padding: 0;
/* The first word of the class is the component that it modifys. */
#splash {
- width: 100vw;
- height: 100vh;
+ width: 100%;
+ height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#app {
- font-family: "SF Pro Text";
+ position: relative; /* must explicitly be declared */
+ font-family: "Default";
-webkit-font-smoothing: antialiased;
- height: 100vh;
- width: 100vw;
+ height: 100vh; /* 100% for website */
+ width: 100vw; /* 100% for website */
border-radius: 15px;
}
#header-titlebar {
- position: fixed;
- width: 100vw;
+ position: absolute;
+ width: 100%;
height: 54px;
opacity: 0.75;
background-color: #EBEBEB;
border: none;
outline: none;
z-index: 1;
+ border-top-left-radius: 15px;
+ border-top-right-radius: 15px;
}
.contacts {
}
.contacts .email {
- font-family: SF Compact Display;
+ font-family: "Compact";
font-size: 12px;
color: #898989;
overflow: w;
}
#header-menu {
- position: fixed;
+ position: absolute;
margin-left: 20px;
margin-top: 20px;
z-index: 2;
justify-content: center;
}
-#login-form {
- font-family: "SF Compact Display";
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding-bottom: 20px;
-}
-
-.login-input-box {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- margin-bottom: 10px;
-}
-
-.login-input {
- background-color: #D3D3D3;
- border: none;
- outline: none;
- padding: 16px;
- text-decoration: none;
- margin: 4px 2px;
- cursor: pointer;
- border-radius: 10px;
-}
-
-.login-input-modifier {
- min-width: 181px;
- font-size: 12px;
- font-weight: bold;
- text-align: left;
- margin-left: 15px;
-}
-
-.login-submit-button {
- font-family: "SF Compact Display";
- position: fixed;
- margin-top: 260px;
- background-color: #58C4FD;
- color: white;
- padding: 10px 30px;
- border-radius: 20px;
- font-size: 14px;
- font-weight: bold;
-}
-
-.login-submit-button:active {
- background-color: #4296C3;
-}
-
-/* generic shaking functionality */
-.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);
- }
-}
-
#messenger {
- width: 100vw;
- height: 100vh;
+ width: 100%;
+ height: 100%;
overflow: auto;
}
}
#ws {
- position: fixed;
- width: 100vw;
+ position: absolute;
+ width: 100%;
height: 54px;
display: flex;
align-items: center;
}
#settings {
- position: fixed;
- width: 100vw;
- height: 100vh;
+ position: absolute;
+ width: 100%;
+ height: 100%;
background-color: rgba(235, 235, 235, 0.75);
z-index: 4;
display: flex;
align-items: center;
animation-name: settings-appear;
animation-duration: 0.5s;
+ border-radius: inherit;
}
@keyframes settings-appear {
}
.settings-icon {
- position: fixed;
+ position: absolute;
right: 0;
border: none;
outline: none;
}
.settings-logout-button {
- font-family: "SF Compact Display";
+ font-family: "Compact";
background-color: #B7B7B7;
- padding: 10px 30px;
+ padding: 10px 20px;
border-radius: 20px;
font-size: 14px;
- font-weight: bold;
}
.settings-logout-button:hover {
}
.message {
- width: 100vw;
+ width: inherit;
display: flex;
flex-direction: column;
padding-top: 9px;
display: flex;
justify-content: center;
align-items: center;
- font-family: "SF Compact Display";
+ font-family: "Compact";
font-size: 12px;
- font-weight: bold;
color: #9B9B9B;
margin-bottom: 18px;
}
.bubble {
position: relative;
max-width: 66vw;
- font-family: "SF Pro Text";
font-size: 14px;
border-radius: 18px;
margin-bottom: 4px;
min-height: 14px;
display: flex;
align-items: center;
- font-family: "SF Compact Display";
+ font-family: "Compact";
font-size: 12px;
- font-weight: bold;
margin-top: 5px;
}
cursor: pointer;
}*/
+/* shake a div to signal error*/
+.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);
+ }
+}
+
.audio {
animation-name: audio-anim;
animation-duration: 2s;
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
}
}
+
+.smart-form {
+ width: 225px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+}
+
+.text-field {
+ width: 100%;
+ padding: 16px;
+ background: none;
+ border: none;
+ margin: 4px;
+ font-size: 14px;
+ border-radius: 10px;
+ background-color: #d1d1d1;
+}
+
+.smart-form-logs {
+ font-family: "Compact";
+ font-size: 12px;
+ text-align: left;
+ margin-left: 10px;
+}