Add support for mobile devices
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
ab71601ae9
commit
c5914d3a28
|
@ -11,7 +11,7 @@
|
|||
<meta name="application-name" content="Mercury">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, height=500px, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover, user-scalable=no" />
|
||||
<title>Mercury</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
43
src/App.vue
43
src/App.vue
|
@ -11,10 +11,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid vh-100">
|
||||
<div class="row vh-100">
|
||||
<router-view />
|
||||
</div>
|
||||
<div class="d-flex flex-row h-100">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -23,12 +21,19 @@ import { onMounted, ref, watch } from 'vue'
|
|||
import { Modal } from 'bootstrap'
|
||||
import { useMercuryStore } from './stores/mercuryStore'
|
||||
import UserSettings from './components/userSettings/UserSettings.vue'
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
|
||||
const mercuryStore = useMercuryStore()
|
||||
const configured = mercuryStore.configured()
|
||||
|
||||
const settingsModal = ref()
|
||||
|
||||
const { width, height } = useWindowSize()
|
||||
watch(width, () => {
|
||||
mercuryStore.mobile = window.innerWidth < 768 ? true : false
|
||||
mercuryStore.showSidebar = !mercuryStore.mobile
|
||||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
const modal = new Modal(settingsModal.value)
|
||||
|
||||
|
@ -44,9 +49,39 @@ onMounted(() => {
|
|||
}, { immediate: true })
|
||||
})
|
||||
|
||||
visualViewport.addEventListener('resize', () => {
|
||||
window.scrollTo(0, 0)
|
||||
document.querySelector('#app').style.height = `${window.visualViewport.height}px`
|
||||
})
|
||||
|
||||
document.addEventListener('gesturestart', (e) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
|
||||
document.addEventListener('touchmove', (e) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html, body, #app {
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
*:not(.overflow-auto, .overflow-auto *) {
|
||||
touch-action: none;
|
||||
}
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export const animateCSS = (element, animation, prefix = 'animate__') => new Promise((resolve, reject) => {
|
||||
const animationName = `${prefix}${animation}`;
|
||||
const animationName = `${prefix}${animation}`
|
||||
|
||||
element.classList.add(`${prefix}animated`, animationName);
|
||||
element.classList.add(`${prefix}animated`, animationName)
|
||||
|
||||
function handleAnimationEnd(event) {
|
||||
event.stopPropagation()
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<h3>Add channel</h3>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input v-model="name" type="text" class="form-control" placeholder="Channel name" aria-label="Channel name">
|
||||
</div>
|
||||
<div class="col">
|
||||
<input v-model="key" type="text" class="form-control" placeholder="Channel key (base64)" aria-label="Channel key">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input v-model="name" type="text" class="form-control" placeholder="Channel name" aria-label="Channel name">
|
||||
</div>
|
||||
<div class="d-flex flex-row mt-3">
|
||||
<button @click="addChannel" type="submit" class="btn btn-primary ms-auto">Add channel</button>
|
||||
<div class="col">
|
||||
<input v-model="key" type="text" class="form-control" placeholder="Channel key (base64)" aria-label="Channel key">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row mt-3">
|
||||
<button @click="addChannel" type="submit" class="btn btn-primary ms-auto">Add channel</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
<template>
|
||||
<div class="col g-0">
|
||||
<div class="d-flex flex-column vh-100">
|
||||
<div class="flex-fill h-100">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<header class="bg-light px-3 py-2 d-flex flex-row align-items-center">
|
||||
<span class="me-3 fs-3">
|
||||
<i v-if="mobile && !showSidebar" @click="showSidebar = true" class="me-3 fs-3 bi bi-three-dots"></i>
|
||||
<h2 class="mb-0">
|
||||
<slot name="title"></slot>
|
||||
</span>
|
||||
<slot name="header"></slot>
|
||||
</h2>
|
||||
<div class="ms-2">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<span v-if="!home" class="ms-auto fs-4">
|
||||
<router-link :to="{ name: 'Home' }"><i class="text-dark bi bi-house-fill"></i></router-link>
|
||||
</span>
|
||||
</header>
|
||||
<div class="px-3 py-2 overflow-auto">
|
||||
<div id="content" class="px-3 py-2 overflow-auto mb-auto">
|
||||
<slot name="content"></slot>
|
||||
<div ref="bottom"></div>
|
||||
</div>
|
||||
<div class="px-3 py-2 mt-auto">
|
||||
<div class="px-3 py-2">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,11 +27,15 @@
|
|||
<script setup>
|
||||
import { computed, onMounted, onUpdated, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMercuryStore } from '../../stores/mercuryStore'
|
||||
|
||||
const props = defineProps({ autoScroll: Boolean })
|
||||
|
||||
const route = useRoute()
|
||||
const home = computed(() => { return route.name == 'Home' })
|
||||
const mercuryStore = useMercuryStore()
|
||||
const { mobile, showSidebar } = storeToRefs(mercuryStore)
|
||||
|
||||
// Scroll to bottom.
|
||||
const bottom = ref(null)
|
||||
|
@ -37,9 +44,16 @@ onUpdated(() => {
|
|||
bottom.value.scrollIntoView({ behavior: "smooth", block: "center" })
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoScroll) {
|
||||
bottom.value.scrollIntoView({ block: "center" })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#content {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
</style>
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useUserStore } from '../../stores/userStore'
|
||||
import Constants from '../../common/constants'
|
||||
import { useMercuryStore } from '../../stores/mercuryStore';
|
||||
import { useUserStore } from '../../stores/userStore'
|
||||
import { useMercuryStore } from '../../stores/mercuryStore'
|
||||
|
||||
const props = defineProps(['message'])
|
||||
const message = props.message
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="input-group mb-3">
|
||||
<input ref="input" @keyup.enter="sendMessage" v-model="message" type="text" class="form-control" placeholder="Message" aria-label="Message" autofocus autocomplete="off">
|
||||
<input ref="inputField" @keyup.enter="sendMessage" v-model="message" type="text" class="form-control" placeholder="Message" aria-label="Message" autocomplete="off">
|
||||
<button @click="sendMessage" class="btn btn-primary" type="button"><i class="bi bi-send-fill"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -21,11 +21,11 @@ const messageStore = useMessageStore()
|
|||
const channel = computed(() => { return props.channel })
|
||||
const message = ref("")
|
||||
|
||||
const input = ref(null)
|
||||
const inputField = ref(null)
|
||||
|
||||
const sendMessage = () => {
|
||||
if (message.value.trim() === '') {
|
||||
animateCSS(input.value, 'headShake')
|
||||
animateCSS(inputField.value, 'headShake')
|
||||
} else {
|
||||
ws.send(packers[MessageTypes.Basic](encoder.encode(message.value), channel.value.rawKey))
|
||||
messageStore.addMessage(channel.value.id, -1, message.value)
|
||||
|
@ -35,6 +35,5 @@ const sendMessage = () => {
|
|||
|
||||
onBeforeRouteUpdate(() => {
|
||||
message.value = ''
|
||||
input.value.focus()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<h5 class="text-muted">Channels</h5>
|
||||
<nav class="d-flex flex-column">
|
||||
<router-link v-for="channel in channelStore.channels" :key="channel.id" :to="{ name: 'Channel', params: { channelId: channel.id }}" class="text-light">
|
||||
{{ channel.name }}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column mb-3">
|
||||
<span v-if="connected" class="text-light"><i class="bi bi-circle-fill text-success"></i> Connected</span>
|
||||
<span v-else class="text-light"><i class="bi bi-circle-fill text-warning"></i> Connecting...</span>
|
||||
<router-link :to="{ name: 'Settings' }" class="text-muted"><i class="bi bi-gear-fill"></i> Settings</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMercuryStore } from '../../stores/mercuryStore'
|
||||
const mercuryStore = useMercuryStore()
|
||||
const { connected } = storeToRefs(mercuryStore)
|
||||
</script>
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<div class="col-2 bg-dark">
|
||||
<div class="d-flex flex-column vh-100">
|
||||
<div class="d-flex flex-row logo align-items-center py-2">
|
||||
<img src="../../assets/logo-dark.png" class="me-2" />
|
||||
<span class="fs-3 text-light">Mercury</span>
|
||||
<div v-show="showSidebar" id="sidebar" :class="{ mobile: mobile }" class="px-3 bg-dark h-100">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div>
|
||||
<div class="d-flex flex-row logo align-items-center py-2">
|
||||
<img src="../../assets/logo-dark.png" class="me-2" />
|
||||
<h2 class="mb-0 text-light">Mercury</h2>
|
||||
<i @click="showSidebar = false" v-if="mobile && showSidebar" class="ms-auto fs-3 text-white bi bi-x-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="configured">
|
||||
<h5 class="text-muted">Channels</h5>
|
||||
<div id="navigation" class="overflow-auto mt-3 mb-auto">
|
||||
<Channels />
|
||||
<slot name="top"></slot>
|
||||
</div>
|
||||
<div class="d-flex flex-column mt-auto mb-3">
|
||||
<span v-if="connected" class="text-light"><i class="bi bi-circle-fill text-success"></i> Connected</span>
|
||||
<span v-else class="text-light"><i class="bi bi-circle-fill text-warning"></i> Connecting...</span>
|
||||
<router-link :to="{ name: 'Settings' }" class="text-muted"><i class="bi bi-gear-fill"></i> Settings</router-link>
|
||||
<div class="mt-3">
|
||||
<Footer />
|
||||
<slot name="bottom"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,13 +21,23 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMercuryStore } from '../../stores/mercuryStore'
|
||||
import Channels from './Channels.vue'
|
||||
import Footer from './Footer.vue'
|
||||
|
||||
const mercuryStore = useMercuryStore()
|
||||
const { connected } = storeToRefs(mercuryStore)
|
||||
const configured = mercuryStore.configured()
|
||||
|
||||
const { mobile, showSidebar } = storeToRefs(mercuryStore)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#sidebar {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
#navigation {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column">
|
||||
<User :user="mercuryStore.user" />
|
||||
<div v-for="user in users" :key="user.id" class="d-flex flex-row align-items-center">
|
||||
<User :user="user" />
|
||||
</div>
|
||||
<User :user="mercuryStore.user" />
|
||||
<div v-for="user in users" :key="user.id">
|
||||
<User :user="user" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -11,7 +9,7 @@
|
|||
import { computed } from 'vue'
|
||||
import { useMercuryStore } from '../../stores/mercuryStore'
|
||||
import { useUserStore } from '../../stores/userStore'
|
||||
import User from './User.vue'
|
||||
import User from '../common/User.vue'
|
||||
|
||||
const props = defineProps(['channel'])
|
||||
const channelId = computed(() => { return props.channel.id })
|
|
@ -1,5 +1,4 @@
|
|||
<template>
|
||||
|
||||
<div ref="picker" class="btn w-100"><div class="label">Set username color</div></div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -36,5 +36,4 @@ function save() {
|
|||
mercuryStore.setUserColor(color.value)
|
||||
requestUserData()
|
||||
}
|
||||
|
||||
</script>
|
|
@ -148,13 +148,13 @@ export default {
|
|||
switch (incomingPacket.messageType) {
|
||||
case MessageTypes.Basic:
|
||||
handleBasicMessage(incomingPacket)
|
||||
break;
|
||||
break
|
||||
case MessageTypes.UserDataRequest:
|
||||
handleUserDataRequest(incomingPacket)
|
||||
break;
|
||||
break
|
||||
case MessageTypes.UserDataResponse:
|
||||
handleUserDataResponse(incomingPacket)
|
||||
break;
|
||||
break
|
||||
|
||||
default:
|
||||
console.error(`Received unknown message type ${incomingPacket.messageType}`);
|
||||
|
|
|
@ -9,7 +9,9 @@ export const useMercuryStore = defineStore({
|
|||
name: '',
|
||||
color: null
|
||||
}),
|
||||
connected: false
|
||||
connected: false,
|
||||
mobile: false,
|
||||
showSidebar: true
|
||||
}),
|
||||
getters: {
|
||||
configured: (state) => {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
</template>
|
||||
</Sidebar>
|
||||
<Content autoScroll>
|
||||
|
||||
<template v-slot:title>{{ channel.name }}</template>
|
||||
<template v-slot:header>
|
||||
<Key :channel="channel" />
|
||||
|
@ -20,16 +21,16 @@
|
|||
</Content>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Sidebar />
|
||||
<Content>
|
||||
<template v-slot:title>Missing channel</template>
|
||||
<template v-slot:content>
|
||||
<div class="d-flex flex-column text-center">
|
||||
<i class="error text-warning bi bi-cone-striped"></i>
|
||||
<h2>That channel does not exist!</h2>
|
||||
</div>
|
||||
</template>
|
||||
</Content>
|
||||
<Sidebar />
|
||||
<Content>
|
||||
<template v-slot:title>Missing channel</template>
|
||||
<template v-slot:content>
|
||||
<div class="d-flex flex-column text-center">
|
||||
<i class="error text-warning bi bi-cone-striped"></i>
|
||||
<h2>That channel does not exist!</h2>
|
||||
</div>
|
||||
</template>
|
||||
</Content>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
@ -40,9 +41,9 @@ import { onBeforeRouteUpdate, useRoute } from 'vue-router'
|
|||
import { useChannelStore } from '../stores/channelStore'
|
||||
import { useMessageStore } from '../stores/messageStore'
|
||||
import Sidebar from '../components/sidebar/Sidebar.vue'
|
||||
import Content from '../components/Content.vue'
|
||||
import Content from '../components/content/Content.vue'
|
||||
import Key from '../components/common/Key.vue'
|
||||
import UserList from '../components/userList/UserList.vue'
|
||||
import UserList from '../components/sidebar/UserList.vue'
|
||||
import Message from '../components/conversation/Message.vue'
|
||||
import MessageInput from '../components/conversation/MessageInput.vue'
|
||||
|
||||
|
@ -53,10 +54,9 @@ const messageStore = useMessageStore()
|
|||
const channel = computed(() => { return channelStore.getChannelById(route.params.channelId) })
|
||||
const messages = computed(() => { return messageStore.getMessagesByChannelId(route.params.channelId) })
|
||||
|
||||
// Show the key.
|
||||
const locked = ref(false)
|
||||
onBeforeRouteUpdate(async (to, from) => { locked.value = false })
|
||||
const abcdef = ref(null)
|
||||
// Show the key in the header.
|
||||
const keyHidden = ref(false)
|
||||
onBeforeRouteUpdate(async (to, from) => { keyHidden.value = false })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<template v-slot:title>Welcome to Mercury!</template>
|
||||
<template v-slot:content>
|
||||
<div class="d-flex flex-column text-center align-items-center px-5 overflow-hidden">
|
||||
<img class="animate__animated animate__rollIn py-5 w-25" id="logo" src="../assets/logo-light.png" />
|
||||
<img class="animate__animated animate__rollIn py-5" id="logo" src="../assets/logo-light.png" />
|
||||
<h1 class="animate__animated animate__zoomIn">
|
||||
Mercury is a web-based BENNC client.
|
||||
</h1>
|
||||
|
@ -18,11 +18,13 @@
|
|||
|
||||
<script setup>
|
||||
import Sidebar from '../components/sidebar/Sidebar.vue'
|
||||
import Content from '../components/Content.vue'
|
||||
import Content from '../components/content/Content.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#logo {
|
||||
width: 25vw;
|
||||
height: auto;
|
||||
filter: invert(80%);
|
||||
}
|
||||
</style>
|
|
@ -11,14 +11,12 @@
|
|||
<AddChannel />
|
||||
</template>
|
||||
</Content>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Sidebar from '../components/sidebar/Sidebar.vue'
|
||||
import Content from '../components/Content.vue'
|
||||
import Content from '../components/content/Content.vue'
|
||||
import UserSettings from '../components/userSettings/UserSettings.vue'
|
||||
import ConfiguredChannels from '../components/channelSettings/ConfiguredChannels.vue'
|
||||
import AddChannel from '../components/channelSettings/AddChannel.vue'
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue