add the ability to select a custom profile icon
This commit is contained in:
35
meshchat.py
35
meshchat.py
@@ -23,6 +23,7 @@ from serial.tools import list_ports
|
|||||||
|
|
||||||
import database
|
import database
|
||||||
from src.backend.announce_handler import AnnounceHandler
|
from src.backend.announce_handler import AnnounceHandler
|
||||||
|
from src.backend.colour_utils import ColourUtils
|
||||||
from src.backend.lxmf_message_fields import LxmfImageField, LxmfFileAttachmentsField, LxmfFileAttachment, LxmfAudioField
|
from src.backend.lxmf_message_fields import LxmfImageField, LxmfFileAttachmentsField, LxmfFileAttachment, LxmfAudioField
|
||||||
from src.backend.audio_call_manager import AudioCall, AudioCallManager
|
from src.backend.audio_call_manager import AudioCall, AudioCallManager
|
||||||
|
|
||||||
@@ -1666,6 +1667,18 @@ class ReticulumMeshChat:
|
|||||||
# enable or disable local propagation node
|
# enable or disable local propagation node
|
||||||
self.enable_local_propagation_node(value)
|
self.enable_local_propagation_node(value)
|
||||||
|
|
||||||
|
# update lxmf user icon name in config
|
||||||
|
if "lxmf_user_icon_name" in data:
|
||||||
|
self.config.lxmf_user_icon_name.set(data["lxmf_user_icon_name"])
|
||||||
|
|
||||||
|
# update lxmf user icon foreground colour in config
|
||||||
|
if "lxmf_user_icon_foreground_colour" in data:
|
||||||
|
self.config.lxmf_user_icon_foreground_colour.set(data["lxmf_user_icon_foreground_colour"])
|
||||||
|
|
||||||
|
# update lxmf user icon background colour in config
|
||||||
|
if "lxmf_user_icon_background_colour" in data:
|
||||||
|
self.config.lxmf_user_icon_background_colour.set(data["lxmf_user_icon_background_colour"])
|
||||||
|
|
||||||
# send config to websocket clients
|
# send config to websocket clients
|
||||||
await self.send_config_to_websocket_clients()
|
await self.send_config_to_websocket_clients()
|
||||||
|
|
||||||
@@ -1890,6 +1903,9 @@ class ReticulumMeshChat:
|
|||||||
"lxmf_preferred_propagation_node_destination_hash": self.config.lxmf_preferred_propagation_node_destination_hash.get(),
|
"lxmf_preferred_propagation_node_destination_hash": self.config.lxmf_preferred_propagation_node_destination_hash.get(),
|
||||||
"lxmf_preferred_propagation_node_auto_sync_interval_seconds": self.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get(),
|
"lxmf_preferred_propagation_node_auto_sync_interval_seconds": self.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get(),
|
||||||
"lxmf_preferred_propagation_node_last_synced_at": self.config.lxmf_preferred_propagation_node_last_synced_at.get(),
|
"lxmf_preferred_propagation_node_last_synced_at": self.config.lxmf_preferred_propagation_node_last_synced_at.get(),
|
||||||
|
"lxmf_user_icon_name": self.config.lxmf_user_icon_name.get(),
|
||||||
|
"lxmf_user_icon_foreground_colour": self.config.lxmf_user_icon_foreground_colour.get(),
|
||||||
|
"lxmf_user_icon_background_colour": self.config.lxmf_user_icon_background_colour.get(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# convert audio call to dict
|
# convert audio call to dict
|
||||||
@@ -2418,6 +2434,22 @@ class ReticulumMeshChat:
|
|||||||
audio_field.audio_bytes,
|
audio_field.audio_bytes,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# add icon appearance if configured
|
||||||
|
# fixme: we could save a tiny amount of bandwidth here, but this requires more effort...
|
||||||
|
# we could keep track of when the icon appearance was last sent to this destination, and when it last changed
|
||||||
|
# we could save 6 bytes for the 2x colours, and also however long the icon name is, but not today!
|
||||||
|
lxmf_user_icon_name = self.config.lxmf_user_icon_name.get()
|
||||||
|
lxmf_user_icon_foreground_colour = self.config.lxmf_user_icon_foreground_colour.get()
|
||||||
|
lxmf_user_icon_background_colour = self.config.lxmf_user_icon_background_colour.get()
|
||||||
|
if (lxmf_user_icon_name is not None
|
||||||
|
and lxmf_user_icon_foreground_colour is not None
|
||||||
|
and lxmf_user_icon_background_colour is not None):
|
||||||
|
lxmf_message.fields[LXMF.FIELD_ICON_APPEARANCE] = [
|
||||||
|
lxmf_user_icon_name,
|
||||||
|
ColourUtils.hex_colour_to_byte_array(lxmf_user_icon_foreground_colour),
|
||||||
|
ColourUtils.hex_colour_to_byte_array(lxmf_user_icon_background_colour),
|
||||||
|
]
|
||||||
|
|
||||||
# register delivery callbacks
|
# register delivery callbacks
|
||||||
lxmf_message.register_delivery_callback(self.on_lxmf_sending_state_updated)
|
lxmf_message.register_delivery_callback(self.on_lxmf_sending_state_updated)
|
||||||
lxmf_message.register_failed_callback(self.on_lxmf_sending_failed)
|
lxmf_message.register_failed_callback(self.on_lxmf_sending_failed)
|
||||||
@@ -2846,6 +2878,9 @@ class Config:
|
|||||||
lxmf_preferred_propagation_node_auto_sync_interval_seconds = IntConfig("lxmf_preferred_propagation_node_auto_sync_interval_seconds", 0)
|
lxmf_preferred_propagation_node_auto_sync_interval_seconds = IntConfig("lxmf_preferred_propagation_node_auto_sync_interval_seconds", 0)
|
||||||
lxmf_preferred_propagation_node_last_synced_at = IntConfig("lxmf_preferred_propagation_node_last_synced_at", None)
|
lxmf_preferred_propagation_node_last_synced_at = IntConfig("lxmf_preferred_propagation_node_last_synced_at", None)
|
||||||
lxmf_local_propagation_node_enabled = BoolConfig("lxmf_local_propagation_node_enabled", False)
|
lxmf_local_propagation_node_enabled = BoolConfig("lxmf_local_propagation_node_enabled", False)
|
||||||
|
lxmf_user_icon_name = StringConfig("lxmf_user_icon_name", None)
|
||||||
|
lxmf_user_icon_foreground_colour = StringConfig("lxmf_user_icon_foreground_colour", None)
|
||||||
|
lxmf_user_icon_background_colour = StringConfig("lxmf_user_icon_background_colour", None)
|
||||||
|
|
||||||
# FIXME: we should probably set this as an instance variable of ReticulumMeshChat so it has a proper home, and pass it in to the constructor?
|
# FIXME: we should probably set this as an instance variable of ReticulumMeshChat so it has a proper home, and pass it in to the constructor?
|
||||||
nomadnet_cached_links = {}
|
nomadnet_cached_links = {}
|
||||||
|
|||||||
10
src/backend/colour_utils.py
Normal file
10
src/backend/colour_utils.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class ColourUtils:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hex_colour_to_byte_array(hex_colour):
|
||||||
|
|
||||||
|
# remove leading "#"
|
||||||
|
hex_colour = hex_colour.lstrip('#')
|
||||||
|
|
||||||
|
# convert the remaining hex string to bytes
|
||||||
|
return bytes.fromhex(hex_colour)
|
||||||
@@ -146,13 +146,16 @@
|
|||||||
<!-- my identity -->
|
<!-- my identity -->
|
||||||
<div v-if="config" class="bg-white border-t dark:border-zinc-900 dark:bg-zinc-950">
|
<div v-if="config" class="bg-white border-t dark:border-zinc-900 dark:bg-zinc-950">
|
||||||
<div @click="isShowingMyIdentitySection = !isShowingMyIdentitySection" class="flex text-gray-700 p-2 cursor-pointer">
|
<div @click="isShowingMyIdentitySection = !isShowingMyIdentitySection" class="flex text-gray-700 p-2 cursor-pointer">
|
||||||
<div class="my-auto mr-2 dark:text-white">
|
<div class="my-auto mr-2">
|
||||||
<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">
|
<RouterLink @click.stop :to="{ name: 'profile' }">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
<LxmfUserIcon
|
||||||
</svg>
|
:icon-name="config?.lxmf_user_icon_name"
|
||||||
|
:icon-foreground-colour="config?.lxmf_user_icon_foreground_colour"
|
||||||
|
:icon-background-colour="config?.lxmf_user_icon_background_colour"/>
|
||||||
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-auto dark:text-white">My Identity</div>
|
<div class="my-auto dark:text-white">My Identity</div>
|
||||||
<div class="ml-auto">
|
<div class="my-auto ml-auto">
|
||||||
<button @click.stop="saveIdentitySettings" type="button" class="my-auto inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500
|
<button @click.stop="saveIdentitySettings" type="button" class="my-auto inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500
|
||||||
dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500">
|
dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500">
|
||||||
Save
|
Save
|
||||||
@@ -311,10 +314,12 @@ import GlobalState from "../js/GlobalState";
|
|||||||
import Utils from "../js/Utils";
|
import Utils from "../js/Utils";
|
||||||
import GlobalEmitter from "../js/GlobalEmitter";
|
import GlobalEmitter from "../js/GlobalEmitter";
|
||||||
import NotificationUtils from "../js/NotificationUtils";
|
import NotificationUtils from "../js/NotificationUtils";
|
||||||
|
import LxmfUserIcon from "./LxmfUserIcon.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
|
LxmfUserIcon,
|
||||||
SidebarLink,
|
SidebarLink,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
23
src/frontend/components/LxmfUserIcon.vue
Normal file
23
src/frontend/components/LxmfUserIcon.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="iconName" class="p-2 rounded" :style="{ 'color': iconForegroundColour, 'background-color': iconBackgroundColour }">
|
||||||
|
<MaterialDesignIcon :icon-name="iconName" class="size-6"/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="bg-gray-200 dark:bg-zinc-700 text-gray-500 dark:text-gray-400 p-2 rounded">
|
||||||
|
<MaterialDesignIcon icon-name="account-outline" class="size-6"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MaterialDesignIcon from "./MaterialDesignIcon.vue";
|
||||||
|
export default {
|
||||||
|
name: "LxmfUserIcon",
|
||||||
|
components: {
|
||||||
|
MaterialDesignIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
iconName: String,
|
||||||
|
iconForegroundColour: String,
|
||||||
|
iconBackgroundColour: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
237
src/frontend/components/profile/ProfilePage.vue
Normal file
237
src/frontend/components/profile/ProfilePage.vue
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col flex-1 overflow-hidden min-w-full sm:min-w-[500px] dark:bg-zinc-950">
|
||||||
|
<div class="overflow-y-auto space-y-2 p-2">
|
||||||
|
|
||||||
|
<!-- info -->
|
||||||
|
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
||||||
|
<div class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold">Customise your Profile Icon</div>
|
||||||
|
<div class="text-gray-900 dark:text-gray-100">
|
||||||
|
<div class="text-sm p-2">
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
<li>Personalise your profile with a custom coloured icon.</li>
|
||||||
|
<li>This icon will be sent in all of your outgoing messages.</li>
|
||||||
|
<li>When you send someone a message, they will see your new icon.</li>
|
||||||
|
<li>You can <span @click="removeProfileIcon" class="cursor-pointer underline text-blue-500">remove your icon</span>, however it will still show for anyone that already received it.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- colours -->
|
||||||
|
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
||||||
|
<div class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold">Select your Colours</div>
|
||||||
|
<div class="divide-y divide-gray-300 dark:divide-zinc-700 text-gray-900 dark:text-gray-100">
|
||||||
|
|
||||||
|
<!-- icon colour -->
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">Icon Colour</div>
|
||||||
|
<div class="flex">
|
||||||
|
<select v-model="iconForegroundColour" class="bg-gray-50 dark:bg-zinc-700 border border-gray-300 dark:border-zinc-600 text-gray-900 dark:text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-600 dark:focus:border-blue-600 block w-full p-2.5">
|
||||||
|
<option value="#000000">Black</option>
|
||||||
|
<option value="#FFFFFF">White</option>
|
||||||
|
<option disabled></option>
|
||||||
|
<option value="#64748b">Slate</option>
|
||||||
|
<option value="#6b7280">Gray</option>
|
||||||
|
<option value="#71717a">Zinc</option>
|
||||||
|
<option value="#737373">Neutral</option>
|
||||||
|
<option value="#78716c">Stone</option>
|
||||||
|
<option disabled></option>
|
||||||
|
<option value="#ef4444">Red</option>
|
||||||
|
<option value="#f97316">Orange</option>
|
||||||
|
<option value="#f59e0b">Amber</option>
|
||||||
|
<option value="#eab308">Yellow</option>
|
||||||
|
<option value="#84cc16">Lime</option>
|
||||||
|
<option value="#22c55e">Green</option>
|
||||||
|
<option value="#10b981">Emerald</option>
|
||||||
|
<option value="#14b8a6">Teal</option>
|
||||||
|
<option value="#06b6d4">Cyan</option>
|
||||||
|
<option value="#0ea5e9">Sky</option>
|
||||||
|
<option value="#3b82f6">Blue</option>
|
||||||
|
<option value="#6366f1">Indigo</option>
|
||||||
|
<option value="#8b5cf6">Violet</option>
|
||||||
|
<option value="#a855f7">Purple</option>
|
||||||
|
<option value="#d946ef">Fuschia</option>
|
||||||
|
<option value="#ec4899">Pink</option>
|
||||||
|
<option value="#f43f5e">Rose</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- background colour -->
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">Background Colour</div>
|
||||||
|
<div class="flex">
|
||||||
|
<select v-model="iconBackgroundColour" class="bg-gray-50 dark:bg-zinc-700 border border-gray-300 dark:border-zinc-600 text-gray-900 dark:text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-600 dark:focus:border-blue-600 block w-full p-2.5">
|
||||||
|
<option value="#000000">Black</option>
|
||||||
|
<option value="#FFFFFF">White</option>
|
||||||
|
<option disabled></option>
|
||||||
|
<option value="#64748b">Slate</option>
|
||||||
|
<option value="#6b7280">Gray</option>
|
||||||
|
<option value="#71717a">Zinc</option>
|
||||||
|
<option value="#737373">Neutral</option>
|
||||||
|
<option value="#78716c">Stone</option>
|
||||||
|
<option disabled></option>
|
||||||
|
<option value="#ef4444">Red</option>
|
||||||
|
<option value="#f97316">Orange</option>
|
||||||
|
<option value="#f59e0b">Amber</option>
|
||||||
|
<option value="#eab308">Yellow</option>
|
||||||
|
<option value="#84cc16">Lime</option>
|
||||||
|
<option value="#22c55e">Green</option>
|
||||||
|
<option value="#10b981">Emerald</option>
|
||||||
|
<option value="#14b8a6">Teal</option>
|
||||||
|
<option value="#06b6d4">Cyan</option>
|
||||||
|
<option value="#0ea5e9">Sky</option>
|
||||||
|
<option value="#3b82f6">Blue</option>
|
||||||
|
<option value="#6366f1">Indigo</option>
|
||||||
|
<option value="#8b5cf6">Violet</option>
|
||||||
|
<option value="#a855f7">Purple</option>
|
||||||
|
<option value="#d946ef">Fuschia</option>
|
||||||
|
<option value="#ec4899">Pink</option>
|
||||||
|
<option value="#f43f5e">Rose</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- search icons -->
|
||||||
|
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
||||||
|
<div class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold">Select your Icon</div>
|
||||||
|
<div class="divide-y divide-gray-300 dark:divide-zinc-700 text-gray-900 dark:text-gray-100">
|
||||||
|
<div class="flex p-1">
|
||||||
|
<input v-model="search" type="text" :placeholder="`Search ${iconNames.length} icons...`" class="bg-gray-50 dark:bg-zinc-700 border border-gray-300 dark:border-zinc-600 text-gray-900 dark:text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-600 dark:focus:border-blue-600 block w-full p-2.5">
|
||||||
|
</div>
|
||||||
|
<div class="divide-y">
|
||||||
|
<div @click="onIconClick(mdiIconName)" v-for="mdiIconName of searchedIconNames" class="flex space-x-1 p-2 cursor-pointer hover:bg-gray-100">
|
||||||
|
<div class="my-auto">
|
||||||
|
<LxmfUserIcon :icon-name="mdiIconName" :icon-foreground-colour="iconForegroundColour" :icon-background-colour="iconBackgroundColour"/>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto">{{ mdiIconName }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="searchedIconNames.length === 0" class="p-1 text-sm text-gray-500">No icons match your search.</div>
|
||||||
|
<div v-if="searchedIconNames.length === maxSearchResults" class="p-1 text-sm text-gray-500">A maximum of {{ maxSearchResults }} icons are shown.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as mdi from "@mdi/js";
|
||||||
|
import MaterialDesignIcon from "../../../../build/exe/lib/src/frontend/components/MaterialDesignIcon.vue";
|
||||||
|
import LxmfUserIcon from "../LxmfUserIcon.vue";
|
||||||
|
import DialogUtils from "../../js/DialogUtils";
|
||||||
|
export default {
|
||||||
|
name: 'ProfilePage',
|
||||||
|
components: {
|
||||||
|
LxmfUserIcon,
|
||||||
|
MaterialDesignIcon,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
config: null,
|
||||||
|
iconForegroundColour: null,
|
||||||
|
iconBackgroundColour: null,
|
||||||
|
|
||||||
|
search: "",
|
||||||
|
maxSearchResults: 100,
|
||||||
|
iconNames: [],
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
this.getConfig();
|
||||||
|
|
||||||
|
// load icon names
|
||||||
|
this.iconNames = Object.keys(mdi).map((mdiIcon) => {
|
||||||
|
return mdiIcon
|
||||||
|
.replace(/^mdi/, '') // Remove the "mdi" prefix
|
||||||
|
.replace(/([a-z])([A-Z])/g, '$1-$2') // Add a hyphen between lowercase and uppercase letters
|
||||||
|
.toLowerCase(); // Convert the entire string to lowercase
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getConfig() {
|
||||||
|
try {
|
||||||
|
const response = await window.axios.get("/api/v1/config");
|
||||||
|
this.config = response.data.config;
|
||||||
|
} catch(e) {
|
||||||
|
// do nothing if failed to load config
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateConfig(config) {
|
||||||
|
try {
|
||||||
|
const response = await window.axios.patch("/api/v1/config", config);
|
||||||
|
this.config = response.data.config;
|
||||||
|
} catch(e) {
|
||||||
|
alert("Failed to save config!");
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onIconClick(iconName) {
|
||||||
|
|
||||||
|
// ensure foreground colour set
|
||||||
|
if(this.iconForegroundColour == null){
|
||||||
|
DialogUtils.alert("Please select an icon colour first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure background colour set
|
||||||
|
if(this.iconBackgroundColour == null){
|
||||||
|
DialogUtils.alert("Please select a background colour first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm user wants to update their icon
|
||||||
|
if(!confirm("Are you sure you want to set this as your profile icon?")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save icon appearance
|
||||||
|
await this.updateConfig({
|
||||||
|
"lxmf_user_icon_name": iconName,
|
||||||
|
"lxmf_user_icon_foreground_colour": this.iconForegroundColour,
|
||||||
|
"lxmf_user_icon_background_colour": this.iconBackgroundColour,
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
async removeProfileIcon() {
|
||||||
|
|
||||||
|
// confirm user wants to remove their icon
|
||||||
|
if(!confirm("Are you sure you want to remove your profile icon? Anyone that has already received it will continue to see it until you send them a new icon.")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove profile icon
|
||||||
|
await this.updateConfig({
|
||||||
|
"lxmf_user_icon_name": null,
|
||||||
|
"lxmf_user_icon_foreground_colour": null,
|
||||||
|
"lxmf_user_icon_background_colour": null,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
searchedIconNames() {
|
||||||
|
return this.iconNames.filter((iconName) => {
|
||||||
|
return iconName.includes(this.search);
|
||||||
|
}).slice(0, this.maxSearchResults);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
config() {
|
||||||
|
// update ui when config is updated
|
||||||
|
this.iconName = this.config.lxmf_user_icon_name;
|
||||||
|
this.iconForegroundColour = this.config.lxmf_user_icon_foreground_colour || "#000000";
|
||||||
|
this.iconBackgroundColour = this.config.lxmf_user_icon_background_colour || "#FFFFFF";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -65,6 +65,11 @@ const router = createRouter({
|
|||||||
path: '/ping',
|
path: '/ping',
|
||||||
component: defineAsyncComponent(() => import("./components/ping/PingPage.vue")),
|
component: defineAsyncComponent(() => import("./components/ping/PingPage.vue")),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "profile",
|
||||||
|
path: '/profile',
|
||||||
|
component: defineAsyncComponent(() => import("./components/profile/ProfilePage.vue")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "settings",
|
name: "settings",
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
|
|||||||
Reference in New Issue
Block a user