add support for sideband/lxmf user appearance icons
This commit is contained in:
16
database.py
16
database.py
@@ -123,3 +123,19 @@ class LxmfConversationReadState(BaseModel):
|
||||
# define table name
|
||||
class Meta:
|
||||
table_name = "lxmf_conversation_read_state"
|
||||
|
||||
|
||||
class LxmfUserIcon(BaseModel):
|
||||
|
||||
id = BigAutoField()
|
||||
destination_hash = CharField(unique=True) # unique destination hash
|
||||
icon_name = CharField() # material design icon name for the destination hash
|
||||
foreground_colour = CharField() # hex colour to use for foreground (icon colour)
|
||||
background_colour = CharField() # hex colour to use for background (background colour)
|
||||
|
||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc))
|
||||
|
||||
# define table name
|
||||
class Meta:
|
||||
table_name = "lxmf_user_icons"
|
||||
|
||||
46
meshchat.py
46
meshchat.py
@@ -74,6 +74,7 @@ class ReticulumMeshChat:
|
||||
database.CustomDestinationDisplayName,
|
||||
database.LxmfMessage,
|
||||
database.LxmfConversationReadState,
|
||||
database.LxmfUserIcon,
|
||||
])
|
||||
|
||||
# init config
|
||||
@@ -1432,6 +1433,16 @@ class ReticulumMeshChat:
|
||||
else:
|
||||
other_user_hash = source_hash
|
||||
|
||||
# find lxmf user icon from database
|
||||
lxmf_user_icon = None
|
||||
db_lxmf_user_icon = database.LxmfUserIcon.get_or_none(database.LxmfUserIcon.destination_hash == other_user_hash)
|
||||
if db_lxmf_user_icon is not None:
|
||||
lxmf_user_icon = {
|
||||
"icon_name": db_lxmf_user_icon.icon_name,
|
||||
"foreground_colour": db_lxmf_user_icon.foreground_colour,
|
||||
"background_colour": db_lxmf_user_icon.background_colour,
|
||||
}
|
||||
|
||||
# add to conversations
|
||||
conversations.append({
|
||||
"display_name": self.get_lxmf_conversation_name(other_user_hash),
|
||||
@@ -1439,6 +1450,7 @@ class ReticulumMeshChat:
|
||||
"destination_hash": other_user_hash,
|
||||
"is_unread": self.is_lxmf_conversation_unread(other_user_hash),
|
||||
"failed_messages_count": self.lxmf_conversation_failed_messages_count(other_user_hash),
|
||||
"lxmf_user_icon": lxmf_user_icon,
|
||||
# we say the conversation was updated when the latest message was created
|
||||
# otherwise this will go crazy when sending a message, as the updated_at on the latest message changes very frequently
|
||||
"updated_at": created_at,
|
||||
@@ -2000,6 +2012,26 @@ class ReticulumMeshChat:
|
||||
"updated_at": db_lxmf_message.updated_at,
|
||||
}
|
||||
|
||||
# updates the lxmf user icon for the provided destination hash
|
||||
def update_lxmf_user_icon(self, destination_hash: str, icon_name: str, foreground_colour: str, background_colour: str):
|
||||
|
||||
# log
|
||||
print(f"updating lxmf user icon for {destination_hash} to icon_name={icon_name}, foreground_colour={foreground_colour}, background_colour={background_colour}")
|
||||
|
||||
# prepare data to insert or update
|
||||
data = {
|
||||
"destination_hash": destination_hash,
|
||||
"icon_name": icon_name,
|
||||
"foreground_colour": foreground_colour,
|
||||
"background_colour": background_colour,
|
||||
"updated_at": datetime.now(timezone.utc),
|
||||
}
|
||||
|
||||
# upsert to database
|
||||
query = database.LxmfUserIcon.insert(data)
|
||||
query = query.on_conflict(conflict_target=[database.LxmfUserIcon.destination_hash], update=data)
|
||||
query.execute()
|
||||
|
||||
# handle an lxmf delivery from reticulum
|
||||
# NOTE: cant be async, as Reticulum doesn't await it
|
||||
def on_lxmf_delivery(self, lxmf_message: LXMF.LXMessage):
|
||||
@@ -2008,6 +2040,20 @@ class ReticulumMeshChat:
|
||||
# upsert lxmf message to database
|
||||
self.db_upsert_lxmf_message(lxmf_message)
|
||||
|
||||
# get icon appearance if available
|
||||
try:
|
||||
message_fields = lxmf_message.get_fields()
|
||||
if LXMF.FIELD_ICON_APPEARANCE in message_fields:
|
||||
icon_appearance = message_fields[LXMF.FIELD_ICON_APPEARANCE]
|
||||
icon_name = icon_appearance[0]
|
||||
foreground_colour = "#" + icon_appearance[1].hex()
|
||||
background_colour = "#" + icon_appearance[2].hex()
|
||||
self.update_lxmf_user_icon(lxmf_message.source_hash.hex(), icon_name, foreground_colour, background_colour)
|
||||
except Exception as e:
|
||||
print("failed to update lxmf user icon from lxmf message")
|
||||
print(e)
|
||||
pass
|
||||
|
||||
# find message from database
|
||||
db_lxmf_message = database.LxmfMessage.get_or_none(database.LxmfMessage.hash == lxmf_message.hash.hex())
|
||||
if db_lxmf_message is None:
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.13.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"click-outside-vue3": "^4.0.1",
|
||||
"electron-prompt": "^1.7.0",
|
||||
@@ -877,6 +878,11 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mdi/js": {
|
||||
"version": "7.4.47",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
|
||||
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ=="
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"click-outside-vue3": "^4.0.1",
|
||||
"electron-prompt": "^1.7.0",
|
||||
|
||||
34
src/frontend/components/MaterialDesignIcon.vue
Normal file
34
src/frontend/components/MaterialDesignIcon.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" :aria-label="iconName" fill="currentColor" style="display:inline-block;vertical-align:middle;">
|
||||
<path :d="iconPath"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as mdi from "@mdi/js";
|
||||
|
||||
export default {
|
||||
name: "MaterialDesignIcon",
|
||||
props: {
|
||||
iconName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
mdiIconName() {
|
||||
// convert icon name from lxmf icon appearance to format expected by the @mdi/js library
|
||||
// e.g: alien-outline -> mdiAlienOutline
|
||||
// https://pictogrammers.github.io/@mdi/font/5.4.55/
|
||||
return "mdi" + this.iconName.split("-").map((word) => {
|
||||
// capitalise first letter of each part
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
}).join("");
|
||||
},
|
||||
iconPath() {
|
||||
// find icon, otherwise fallback to question mark, and if that doesn't exist, show nothing...
|
||||
return mdi[this.mdiIconName] || mdi["mdiProgressQuestion"] || "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -22,7 +22,10 @@
|
||||
<div v-if="searchedConversations.length > 0" class="w-full">
|
||||
<div @click="onConversationClick(conversation)" v-for="conversation of searchedConversations" class="flex cursor-pointer p-2 border-l-2" :class="[ conversation.destination_hash === selectedDestinationHash ? 'bg-gray-100 border-blue-500' : 'bg-white border-transparent hover:bg-gray-50 hover:border-gray-200' ]">
|
||||
<div class="my-auto mr-2">
|
||||
<div class="bg-gray-200 text-gray-500 p-2 rounded">
|
||||
<div v-if="conversation.lxmf_user_icon" class="p-2 rounded" :style="{ 'color': conversation.lxmf_user_icon.foreground_colour, 'background-color': conversation.lxmf_user_icon.background_colour }">
|
||||
<MaterialDesignIcon :icon-name="conversation.lxmf_user_icon.icon_name" class="w-6 h-6"/>
|
||||
</div>
|
||||
<div v-else class="bg-gray-200 text-gray-500 p-2 rounded">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
|
||||
</svg>
|
||||
@@ -130,9 +133,11 @@
|
||||
|
||||
<script>
|
||||
import Utils from "../../js/Utils";
|
||||
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
|
||||
|
||||
export default {
|
||||
name: 'MessagesSidebar',
|
||||
components: {MaterialDesignIcon},
|
||||
props: {
|
||||
peers: Object,
|
||||
conversations: Array,
|
||||
|
||||
Reference in New Issue
Block a user