improved mobile layout

This commit is contained in:
2025-12-29 10:47:36 +01:00
parent 24a460e6e2
commit 610fd74e89
19 changed files with 144 additions and 73 deletions

View File

@@ -42,11 +42,12 @@ footer {
@media (max-width: 720px) { @media (max-width: 720px) {
#content { #content {
padding: 1.2rem; padding: 12px;
padding-top: 30px;
} }
footer { footer {
padding-bottom: 16px; padding-bottom: 56px;
} }
} }
</style> </style>

View File

@@ -1,4 +1,4 @@
import { initAuth, logout } from '../stores/auth.ts' import { initAuth, logout } from '../store.ts'
import { API } from '../main.ts' import { API } from '../main.ts'
import router from '../router' import router from '../router'

View File

@@ -1,5 +1,5 @@
import { apiFetch } from './client' import { apiFetch } from './client'
import type { Friend, FriendRequest } from '../types/api' import type { Friend, FriendRequest } from '../api'
export function fetchFriends() { export function fetchFriends() {
return apiFetch<Friend[]>('/friends') return apiFetch<Friend[]>('/friends')

View File

@@ -1,5 +1,5 @@
import { apiFetch } from './client' import { apiFetch } from './client'
import type { Message } from '../types/api' import type { Message } from '../api'
export function fetchMessages(roomUuid: string) { export function fetchMessages(roomUuid: string) {
return apiFetch<Message[]>(`/messages/${roomUuid}`) return apiFetch<Message[]>(`/messages/${roomUuid}`)

View File

@@ -1,5 +1,5 @@
import { apiFetch } from './client' import { apiFetch } from './client'
import type { Room } from '../types/api' import type { Room } from '../api'
export function fetchRooms(userUuid: string) { export function fetchRooms(userUuid: string) {
return apiFetch<Room[]>(`/rooms/${userUuid}`) return apiFetch<Room[]>(`/rooms/${userUuid}`)

View File

@@ -150,3 +150,29 @@ i:hover {
.btn { .btn {
outline: none; outline: none;
} }
@media (hover: hover) {
i:hover {
color: var(--text);
}
button:hover, .button:hover {
background: var(--accent-hover);
}
input[type="checkbox"]:hover {
border-color: var(--accent);
}
::-webkit-scrollbar-thumb:hover {
filter: brightness(1.2);
}
}
button:active, .nav-item:active {
opacity: 0.7;
}
* {
-webkit-tap-highlight-color: transparent;
}

View File

@@ -21,7 +21,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from "vue"; import { ref, onMounted, onUnmounted, watch, nextTick } from "vue";
import { fetchMessages, sendMessage, getWsToken } from "../api/messages"; import { fetchMessages, sendMessage, getWsToken } from "../api/messages";
import type { Message } from "../types/api"; import type { Message } from "../types";
import { API_WS } from '../main.ts' import { API_WS } from '../main.ts'
import MessageList from "./MessageList.vue"; import MessageList from "./MessageList.vue";
import MessageInput from "./MessageInput.vue"; import MessageInput from "./MessageInput.vue";
@@ -128,6 +128,8 @@ onUnmounted(() => {
.empty-state { .empty-state {
text-align: center; text-align: center;
font-size: 1.2rem; font-size: 1.2rem;
user-select: none;
-webkit-user-select: none;
} }
.empty-state i { .empty-state i {

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { createRoom } from '../api/rooms' import { createRoom } from '../api/rooms'
import type { Room } from '../types/api' import type { Room } from '../types'
const name = ref('') const name = ref('')
const global = ref(false) const global = ref(false)

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Message } from '../types/api' import type { Message } from '../api'
defineProps<{ messages: Message[] }>() defineProps<{ messages: Message[] }>()
</script> </script>

View File

@@ -18,7 +18,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { logout as authLogout } from '../stores/auth.ts' import { logout as authLogout } from '../store.ts'
const router = useRouter() const router = useRouter()
@@ -38,6 +38,10 @@ function logout() {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 100vh; border-radius: 100vh;
z-index: 50; z-index: 50;
user-select: none;
-webkit-user-select: none;
} }
.nav-item { .nav-item {
@@ -67,18 +71,20 @@ function logout() {
transform 0.15s ease; transform 0.15s ease;
} }
@media (hover: hover) {
.nav-item:hover { .nav-item:hover {
background: rgba(255, 255, 255, 0.04); background: rgba(255, 255, 255, 0.04);
} }
.nav-item:not(.router-link-active):hover {
background: rgba(255, 255, 255, 0.04);
}
.nav-item:not(.router-link-active):hover i { .nav-item:not(.router-link-active):hover i {
color: var(--text); color: var(--text);
} }
.nav-item.logout:hover i {
color: rgba(255, 80, 80, 0.8);
}
}
.router-link-active i { .router-link-active i {
color: var(--accent); color: var(--accent);
} }

View File

@@ -9,7 +9,7 @@
<div class="scroll-area"> <div class="scroll-area">
<router-link v-for="room in rooms" :key="room.uuid" :to="`/rooms/${room.uuid}`" class="btn room-item" <router-link v-for="room in rooms" :key="room.uuid" :to="`/rooms/${room.uuid}`" class="btn room-item"
:class="{ active: route.params.uuid === room.uuid }"> :class="{ active: route.params.uuid === room.uuid }" @click="emit('select-room')">
<div class="room-info"> <div class="room-info">
<span class="room-name">{{ room.name }}</span> <span class="room-name">{{ room.name }}</span>
<span class="room-owner">by {{ room.owner_name }}</span> <span class="room-owner">by {{ room.owner_name }}</span>
@@ -22,15 +22,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { initAuth } from '../stores/auth.ts'; import { initAuth } from '../store.ts';
import { fetchRooms } from '../api/rooms'; import { fetchRooms } from '../api/rooms';
import type { Room } from '../types/api'; import type { Room } from '../types';
import CreateRoomModal from './CreateRoomModal.vue'; import CreateRoomModal from './CreateRoomModal.vue';
const route = useRoute(); const route = useRoute();
const showCreate = ref(false); const showCreate = ref(false);
const rooms = ref<Room[]>([]); const rooms = ref<Room[]>([]);
const emit = defineEmits(['select-room']);
onMounted(async () => { onMounted(async () => {
const auth = await initAuth(); const auth = await initAuth();
rooms.value = await fetchRooms(auth.uuid!); rooms.value = await fetchRooms(auth.uuid!);
@@ -42,6 +44,8 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
user-select: none;
-webkit-user-select: none;
} }
.rooms-header { .rooms-header {

View File

@@ -1,7 +1,7 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import router from './router' import router from './router.ts'
import App from './App.vue' import App from './App.vue'
import { validateToken } from './stores/auth.ts' import { validateToken } from './store.ts'
import './base.css' import './base.css'
@@ -15,5 +15,5 @@ async function init() {
init() init()
export const API = 'http://127.0.0.1:8080' export const API = 'http://192.168.1.183:8081'
export const API_WS = 'ws://127.0.0.1:8080/ws' export const API_WS = 'ws://192.168.1.183:8081/ws'

View File

@@ -6,7 +6,7 @@
<aside class="sidebar" :class="{ 'is-open': isSidebarOpen }"> <aside class="sidebar" :class="{ 'is-open': isSidebarOpen }">
<div class="sidebar-content"> <div class="sidebar-content">
<RoomList /> <RoomList @select-room="handleRoomSelection" />
</div> </div>
</aside> </aside>
@@ -24,8 +24,13 @@ import RoomList from "../components/RoomList.vue";
import ChatWindow from "../components/ChatWindow.vue"; import ChatWindow from "../components/ChatWindow.vue";
defineProps<{ uuid: string }>(); defineProps<{ uuid: string }>();
const isSidebarOpen = ref(true); const isSidebarOpen = ref(true);
const handleRoomSelection = () => {
if (window.innerWidth <= 720) {
isSidebarOpen.value = false;
}
};
</script> </script>
<style scoped> <style scoped>
@@ -64,6 +69,7 @@ const isSidebarOpen = ref(true);
.chat-window-container { .chat-window-container {
flex: 1; flex: 1;
padding-left: 38px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
@@ -124,8 +130,8 @@ const isSidebarOpen = ref(true);
z-index: 15; z-index: 15;
} }
.chat-window-container { .menu-toggle:hover i {
padding-top: 50px; color: var(--muted);
} }
} }
</style> </style>

View File

@@ -43,7 +43,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { fetchFriends, fetchFriendRequests, acceptFriendRequest, sendFriendRequest } from '../api/friends' import { fetchFriends, fetchFriendRequests, acceptFriendRequest, sendFriendRequest } from '../api/friends'
import type { Friend, FriendRequest } from '../types/api' import type { Friend, FriendRequest } from '../api'
const friends = ref<Friend[]>([]) const friends = ref<Friend[]>([])
const requests = ref<FriendRequest[]>([]) const requests = ref<FriendRequest[]>([])

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { login } from '../stores/auth.ts' import { login } from '../store.ts'
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
const email = ref(""); const email = ref("");

52
src/router.ts Normal file
View File

@@ -0,0 +1,52 @@
import { createRouter, createWebHistory } from 'vue-router'
import { initAuth, getLastRoom, setLastRoom } from './store'
import LoginPage from './pages/LoginPage.vue'
import ChatPage from './pages/ChatPage.vue'
import FriendListPage from './pages/FriendListPage.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'root', component: { render: () => null } },
{
path: '/login',
name: 'login',
component: LoginPage,
meta: { hideNavbar: true }
},
{
path: '/rooms/:uuid',
name: 'chat',
component: ChatPage,
props: true
},
{ path: '/friendlist', component: FriendListPage }
],
})
router.beforeEach(async (to) => {
if (to.path === '/login') return true
const auth = await initAuth()
if (!auth.isAuthenticated) {
return '/login'
}
// Handle the redirect from "/" to the last room
if (to.path === '/') {
const lastRoom = await getLastRoom()
return `/rooms/${lastRoom || 'none'}`
}
return true
})
// Save the room UUID to storage after every successful navigation to a chat room
router.afterEach((to) => {
if (to.name === 'chat' && to.params.uuid) {
setLastRoom(to.params.uuid as string)
}
})
export default router

View File

@@ -1,40 +0,0 @@
import { createRouter, createWebHistory } from 'vue-router'
import { initAuth } from '../stores/auth.ts'
import LoginPage from '../pages/LoginPage.vue'
import ChatPage from '../pages/ChatPage.vue'
import FriendListPage from '../pages/FriendListPage.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', redirect: '/rooms/none' },
{
path: '/login',
name: 'login',
component: LoginPage,
meta: { hideNavbar: true }
},
{
path: '/rooms/:uuid',
name: 'chat',
component: ChatPage,
props: true
},
{ path: '/friendlist', component: FriendListPage }
],
})
router.beforeEach(async (to) => {
if (to.path === '/login') return true
const auth = await initAuth()
if (!auth.isAuthenticated) {
return '/login'
}
return true
})
export default router

View File

@@ -1,6 +1,6 @@
import { load, Store } from '@tauri-apps/plugin-store' import { load, Store } from '@tauri-apps/plugin-store'
import type { LoginResponse } from '../types/api' import type { LoginResponse } from './types'
import { apiFetch } from '../api/client' import { apiFetch } from './api/client'
let store: Store | null = null let store: Store | null = null
@@ -11,6 +11,20 @@ async function getStore() {
return store return store
} }
export async function setLastRoom(uuid: string) {
if (!uuid || uuid === 'none') return
const s = await getStore()
await s.set('last_room_uuid', uuid)
await s.save()
}
export async function getLastRoom(): Promise<string | null> {
const s = await getStore()
const lastRoom = await s.get<string>('last_room_uuid')
return lastRoom ?? null
}
export async function initAuth() { export async function initAuth() {
const s = await getStore() const s = await getStore()
const token = await s.get<string>('token') const token = await s.get<string>('token')