fix(App): refine notification logic for incoming messages; enhance button formatting in changelog modal; improve icon color handling in user icon component
This commit is contained in:
@@ -735,7 +735,7 @@ export default {
|
|||||||
|
|
||||||
// show notification for new messages if window is not focussed
|
// show notification for new messages if window is not focussed
|
||||||
// only for incoming messages
|
// only for incoming messages
|
||||||
if (!document.hasFocus() && json.lxmf_message?.is_incoming) {
|
if (!document.hasFocus() && json.lxmf_message?.is_incoming === true) {
|
||||||
NotificationUtils.showNewMessageNotification(
|
NotificationUtils.showNewMessageNotification(
|
||||||
json.remote_identity_name,
|
json.remote_identity_name,
|
||||||
json.lxmf_message?.content
|
json.lxmf_message?.content
|
||||||
|
|||||||
@@ -50,7 +50,13 @@
|
|||||||
<div v-else-if="error" class="flex flex-col items-center justify-center h-full text-center space-y-4">
|
<div v-else-if="error" class="flex flex-col items-center justify-center h-full text-center space-y-4">
|
||||||
<v-icon icon="mdi-alert-circle-outline" size="64" color="red"></v-icon>
|
<v-icon icon="mdi-alert-circle-outline" size="64" color="red"></v-icon>
|
||||||
<div class="text-red-500 font-bold text-lg">{{ error }}</div>
|
<div class="text-red-500 font-bold text-lg">{{ error }}</div>
|
||||||
<v-btn color="blue" variant="flat" class="font-bold uppercase px-6" rounded="lg" @click="fetchChangelog">
|
<v-btn
|
||||||
|
color="blue"
|
||||||
|
variant="flat"
|
||||||
|
class="font-bold uppercase px-6"
|
||||||
|
rounded="lg"
|
||||||
|
@click="fetchChangelog"
|
||||||
|
>
|
||||||
Retry
|
Retry
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +137,13 @@
|
|||||||
<div v-else-if="error" class="flex flex-col items-center justify-center py-20 text-center space-y-4">
|
<div v-else-if="error" class="flex flex-col items-center justify-center py-20 text-center space-y-4">
|
||||||
<v-icon icon="mdi-alert-circle-outline" size="64" color="red"></v-icon>
|
<v-icon icon="mdi-alert-circle-outline" size="64" color="red"></v-icon>
|
||||||
<div class="text-red-500 font-bold text-lg">{{ error }}</div>
|
<div class="text-red-500 font-bold text-lg">{{ error }}</div>
|
||||||
<v-btn color="blue" variant="flat" class="font-bold uppercase px-6" rounded="lg" @click="fetchChangelog">
|
<v-btn
|
||||||
|
color="blue"
|
||||||
|
variant="flat"
|
||||||
|
class="font-bold uppercase px-6"
|
||||||
|
rounded="lg"
|
||||||
|
@click="fetchChangelog"
|
||||||
|
>
|
||||||
Retry
|
Retry
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="iconName"
|
v-else-if="iconName"
|
||||||
class="p-[15%] rounded-full shrink-0 flex items-center justify-center"
|
class="p-[15%] rounded-full shrink-0 flex items-center justify-center"
|
||||||
:style="{ 'background-color': iconBackgroundColour }"
|
:style="{ 'background-color': finalBackgroundColor }"
|
||||||
:class="iconClass || 'size-6'"
|
:class="iconClass || 'size-6'"
|
||||||
>
|
>
|
||||||
<MaterialDesignIcon :icon-name="iconName" class="w-full h-full" :style="{ color: iconForegroundColour }" />
|
<MaterialDesignIcon :icon-name="iconName" class="w-full h-full" :style="{ color: finalForegroundColor }" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -52,5 +52,17 @@ export default {
|
|||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
finalForegroundColor() {
|
||||||
|
return this.iconForegroundColour && this.iconForegroundColour !== ""
|
||||||
|
? this.iconForegroundColour
|
||||||
|
: "#6b7280";
|
||||||
|
},
|
||||||
|
finalBackgroundColor() {
|
||||||
|
return this.iconBackgroundColour && this.iconBackgroundColour !== ""
|
||||||
|
? this.iconBackgroundColour
|
||||||
|
: "#e5e7eb";
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
mdiIconName() {
|
mdiIconName() {
|
||||||
if (!this.iconName) return "mdiAccountOutline";
|
if (!this.iconName) return "mdiAccountOutline";
|
||||||
|
|
||||||
// if already starts with mdi and is camelCase, return as is
|
// if already starts with mdi and is camelCase, return as is
|
||||||
if (this.iconName.startsWith("mdi") && /[A-Z]/.test(this.iconName)) {
|
if (this.iconName.startsWith("mdi") && /[A-Z]/.test(this.iconName)) {
|
||||||
return this.iconName;
|
return this.iconName;
|
||||||
@@ -41,7 +41,7 @@ export default {
|
|||||||
"mdi" +
|
"mdi" +
|
||||||
this.iconName
|
this.iconName
|
||||||
.split("-")
|
.split("-")
|
||||||
.filter(word => word.length > 0)
|
.filter((word) => word.length > 0)
|
||||||
.map((word) => {
|
.map((word) => {
|
||||||
// capitalise first letter of each part
|
// capitalise first letter of each part
|
||||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||||
@@ -50,12 +50,18 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
iconPath() {
|
iconPath() {
|
||||||
if (!mdi) return "";
|
if (!mdi || Object.keys(mdi).length === 0) {
|
||||||
|
console.error("MDI library not loaded or empty");
|
||||||
const path = mdi[this.mdiIconName];
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = this.mdiIconName;
|
||||||
|
const path = mdi[name];
|
||||||
|
|
||||||
if (path) return path;
|
if (path) return path;
|
||||||
|
|
||||||
// fallback logic
|
// fallback logic
|
||||||
|
console.warn(`Icon not found: ${name} (original: ${this.iconName})`);
|
||||||
return mdi["mdiHelpCircleOutline"] || mdi["mdiProgressQuestion"] || "";
|
return mdi["mdiHelpCircleOutline"] || mdi["mdiProgressQuestion"] || "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -265,7 +265,11 @@
|
|||||||
{{ $t("about.backend_dependencies") }}
|
{{ $t("about.backend_dependencies") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
|
||||||
<div v-for="(version, name) in appInfo.dependencies" :key="name" class="flex flex-col">
|
<div
|
||||||
|
v-for="(version, name) in appInfo.dependencies"
|
||||||
|
:key="name"
|
||||||
|
class="flex flex-col"
|
||||||
|
>
|
||||||
<span class="text-[10px] font-black text-blue-500/70 uppercase">{{
|
<span class="text-[10px] font-black text-blue-500/70 uppercase">{{
|
||||||
name.replace("_", " ")
|
name.replace("_", " ")
|
||||||
}}</span>
|
}}</span>
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-3 items-center bg-white/50 dark:bg-zinc-800/50 p-3 rounded-lg border border-gray-200 dark:border-zinc-700">
|
<div
|
||||||
|
class="flex flex-wrap gap-3 items-center bg-white/50 dark:bg-zinc-800/50 p-3 rounded-lg border border-gray-200 dark:border-zinc-700"
|
||||||
|
>
|
||||||
<div class="relative flex-1 min-w-[200px]">
|
<div class="relative flex-1 min-w-[200px]">
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<MaterialDesignIcon icon-name="magnify" class="w-4 h-4 text-gray-400" />
|
<MaterialDesignIcon icon-name="magnify" class="w-4 h-4 text-gray-400" />
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
@input="debouncedSearch"
|
@input="debouncedSearch"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
v-model="level"
|
v-model="level"
|
||||||
class="block pl-3 pr-10 py-2 text-base border-gray-300 dark:border-zinc-600 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md bg-white dark:bg-zinc-900 text-gray-900 dark:text-white"
|
class="block pl-3 pr-10 py-2 text-base border-gray-300 dark:border-zinc-600 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md bg-white dark:bg-zinc-900 text-gray-900 dark:text-white"
|
||||||
@@ -61,15 +63,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 overflow-hidden glass-card max-w-6xl mx-auto w-full p-0 flex flex-col rounded-sm">
|
<div class="flex-1 overflow-hidden glass-card max-w-6xl mx-auto w-full p-0 flex flex-col rounded-sm">
|
||||||
<div class="flex-1 overflow-auto p-4 font-mono text-[10px] sm:text-xs leading-relaxed select-text bg-white dark:bg-zinc-950">
|
<div
|
||||||
|
class="flex-1 overflow-auto p-4 font-mono text-[10px] sm:text-xs leading-relaxed select-text bg-white dark:bg-zinc-950"
|
||||||
|
>
|
||||||
<div v-if="logs.length === 0" class="text-gray-500 italic text-center py-10">
|
<div v-if="logs.length === 0" class="text-gray-500 italic text-center py-10">
|
||||||
{{ loading ? 'Loading logs...' : 'No logs found matching your criteria.' }}
|
{{ loading ? "Loading logs..." : "No logs found matching your criteria." }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(log, index) in logs"
|
v-for="(log, index) in logs"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="border-b border-gray-100 dark:border-zinc-900 py-1 flex gap-3 hover:bg-gray-50 dark:hover:bg-zinc-900/50"
|
class="border-b border-gray-100 dark:border-zinc-900 py-1 flex gap-3 hover:bg-gray-50 dark:hover:bg-zinc-900/50"
|
||||||
:class="{'bg-red-50/30 dark:bg-red-900/10': log.is_anomaly}"
|
:class="{ 'bg-red-50/30 dark:bg-red-900/10': log.is_anomaly }"
|
||||||
>
|
>
|
||||||
<span class="text-gray-400 shrink-0">{{ formatTime(log.timestamp) }}</span>
|
<span class="text-gray-400 shrink-0">{{ formatTime(log.timestamp) }}</span>
|
||||||
<span :class="levelClass(log.level)" class="w-12 shrink-0 font-bold uppercase">{{
|
<span :class="levelClass(log.level)" class="w-12 shrink-0 font-bold uppercase">{{
|
||||||
@@ -80,16 +84,21 @@
|
|||||||
>
|
>
|
||||||
<span class="text-gray-800 dark:text-gray-200 break-words flex-1">
|
<span class="text-gray-800 dark:text-gray-200 break-words flex-1">
|
||||||
{{ log.message }}
|
{{ log.message }}
|
||||||
<span v-if="log.is_anomaly" class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 uppercase">
|
<span
|
||||||
|
v-if="log.is_anomaly"
|
||||||
|
class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 uppercase"
|
||||||
|
>
|
||||||
<MaterialDesignIcon icon-name="alert-circle" class="w-2.5 h-2.5 mr-1" />
|
<MaterialDesignIcon icon-name="alert-circle" class="w-2.5 h-2.5 mr-1" />
|
||||||
{{ log.anomaly_type || 'anomaly' }}
|
{{ log.anomaly_type || "anomaly" }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<div class="px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-zinc-800 bg-gray-50 dark:bg-zinc-900/50">
|
<div
|
||||||
|
class="px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-zinc-800 bg-gray-50 dark:bg-zinc-900/50"
|
||||||
|
>
|
||||||
<div class="flex-1 flex justify-between sm:hidden">
|
<div class="flex-1 flex justify-between sm:hidden">
|
||||||
<button
|
<button
|
||||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
|
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
|
||||||
@@ -226,7 +235,7 @@ export default {
|
|||||||
formatTime(timestamp) {
|
formatTime(timestamp) {
|
||||||
try {
|
try {
|
||||||
// If timestamp is a number (Unix timestamp from Python), multiply by 1000 for JS
|
// If timestamp is a number (Unix timestamp from Python), multiply by 1000 for JS
|
||||||
const ts = typeof timestamp === 'number' ? timestamp * 1000 : timestamp;
|
const ts = typeof timestamp === "number" ? timestamp * 1000 : timestamp;
|
||||||
const date = new Date(ts);
|
const date = new Date(ts);
|
||||||
return date.toLocaleString();
|
return date.toLocaleString();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -242,7 +251,12 @@ export default {
|
|||||||
return "text-gray-400";
|
return "text-gray-400";
|
||||||
},
|
},
|
||||||
async copyLogs() {
|
async copyLogs() {
|
||||||
const logText = this.logs.map((l) => `${this.formatTime(l.timestamp)} [${l.level}] [${l.module}] ${l.message}${l.is_anomaly ? ' [ANOMALY:' + l.anomaly_type + ']' : ''}`).join("\n");
|
const logText = this.logs
|
||||||
|
.map(
|
||||||
|
(l) =>
|
||||||
|
`${this.formatTime(l.timestamp)} [${l.level}] [${l.module}] ${l.message}${l.is_anomaly ? " [ANOMALY:" + l.anomaly_type + "]" : ""}`
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(logText);
|
await navigator.clipboard.writeText(logText);
|
||||||
ToastUtils.success("Logs on this page copied to clipboard");
|
ToastUtils.success("Logs on this page copied to clipboard");
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
<div class="overflow-y-auto">
|
<div class="overflow-y-auto">
|
||||||
<div class="max-w-4xl mx-auto p-4 space-y-6">
|
<div class="max-w-4xl mx-auto p-4 space-y-6">
|
||||||
<!-- Header with Preview -->
|
<!-- Header with Preview -->
|
||||||
<div
|
<div class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800">
|
||||||
class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800"
|
|
||||||
>
|
|
||||||
<div class="p-6 border-b border-gray-200 dark:border-zinc-800">
|
<div class="p-6 border-b border-gray-200 dark:border-zinc-800">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -66,9 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Color Selection -->
|
<!-- Color Selection -->
|
||||||
<div
|
<div class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800">
|
||||||
class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800"
|
|
||||||
>
|
|
||||||
<div class="p-4 border-b border-gray-200 dark:border-zinc-800">
|
<div class="p-4 border-b border-gray-200 dark:border-zinc-800">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Colors</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Colors</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,9 +143,17 @@
|
|||||||
@click="onIconClick(mdiIconName)"
|
@click="onIconClick(mdiIconName)"
|
||||||
>
|
>
|
||||||
<LxmfUserIcon
|
<LxmfUserIcon
|
||||||
|
:key="
|
||||||
|
mdiIconName +
|
||||||
|
(iconName === mdiIconName ? iconForegroundColour + iconBackgroundColour : '')
|
||||||
|
"
|
||||||
:icon-name="mdiIconName"
|
:icon-name="mdiIconName"
|
||||||
:icon-foreground-colour="iconForegroundColour"
|
:icon-foreground-colour="
|
||||||
:icon-background-colour="iconBackgroundColour"
|
iconName === mdiIconName ? iconForegroundColour : '#6b7280'
|
||||||
|
"
|
||||||
|
:icon-background-colour="
|
||||||
|
iconName === mdiIconName ? iconBackgroundColour : '#e5e7eb'
|
||||||
|
"
|
||||||
icon-class="size-8"
|
icon-class="size-8"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user