fixed and improved profile pictures
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"@fluent/bundle": "^0.19.1",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-http": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-store": "~2",
|
||||
|
||||
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@@ -444,6 +444,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-store",
|
||||
|
||||
@@ -27,4 +27,5 @@ tauri-plugin-http = "2"
|
||||
tauri-plugin-websocket = "2"
|
||||
tauri-plugin-upload = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
{
|
||||
"url": "http://127.0.0.1:*"
|
||||
},
|
||||
{
|
||||
"url": "http://192.168.1.183:*"
|
||||
},
|
||||
{
|
||||
"url": "https://alatreon.org/chatapp/*"
|
||||
}
|
||||
@@ -27,6 +30,9 @@
|
||||
{
|
||||
"url": "ws://127.0.0.1:*"
|
||||
},
|
||||
{
|
||||
"url": "http://192.168.1.183:*"
|
||||
},
|
||||
{
|
||||
"url": "wss://alatreon.org/chatapp/*"
|
||||
}
|
||||
@@ -34,7 +40,14 @@
|
||||
},
|
||||
"websocket:default",
|
||||
"upload:default",
|
||||
"dialog:default"
|
||||
"dialog:default",
|
||||
"fs:default",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
@@ -7,6 +7,7 @@ fn greet(name: &str) -> String {
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
.plugin(tauri_plugin_websocket::init())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UpdateUserResponse } from '../types';
|
||||
import { apiFetch } from './client'
|
||||
import { upload } from '@tauri-apps/plugin-upload';
|
||||
import { getAuthData } from '../authStore';
|
||||
// import { upload } from '@tauri-apps/plugin-upload';
|
||||
import { getAuthData } from '../store';
|
||||
import { API } from '../main.ts';
|
||||
|
||||
export function updateSettings(username: string, email: string, password: string) {
|
||||
@@ -12,24 +12,41 @@ export function updateSettings(username: string, email: string, password: string
|
||||
}
|
||||
|
||||
export async function uploadAvatar(
|
||||
filePath: string,
|
||||
fileData: Uint8Array,
|
||||
onProgress: (progress: number, total: number) => void
|
||||
) {
|
||||
const auth = await getAuthData();
|
||||
const url = `${API}/account/upload-avatar`;
|
||||
const headers = new Map<string, string>([
|
||||
['Authorization', `Bearer ${auth.token}`],
|
||||
['Content-Type', 'application/octet-stream']
|
||||
]);
|
||||
|
||||
return upload(
|
||||
url,
|
||||
filePath,
|
||||
({ progress, total }) => {
|
||||
onProgress(progress, total);
|
||||
},
|
||||
headers
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', url);
|
||||
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${auth.token}`);
|
||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||
|
||||
// Handle Progress
|
||||
if (xhr.upload && onProgress) {
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
onProgress(event.loaded, event.total);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Response
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject(new Error(`Upload failed with status ${xhr.status}: ${xhr.responseText}`));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => reject(new Error('Network error during upload'));
|
||||
|
||||
xhr.send(fileData);
|
||||
});
|
||||
}
|
||||
|
||||
export function getAvatar(uuid: string): string {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { getAuthData, clearAuthData } from '../authStore'
|
||||
import { getAuthData, clearAuthData } from '../store'
|
||||
import { API } from '../main.ts'
|
||||
import router from '../router'
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { load, Store } from '@tauri-apps/plugin-store'
|
||||
import { UpdateUserResponse, User } from './types'
|
||||
import { getAvatar } from './api/account'
|
||||
|
||||
let store: Store | null = null
|
||||
|
||||
async function getStore() {
|
||||
if (!store) store = await load('store.json')
|
||||
return store
|
||||
}
|
||||
|
||||
export async function getAuthData() {
|
||||
const s = await getStore()
|
||||
const token = await s.get<string>('token')
|
||||
const user = await s.get<User>('user')
|
||||
return { token: token || null, user: user || null, isAuthenticated: !!token }
|
||||
}
|
||||
|
||||
export async function saveAuthData(token: string, user: User) {
|
||||
const s = await getStore()
|
||||
await s.set('token', token)
|
||||
await s.set('user', user)
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function clearAuthData() {
|
||||
const s = await getStore()
|
||||
s.clear()
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function updateLocalUser(newData: UpdateUserResponse, uuid?: string) {
|
||||
const updatedUser = {
|
||||
username: newData.username,
|
||||
email: newData.email,
|
||||
uuid,
|
||||
avatar_url: `${getAvatar(uuid || '')}?t=${Date.now()}`
|
||||
}
|
||||
|
||||
const s = await getStore()
|
||||
await s.set('user', updatedUser)
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function refreshLocalUser() {
|
||||
const s = await getStore()
|
||||
const user = await s.get<User>('user')
|
||||
|
||||
if (user) {
|
||||
user.avatar_url = `${getAvatar(user.uuid)}?t=${Date.now()}`
|
||||
|
||||
await s.set('user', user)
|
||||
await s.save()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
return (await s.get<string>('last_room_uuid')) ?? null
|
||||
}
|
||||
|
||||
export async function saveLocalePreference(locale: string) {
|
||||
const s = await getStore()
|
||||
await s.set('language', locale)
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function getLocalePreference(): Promise<string | null> {
|
||||
const s = await getStore()
|
||||
return (await s.get<string>('language')) ?? null
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import MessageList from "./MessageList.vue";
|
||||
import MessageInput from "./MessageInput.vue";
|
||||
import InvitePeopleModal from './InvitePeopleModal.vue';
|
||||
import WebSocket from '@tauri-apps/plugin-websocket';
|
||||
import { getAuthData } from "../authStore.ts";
|
||||
import { getAuthData } from "../store.ts";
|
||||
import { fetchRoomInfo } from "../api/rooms.ts";
|
||||
|
||||
const props = defineProps<{ uuid: string }>();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ul>
|
||||
<li v-for="(m, i) in messages" :key="i" class="message" :class="{ 'is-me': m.sender_uuid === currentUserUuid }">
|
||||
<div class="sender-info">
|
||||
<img :src="getAvatar(m.sender_uuid)" @error="handleAvatarError" class="sender-avatar" />
|
||||
<img :src="getAvatarUrl(m.sender_uuid)" @error="handleAvatarError" class="sender-avatar" />
|
||||
<div class="sender">{{ m.sender }}</div>
|
||||
<span class="timestamp">{{ m.sent_at }}</span>
|
||||
</div>
|
||||
@@ -11,13 +11,12 @@
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { Message } from '../types'
|
||||
import { getAvatar } from '../api/account.ts'
|
||||
import { getAvatarUrl } from '../store.ts'
|
||||
import defaultAvatar from '../assets/default-avatar.png'
|
||||
import { getAuthData } from '../authStore';
|
||||
import { getAuthData } from '../store.ts';
|
||||
|
||||
defineProps<{ messages: Message[] }>()
|
||||
|
||||
@@ -72,7 +71,7 @@ ul {
|
||||
/* border-radius: var(--radius) var(--radius) 0 0; */
|
||||
/* background-color: rgba(255, 255, 255, 0.02); */
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
padding: 5px 18px;
|
||||
}
|
||||
|
||||
.sender-avatar {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { updateSettings } from '../api/account'
|
||||
import { updateLocalUser } from '../authStore'
|
||||
import { updateLocalUser } from '../store'
|
||||
import type { User } from '../types'
|
||||
import { useFluent } from 'fluent-vue';
|
||||
|
||||
|
||||
@@ -41,8 +41,11 @@ import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { uploadAvatar } from '../api/account';
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { refreshLocalUser } from '../authStore';
|
||||
// import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { readFile } from '@tauri-apps/plugin-fs';
|
||||
import { refreshLocalUser } from '../store.ts';
|
||||
import { getAuthData } from '../store.ts';
|
||||
import { refreshAvatar } from '../store.ts';
|
||||
|
||||
const emit = defineEmits(['close', 'updated']);
|
||||
|
||||
@@ -59,24 +62,13 @@ let unlistenHover: UnlistenFn;
|
||||
let unlistenLeave: UnlistenFn;
|
||||
|
||||
onMounted(async () => {
|
||||
// When files are hovered over the window
|
||||
unlistenHover = await listen('tauri://drag-enter', () => {
|
||||
isDragging.value = true;
|
||||
});
|
||||
|
||||
unlistenLeave = await listen('tauri://drag-leave', () => {
|
||||
isDragging.value = false;
|
||||
});
|
||||
|
||||
// When files are dropped
|
||||
unlistenHover = await listen('tauri://drag-enter', () => isDragging.value = true);
|
||||
unlistenLeave = await listen('tauri://drag-leave', () => isDragging.value = false);
|
||||
unlistenDrag = await listen<{ paths: string[] }>('tauri://drag-drop', (event) => {
|
||||
isDragging.value = false;
|
||||
const path = event.payload.paths[0];
|
||||
if (path && isImage(path)) {
|
||||
setFile(path);
|
||||
} else {
|
||||
errorMessage.value = "Please drop a valid image file.";
|
||||
}
|
||||
if (path && isImage(path)) setFile(path);
|
||||
else errorMessage.value = "Please drop a valid image file.";
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,6 +76,8 @@ onUnmounted(() => {
|
||||
if (unlistenDrag) unlistenDrag();
|
||||
if (unlistenHover) unlistenHover();
|
||||
if (unlistenLeave) unlistenLeave();
|
||||
// Clean up
|
||||
if (previewUrl.value) URL.revokeObjectURL(previewUrl.value);
|
||||
});
|
||||
|
||||
async function pickFile() {
|
||||
@@ -94,18 +88,30 @@ async function pickFile() {
|
||||
});
|
||||
|
||||
if (selected && typeof selected === 'string') {
|
||||
setFile(selected);
|
||||
await setFile(selected);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to set path and preview
|
||||
function setFile(path: string) {
|
||||
selectedPath.value = path;
|
||||
previewUrl.value = convertFileSrc(path);
|
||||
errorMessage.value = '';
|
||||
// Async function to read file and create blob URL
|
||||
async function setFile(path: string) {
|
||||
try {
|
||||
selectedPath.value = path;
|
||||
|
||||
const contents = await readFile(path);
|
||||
const blob = new Blob([contents]);
|
||||
|
||||
if (previewUrl.value) URL.revokeObjectURL(previewUrl.value);
|
||||
|
||||
previewUrl.value = URL.createObjectURL(blob);
|
||||
|
||||
errorMessage.value = '';
|
||||
} catch (e) {
|
||||
console.error("Error reading file:", e);
|
||||
errorMessage.value = "Could not read image file.";
|
||||
}
|
||||
}
|
||||
|
||||
function isImage(path: string) {
|
||||
@@ -120,17 +126,25 @@ async function handleUpload() {
|
||||
uploadProgress.value = 0;
|
||||
|
||||
try {
|
||||
await uploadAvatar(selectedPath.value, (progress, total) => {
|
||||
const fileBytes = await readFile(selectedPath.value);
|
||||
|
||||
await uploadAvatar(fileBytes, (progress, total) => {
|
||||
uploadProgress.value = Math.round((progress / total) * 100);
|
||||
});
|
||||
|
||||
await refreshLocalUser();
|
||||
|
||||
// Trigger global UI refresh
|
||||
const auth = await getAuthData();
|
||||
if (auth.user) {
|
||||
refreshAvatar(auth.user.uuid);
|
||||
}
|
||||
|
||||
emit('updated');
|
||||
emit('close');
|
||||
} catch (err: any) {
|
||||
console.error("Upload failed:", err);
|
||||
errorMessage.value = 'Failed to upload avatar. Please try again.';
|
||||
errorMessage.value = err.message || 'Failed to upload avatar';
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
@@ -254,6 +268,8 @@ async function handleUpload() {
|
||||
.error-message {
|
||||
color: var(--error);
|
||||
font-size: 0.9rem;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { validateToken } from './store.ts'
|
||||
import { fluent, setLanguage } from './i18n'
|
||||
|
||||
import './base.css'
|
||||
import { getLocalePreference } from './authStore.ts'
|
||||
import { getLocalePreference } from './store.ts'
|
||||
|
||||
async function init() {
|
||||
await validateToken()
|
||||
@@ -28,7 +28,9 @@ async function init() {
|
||||
|
||||
init()
|
||||
|
||||
export const API = 'http://127.0.0.1:8080'
|
||||
// export const API = 'http://127.0.0.1:8080'
|
||||
export const API = 'http://192.168.1.183:8080'
|
||||
// export const API = 'https://alatreon.org/chatapp'
|
||||
export const API_WS = 'ws://127.0.0.1:8080/ws'
|
||||
// export const API_WS = 'ws://127.0.0.1:8080/ws'
|
||||
export const API_WS = 'ws://192.168.1.183:8080/ws'
|
||||
// export const API_WS = 'wss://alatreon.org/chatapp/ws'
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<h1>{{ $t('settings-title') }}</h1>
|
||||
|
||||
<UpdateAccountModal v-if="showUpdateModal" :user="user" @close="showUpdateModal = false" @updated="fetchUserData" />
|
||||
<UploadAvatarModal v-if="showAvatarModal" @close="showAvatarModal = false" @updated="fetchUserData" />
|
||||
<UploadAvatarModal v-if="showAvatarModal" @close="showAvatarModal = false" />
|
||||
|
||||
<h2>{{ $t('settings-account') }}</h2>
|
||||
<div v-if="user" class="info-card">
|
||||
<div class="avatar-display">
|
||||
<img :src="getAvatar(user.uuid)" @error="handleAvatarError" class="avatar-img" />
|
||||
<img :src="getAvatarUrl(user.uuid)" @error="handleAvatarError" class="avatar-img" />
|
||||
|
||||
<button class="update-btn" @click="showAvatarModal = true">
|
||||
{{ $t('settings-upload-avatar-btn') || 'Change Avatar' }}
|
||||
@@ -49,21 +49,25 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { logout as authLogout } from '../store.ts'
|
||||
import { getAuthData } from "../authStore.ts"
|
||||
import { getAuthData } from "../store.ts"
|
||||
import type { User } from "../types"
|
||||
import UpdateAccountModal from '../components/UpdateAccountModal.vue'
|
||||
import { useFluent } from 'fluent-vue'
|
||||
import { saveLocalePreference, getLocalePreference } from "../authStore.ts"
|
||||
import { saveLocalePreference, getLocalePreference } from "../store.ts"
|
||||
import { getSupportedLanguagesMetadata, setLanguage } from '../i18n'
|
||||
import UploadAvatarModal from '../components/UploadAvatarModal.vue'
|
||||
import defaultAvatar from '../assets/default-avatar.png'
|
||||
import { getAvatar } from '../api/account.ts'
|
||||
import { getAvatarUrl } from '../store.ts'
|
||||
|
||||
const handleAvatarError = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement;
|
||||
img.src = defaultAvatar;
|
||||
};
|
||||
|
||||
async function handleAvatarUpdated() {
|
||||
await fetchUserData()
|
||||
}
|
||||
|
||||
const showAvatarModal = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
111
src/store.ts
111
src/store.ts
@@ -1,15 +1,90 @@
|
||||
import { apiFetch } from './api/client'
|
||||
import type { LoginResponse, User } from './types'
|
||||
import * as authStore from './authStore'
|
||||
import { ref, computed } from 'vue'
|
||||
import { fetchFriendRequests } from './api/friends'
|
||||
import { fetchRoomInvites } from './api/rooms'
|
||||
import type { FriendRequest, RoomInvite } from './types'
|
||||
import { getAvatar } from './api/account'
|
||||
import { load, Store } from '@tauri-apps/plugin-store'
|
||||
import { UpdateUserResponse } from './types'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
let store: Store | null = null
|
||||
export const initAuth = getAuthData
|
||||
|
||||
async function getStore() {
|
||||
if (!store) store = await load('store.json')
|
||||
return store
|
||||
}
|
||||
|
||||
export async function getAuthData() {
|
||||
const s = await getStore()
|
||||
const token = await s.get<string>('token')
|
||||
const user = await s.get<User>('user')
|
||||
return { token: token || null, user: user || null, isAuthenticated: !!token }
|
||||
}
|
||||
|
||||
export async function saveAuthData(token: string, user: User) {
|
||||
const s = await getStore()
|
||||
await s.set('token', token)
|
||||
await s.set('user', user)
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function clearAuthData() {
|
||||
const s = await getStore()
|
||||
s.clear()
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function updateLocalUser(newData: UpdateUserResponse, uuid?: string) {
|
||||
const updatedUser = {
|
||||
username: newData.username,
|
||||
email: newData.email,
|
||||
uuid,
|
||||
avatar_url: `${getAvatar(uuid || '')}?t=${Date.now()}`
|
||||
}
|
||||
|
||||
const s = await getStore()
|
||||
await s.set('user', updatedUser)
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function refreshLocalUser() {
|
||||
const s = await getStore()
|
||||
const user = await s.get<User>('user')
|
||||
|
||||
if (user) {
|
||||
user.avatar_url = `${getAvatar(user.uuid)}?t=${Date.now()}`
|
||||
|
||||
await s.set('user', user)
|
||||
await s.save()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
return (await s.get<string>('last_room_uuid')) ?? null
|
||||
}
|
||||
|
||||
export async function saveLocalePreference(locale: string) {
|
||||
const s = await getStore()
|
||||
await s.set('language', locale)
|
||||
await s.save()
|
||||
}
|
||||
|
||||
export async function getLocalePreference(): Promise<string | null> {
|
||||
const s = await getStore()
|
||||
return (await s.get<string>('language')) ?? null
|
||||
}
|
||||
|
||||
export const initAuth = authStore.getAuthData
|
||||
export const getLastRoom = authStore.getLastRoom
|
||||
export const setLastRoom = authStore.setLastRoom
|
||||
|
||||
export async function login(email: string, username: string, password: string) {
|
||||
const res: LoginResponse = await apiFetch('/login', {
|
||||
@@ -21,19 +96,19 @@ export async function login(email: string, username: string, password: string) {
|
||||
uuid: res.uuid,
|
||||
username: res.username,
|
||||
email: res.email,
|
||||
avatar_url: await getAvatar(res.uuid)
|
||||
avatar_url: getAvatar(res.uuid)
|
||||
};
|
||||
await authStore.saveAuthData(res.token, user)
|
||||
await saveAuthData(res.token, user)
|
||||
return { token: res.token, uuid: res.uuid, isAuthenticated: true }
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
await authStore.clearAuthData()
|
||||
await clearAuthData()
|
||||
return { token: null, uuid: null, isAuthenticated: false }
|
||||
}
|
||||
|
||||
export async function validateToken(): Promise<boolean> {
|
||||
const auth = await authStore.getAuthData()
|
||||
const auth = await getAuthData()
|
||||
if (!auth.token) return false
|
||||
|
||||
try {
|
||||
@@ -62,7 +137,7 @@ export function useNotifications() {
|
||||
const totalCount = computed(() => requests.value.length + invites.value.length)
|
||||
|
||||
async function refreshNotifications() {
|
||||
const auth = await authStore.getAuthData()
|
||||
const auth = await getAuthData()
|
||||
if (!auth.token) {
|
||||
return
|
||||
}
|
||||
@@ -86,3 +161,21 @@ export function useNotifications() {
|
||||
refreshNotifications
|
||||
}
|
||||
}
|
||||
|
||||
// A reactive object to store the last updated timestamp for each user
|
||||
const avatarTimestamps = reactive<Record<string, number>>({})
|
||||
|
||||
// Generates the avatar URL with a timestamp
|
||||
export function getAvatarUrl(uuid: string | undefined | null) {
|
||||
if (!uuid) return ''
|
||||
|
||||
if (!avatarTimestamps[uuid]) {
|
||||
avatarTimestamps[uuid] = Date.now()
|
||||
}
|
||||
|
||||
return `${getAvatar(uuid)}?t=${avatarTimestamps[uuid]}`
|
||||
}
|
||||
|
||||
export function refreshAvatar(uuid: string) {
|
||||
avatarTimestamps[uuid] = Date.now()
|
||||
}
|
||||
|
||||
@@ -409,6 +409,13 @@
|
||||
dependencies:
|
||||
"@tauri-apps/api" "^2.8.0"
|
||||
|
||||
"@tauri-apps/plugin-fs@~2":
|
||||
version "2.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-fs/-/plugin-fs-2.4.5.tgz#4b32b6de32e2ee735632bff356fab09fcc281b42"
|
||||
integrity sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "^2.8.0"
|
||||
|
||||
"@tauri-apps/plugin-http@~2":
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-http/-/plugin-http-2.5.4.tgz#998a9cd02efa006fcbeddd92e8e51434ff3804dd"
|
||||
|
||||
Reference in New Issue
Block a user