improved mobile layout
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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}`)
|
||||||
|
|||||||
@@ -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}`)
|
||||||
|
|||||||
26
src/base.css
26
src/base.css
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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,16 +71,18 @@ function logout() {
|
|||||||
transform 0.15s ease;
|
transform 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item:hover {
|
@media (hover: hover) {
|
||||||
background: rgba(255, 255, 255, 0.04);
|
.nav-item:hover {
|
||||||
}
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item:not(.router-link-active):hover {
|
.nav-item:not(.router-link-active):hover i {
|
||||||
background: rgba(255, 255, 255, 0.04);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item:not(.router-link-active):hover i {
|
.nav-item.logout:hover i {
|
||||||
color: var(--text);
|
color: rgba(255, 80, 80, 0.8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.router-link-active i {
|
.router-link-active i {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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[]>([])
|
||||||
|
|||||||
@@ -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
52
src/router.ts
Normal 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
|
||||||
@@ -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
|
|
||||||
@@ -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')
|
||||||
Reference in New Issue
Block a user