feat(profile): redesign ProfileIconPage with improved layout, color selection, and icon management features
This commit is contained in:
@@ -1,102 +1,201 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1 overflow-hidden min-w-0 dark:bg-zinc-950">
|
<div class="flex flex-col flex-1 overflow-hidden min-w-0 dark:bg-zinc-950">
|
||||||
<div class="overflow-y-auto space-y-2 p-2">
|
<div class="overflow-y-auto">
|
||||||
<!-- info -->
|
<div class="max-w-4xl mx-auto p-4 space-y-6">
|
||||||
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
<!-- Header with Preview -->
|
||||||
<div
|
<div
|
||||||
class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold"
|
class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800 overflow-hidden"
|
||||||
>
|
>
|
||||||
Customise your Profile Icon
|
<div class="p-6 border-b border-gray-200 dark:border-zinc-800">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Profile Icon Customizer</h2>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-zinc-400 mt-1">
|
||||||
|
Customize your profile icon that appears in all your messages
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:disabled="!hasChanges || isSaving"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg border transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
:class="
|
||||||
|
hasChanges && !isSaving
|
||||||
|
? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:border-blue-500 dark:hover:bg-blue-600'
|
||||||
|
: 'bg-gray-100 text-gray-700 border-gray-300 dark:bg-zinc-800 dark:text-zinc-300 dark:border-zinc-700'
|
||||||
|
"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
<MaterialDesignIcon
|
||||||
|
v-if="isSaving"
|
||||||
|
icon-name="refresh"
|
||||||
|
class="size-4 animate-spin"
|
||||||
|
/>
|
||||||
|
<MaterialDesignIcon v-else icon-name="content-save" class="size-4" />
|
||||||
|
{{ isSaving ? "Saving..." : "Save" }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:disabled="!hasChanges || isSaving"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 text-gray-700 dark:text-zinc-300 hover:bg-gray-50 dark:hover:bg-zinc-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
@click="resetChanges"
|
||||||
|
>
|
||||||
|
<MaterialDesignIcon icon-name="refresh" class="size-4" />
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex flex-col items-center justify-center space-y-4">
|
||||||
|
<div class="text-sm font-medium text-gray-700 dark:text-zinc-300">Preview</div>
|
||||||
|
<div class="p-8 bg-gray-50 dark:bg-zinc-800 rounded-2xl">
|
||||||
|
<LxmfUserIcon
|
||||||
|
:icon-name="iconName"
|
||||||
|
:icon-foreground-colour="iconForegroundColour"
|
||||||
|
:icon-background-colour="iconBackgroundColour"
|
||||||
|
icon-class="size-16"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 dark:text-zinc-400 text-center max-w-md">
|
||||||
|
This is how your icon will appear to others when you send messages
|
||||||
</div>
|
</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 class="cursor-pointer underline text-blue-500" @click="removeProfileIcon"
|
|
||||||
>remove your icon</span
|
|
||||||
>, however it will still show for anyone that already received it.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- colours -->
|
<!-- Color Selection -->
|
||||||
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
|
||||||
<div
|
<div
|
||||||
class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold"
|
class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800 overflow-hidden"
|
||||||
>
|
>
|
||||||
Select your Colours
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y divide-gray-300 dark:divide-zinc-700 text-gray-900 dark:text-gray-100">
|
<div class="p-4 space-y-4">
|
||||||
<!-- background colour -->
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div class="p-2 flex space-x-2">
|
<div class="flex-1">
|
||||||
<div class="flex my-auto">
|
<label class="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
|
||||||
|
Background Color
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
<ColourPickerDropdown v-model:colour="iconBackgroundColour" />
|
<ColourPickerDropdown v-model:colour="iconBackgroundColour" />
|
||||||
</div>
|
<div class="flex-1">
|
||||||
<div class="my-auto">
|
<input
|
||||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">Background Colour</div>
|
v-model="iconBackgroundColour"
|
||||||
<div class="text-sm text-gray-900 dark:text-gray-100">{{ iconBackgroundColour }}</div>
|
type="text"
|
||||||
|
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-800 text-gray-900 dark:text-zinc-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="#e5e7eb"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- icon colour -->
|
</div>
|
||||||
<div class="p-2 flex space-x-2">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div class="flex my-auto">
|
<div class="flex-1">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-zinc-300 mb-2">
|
||||||
|
Icon Color
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
<ColourPickerDropdown v-model:colour="iconForegroundColour" />
|
<ColourPickerDropdown v-model:colour="iconForegroundColour" />
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
v-model="iconForegroundColour"
|
||||||
|
type="text"
|
||||||
|
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-800 text-gray-900 dark:text-zinc-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="#6b7280"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-auto">
|
|
||||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">Icon Colour</div>
|
|
||||||
<div class="text-sm text-gray-900 dark:text-gray-100">{{ iconForegroundColour }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- search icons -->
|
<!-- Icon Selection -->
|
||||||
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
|
||||||
<div
|
<div
|
||||||
class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold"
|
class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800 overflow-hidden"
|
||||||
>
|
>
|
||||||
Select your Icon
|
<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">Icon</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y divide-gray-300 dark:divide-zinc-700 text-gray-900 dark:text-gray-100">
|
<div class="p-4 space-y-4">
|
||||||
<div class="flex p-1">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
v-model="search"
|
v-model="search"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="`Search ${iconNames.length} icons...`"
|
: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"
|
class="w-full px-4 py-3 text-sm border border-gray-300 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-800 text-gray-900 dark:text-zinc-100 placeholder-gray-400 dark:placeholder-zinc-500 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
<MaterialDesignIcon
|
||||||
|
icon-name="magnify"
|
||||||
|
class="absolute right-3 top-1/2 -translate-y-1/2 size-5 text-gray-400 dark:text-zinc-500 pointer-events-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y divide-gray-300 dark:divide-zinc-700">
|
<div
|
||||||
|
class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3 max-h-[500px] overflow-y-auto p-1"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="mdiIconName of searchedIconNames"
|
v-for="mdiIconName of searchedIconNames"
|
||||||
:key="mdiIconName"
|
:key="mdiIconName"
|
||||||
class="flex space-x-2 p-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-700"
|
class="flex flex-col items-center justify-center p-4 rounded-lg border-2 cursor-pointer transition-all hover:bg-gray-50 dark:hover:bg-zinc-800 hover:border-blue-500 dark:hover:border-blue-500"
|
||||||
|
:class="
|
||||||
|
iconName === mdiIconName
|
||||||
|
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||||||
|
: 'border-gray-200 dark:border-zinc-700'
|
||||||
|
"
|
||||||
@click="onIconClick(mdiIconName)"
|
@click="onIconClick(mdiIconName)"
|
||||||
>
|
>
|
||||||
<div class="my-auto">
|
|
||||||
<LxmfUserIcon
|
<LxmfUserIcon
|
||||||
:icon-name="mdiIconName"
|
:icon-name="mdiIconName"
|
||||||
:icon-foreground-colour="iconForegroundColour"
|
:icon-foreground-colour="iconForegroundColour"
|
||||||
:icon-background-colour="iconBackgroundColour"
|
:icon-background-colour="iconBackgroundColour"
|
||||||
|
icon-class="size-8"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
class="mt-2 text-xs text-center text-gray-600 dark:text-zinc-400 truncate w-full"
|
||||||
|
:title="mdiIconName"
|
||||||
|
>
|
||||||
|
{{ mdiIconName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="my-auto">{{ mdiIconName }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="searchedIconNames.length === 0" class="p-1 text-sm text-gray-500">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="searchedIconNames.length === 0"
|
||||||
|
class="text-center py-8 text-sm text-gray-500 dark:text-zinc-400"
|
||||||
|
>
|
||||||
No icons match your search.
|
No icons match your search.
|
||||||
</div>
|
</div>
|
||||||
<div v-if="searchedIconNames.length === maxSearchResults" class="p-1 text-sm text-gray-500">
|
<div
|
||||||
A maximum of {{ maxSearchResults }} icons are shown.
|
v-if="searchedIconNames.length === maxSearchResults"
|
||||||
|
class="text-center py-2 text-xs text-gray-500 dark:text-zinc-400"
|
||||||
|
>
|
||||||
|
Showing first {{ maxSearchResults }} results. Refine your search to see more.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Remove Icon Section -->
|
||||||
|
<div
|
||||||
|
class="bg-white dark:bg-zinc-900 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-800 overflow-hidden"
|
||||||
|
>
|
||||||
|
<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">Remove Icon</h3>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<p class="text-sm text-gray-600 dark:text-zinc-400 mb-4">
|
||||||
|
Remove your profile icon. Anyone who has already received it will continue to see it until
|
||||||
|
you send them a new icon.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg border border-red-300 dark:border-red-800 bg-white dark:bg-zinc-800 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
||||||
|
@click="removeProfileIcon"
|
||||||
|
>
|
||||||
|
<MaterialDesignIcon icon-name="delete-outline" class="size-4" />
|
||||||
|
Remove Icon
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,115 +204,196 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as mdi from "@mdi/js";
|
import * as mdi from "@mdi/js";
|
||||||
import LxmfUserIcon from "../LxmfUserIcon.vue";
|
import LxmfUserIcon from "../LxmfUserIcon.vue";
|
||||||
import DialogUtils from "../../js/DialogUtils";
|
|
||||||
import ToastUtils from "../../js/ToastUtils";
|
import ToastUtils from "../../js/ToastUtils";
|
||||||
import ColourPickerDropdown from "../ColourPickerDropdown.vue";
|
import ColourPickerDropdown from "../ColourPickerDropdown.vue";
|
||||||
|
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ProfileIconPage",
|
name: "ProfileIconPage",
|
||||||
components: {
|
components: {
|
||||||
ColourPickerDropdown,
|
ColourPickerDropdown,
|
||||||
LxmfUserIcon,
|
LxmfUserIcon,
|
||||||
|
MaterialDesignIcon,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: null,
|
config: null,
|
||||||
|
iconName: null,
|
||||||
iconForegroundColour: null,
|
iconForegroundColour: null,
|
||||||
iconBackgroundColour: null,
|
iconBackgroundColour: null,
|
||||||
|
|
||||||
|
originalIconName: null,
|
||||||
|
originalIconForegroundColour: null,
|
||||||
|
originalIconBackgroundColour: null,
|
||||||
|
|
||||||
search: "",
|
search: "",
|
||||||
maxSearchResults: 100,
|
maxSearchResults: 200,
|
||||||
iconNames: [],
|
iconNames: [],
|
||||||
|
|
||||||
|
isSaving: false,
|
||||||
|
autoSaveTimeout: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
searchedIconNames() {
|
searchedIconNames() {
|
||||||
|
const searchLower = this.search.toLowerCase();
|
||||||
return this.iconNames
|
return this.iconNames
|
||||||
.filter((iconName) => {
|
.filter((iconName) => {
|
||||||
return iconName.includes(this.search);
|
return iconName.toLowerCase().includes(searchLower);
|
||||||
})
|
})
|
||||||
.slice(0, this.maxSearchResults);
|
.slice(0, this.maxSearchResults);
|
||||||
},
|
},
|
||||||
|
hasChanges() {
|
||||||
|
return (
|
||||||
|
this.iconName !== this.originalIconName ||
|
||||||
|
this.iconForegroundColour !== this.originalIconForegroundColour ||
|
||||||
|
this.iconBackgroundColour !== this.originalIconBackgroundColour
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
config() {
|
config() {
|
||||||
// update ui when config is updated
|
if (this.config) {
|
||||||
this.iconName = this.config.lxmf_user_icon_name;
|
this.iconName = this.config.lxmf_user_icon_name || null;
|
||||||
this.iconForegroundColour = this.config.lxmf_user_icon_foreground_colour || "#6b7280";
|
this.iconForegroundColour = this.config.lxmf_user_icon_foreground_colour || "#6b7280";
|
||||||
this.iconBackgroundColour = this.config.lxmf_user_icon_background_colour || "#e5e7eb";
|
this.iconBackgroundColour = this.config.lxmf_user_icon_background_colour || "#e5e7eb";
|
||||||
|
|
||||||
|
this.saveOriginalValues();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
iconForegroundColour() {
|
||||||
|
this.debouncedAutoSave();
|
||||||
|
},
|
||||||
|
iconBackgroundColour() {
|
||||||
|
this.debouncedAutoSave();
|
||||||
|
},
|
||||||
|
iconName() {
|
||||||
|
this.debouncedAutoSave();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getConfig();
|
this.getConfig();
|
||||||
|
|
||||||
// load icon names
|
|
||||||
this.iconNames = Object.keys(mdi).map((mdiIcon) => {
|
this.iconNames = Object.keys(mdi).map((mdiIcon) => {
|
||||||
return mdiIcon
|
return mdiIcon
|
||||||
.replace(/^mdi/, "") // Remove the "mdi" prefix
|
.replace(/^mdi/, "")
|
||||||
.replace(/([a-z])([A-Z])/g, "$1-$2") // Add a hyphen between lowercase and uppercase letters
|
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||||
.toLowerCase(); // Convert the entire string to lowercase
|
.toLowerCase();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.autoSaveTimeout) {
|
||||||
|
clearTimeout(this.autoSaveTimeout);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
saveOriginalValues() {
|
||||||
|
this.originalIconName = this.iconName;
|
||||||
|
this.originalIconForegroundColour = this.iconForegroundColour;
|
||||||
|
this.originalIconBackgroundColour = this.iconBackgroundColour;
|
||||||
|
},
|
||||||
|
debouncedAutoSave() {
|
||||||
|
if (this.autoSaveTimeout) {
|
||||||
|
clearTimeout(this.autoSaveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoSaveTimeout = setTimeout(() => {
|
||||||
|
if (this.hasChanges && this.iconName && this.iconForegroundColour && this.iconBackgroundColour) {
|
||||||
|
this.saveChanges(true);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
async getConfig() {
|
async getConfig() {
|
||||||
try {
|
try {
|
||||||
const response = await window.axios.get("/api/v1/config");
|
const response = await window.axios.get("/api/v1/config");
|
||||||
this.config = response.data.config;
|
this.config = response.data.config;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing if failed to load config
|
ToastUtils.error("Failed to load configuration");
|
||||||
console.log(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async updateConfig(config) {
|
async updateConfig(config, silent = false) {
|
||||||
try {
|
try {
|
||||||
const response = await window.axios.patch("/api/v1/config", config);
|
const response = await window.axios.patch("/api/v1/config", config);
|
||||||
this.config = response.data.config;
|
this.config = response.data.config;
|
||||||
|
this.saveOriginalValues();
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
ToastUtils.success("Profile icon saved successfully");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastUtils.error("Failed to save config!");
|
if (!silent) {
|
||||||
console.log(e);
|
ToastUtils.error("Failed to save profile icon");
|
||||||
|
}
|
||||||
|
console.error(e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onIconClick(iconName) {
|
async saveChanges(silent = false) {
|
||||||
// ensure foreground colour set
|
if (!this.hasChanges) {
|
||||||
if (this.iconForegroundColour == null) {
|
|
||||||
DialogUtils.alert("Please select an icon colour first");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure background colour set
|
if (!this.iconForegroundColour || !this.iconBackgroundColour) {
|
||||||
if (this.iconBackgroundColour == null) {
|
ToastUtils.warning("Please select both background and icon colors");
|
||||||
DialogUtils.alert("Please select a background colour first");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm user wants to update their icon
|
if (!this.iconName) {
|
||||||
if (!(await DialogUtils.confirm("Are you sure you want to set this as your profile icon?"))) {
|
ToastUtils.warning("Please select an icon");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save icon appearance
|
this.isSaving = true;
|
||||||
await this.updateConfig({
|
|
||||||
lxmf_user_icon_name: iconName,
|
try {
|
||||||
|
const success = await this.updateConfig(
|
||||||
|
{
|
||||||
|
lxmf_user_icon_name: this.iconName,
|
||||||
lxmf_user_icon_foreground_colour: this.iconForegroundColour,
|
lxmf_user_icon_foreground_colour: this.iconForegroundColour,
|
||||||
lxmf_user_icon_background_colour: this.iconBackgroundColour,
|
lxmf_user_icon_background_colour: this.iconBackgroundColour,
|
||||||
});
|
|
||||||
},
|
},
|
||||||
async removeProfileIcon() {
|
silent
|
||||||
// confirm user wants to remove their icon
|
);
|
||||||
if (
|
|
||||||
!(await DialogUtils.confirm(
|
if (success && !silent) {
|
||||||
"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."
|
ToastUtils.success("Profile icon saved successfully");
|
||||||
))
|
}
|
||||||
) {
|
} finally {
|
||||||
|
this.isSaving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetChanges() {
|
||||||
|
if (!this.hasChanges) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove profile icon
|
this.iconName = this.originalIconName;
|
||||||
await this.updateConfig({
|
this.iconForegroundColour = this.originalIconForegroundColour;
|
||||||
|
this.iconBackgroundColour = this.originalIconBackgroundColour;
|
||||||
|
|
||||||
|
ToastUtils.info("Changes reset to saved values");
|
||||||
|
},
|
||||||
|
onIconClick(iconName) {
|
||||||
|
this.iconName = iconName;
|
||||||
|
},
|
||||||
|
async removeProfileIcon() {
|
||||||
|
this.isSaving = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await this.updateConfig({
|
||||||
lxmf_user_icon_name: null,
|
lxmf_user_icon_name: null,
|
||||||
lxmf_user_icon_foreground_colour: null,
|
lxmf_user_icon_foreground_colour: null,
|
||||||
lxmf_user_icon_background_colour: null,
|
lxmf_user_icon_background_colour: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
ToastUtils.success("Profile icon removed successfully");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.isSaving = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user