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) {
#content {
padding: 1.2rem;
padding: 12px;
padding-top: 30px;
}
footer {
padding-bottom: 16px;
padding-bottom: 56px;
}
}
</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 router from '../router'

View File

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

View File

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

View File

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

View File

@@ -150,3 +150,29 @@ i:hover {
.btn {
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">
import { ref, onMounted, onUnmounted, watch, nextTick } from "vue";
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 MessageList from "./MessageList.vue";
import MessageInput from "./MessageInput.vue";
@@ -128,6 +128,8 @@ onUnmounted(() => {
.empty-state {
text-align: center;
font-size: 1.2rem;
user-select: none;
-webkit-user-select: none;
}
.empty-state i {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
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 requests = ref<FriendRequest[]>([])

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import { login } from '../stores/auth.ts'
import { login } from '../store.ts'
import { useRouter } from "vue-router";
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 type { LoginResponse } from '../types/api'
import { apiFetch } from '../api/client'
import type { LoginResponse } from './types'
import { apiFetch } from './api/client'
let store: Store | null = null
@@ -11,6 +11,20 @@ async function getStore() {
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() {
const s = await getStore()
const token = await s.get<string>('token')