chore: Use resize observer

This commit is contained in:
iamsivin
2024-03-14 15:13:40 +05:30
parent 3cc1147fa8
commit 048e1d9bc4
2 changed files with 77 additions and 36 deletions
@@ -31,7 +31,10 @@
</span>
</div>
<div class="flex flex-row justify-between items-center w-full gap-2">
<div
ref="inboxCardInfo"
class="flex flex-row justify-between items-center w-full gap-2"
>
<div
v-if="snoozedUntilTime || hasLastSnoozed"
class="flex flex-row flex-1 gap-1 items-center"
@@ -53,11 +56,10 @@
:inbox="inbox"
:conversation-id="primaryActor.id"
:conversation-labels="primaryActor.labels"
:notification-type-width="notificationTypeElementWidth"
:parent-element-width="inboxCardInfoElementWidth"
/>
<div
v-show="notificationTypes"
ref="notificationTypes"
class="flex flex-row items-center gap-0.5"
>
<fluent-icon
@@ -105,6 +107,10 @@ import {
shortenSnoozeTime,
} from 'dashboard/helper/snoozeHelpers';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { debounce } from '@chatwoot/utils';
const OBSERVER_DEBOUNCE_TIME = 100;
export default {
components: {
PriorityIcon,
@@ -128,6 +134,8 @@ export default {
return {
isContextMenuOpen: false,
contextMenuPosition: { x: null, y: null },
resizeObserver: null,
inboxCardInfoElementWidth: 0,
};
},
computed: {
@@ -214,14 +222,30 @@ export default {
}
return '';
},
notificationTypeElementWidth() {
return this.$refs.notificationTypes?.clientWidth;
},
},
mounted() {
this.$nextTick(() => {
this.resizeObserver = new ResizeObserver(
debounce(this.resizeObserverCallback, OBSERVER_DEBOUNCE_TIME, false)
);
if (this.$refs.inboxCardInfo) {
this.resizeObserver.observe(this.$refs.inboxCardInfo);
}
});
},
unmounted() {
if (this.resizeObserver) {
this.resizeObserver.unobserve(this.$refs.inboxCardInfo);
this.resizeObserver.disconnect();
}
this.closeContextMenu();
},
methods: {
resizeObserverCallback(entries) {
entries.forEach(entry => {
this.inboxCardInfoElementWidth = entry.contentRect.width;
});
},
openConversation(notification) {
const {
id,
@@ -1,9 +1,9 @@
<template>
<div ref="container" class="w-auto bg-none">
<div class="w-auto bg-none">
<div
class="flex items-center w-full justify-start rounded-[4px] border border-slate-100 dark:border-slate-700/50 divide-x divide-slate-100 dark:divide-slate-700/50 bg-none"
>
<!-- Inbox icon and conversation ID -->
<!-- Inbox icon -->
<div
v-if="inbox"
ref="inboxIcon"
@@ -16,22 +16,21 @@
size="14"
/>
</div>
<!-- Conversation ID -->
<span
ref="conversationId"
class="flex items-center py-0.5 px-1.5 font-medium text-slate-600 dark:text-slate-200 text-xs"
>
{{ conversationId }}
</span>
<!-- Labels display logic -->
<div
ref="labelContainer"
class="flex flex-row w-full divide-x divide-slate-100 dark:divide-slate-700/50"
>
<div
v-for="(label, index) in activeLabels"
v-show="index < visibleLabels"
:key="label.id"
class="label flex items-center justify-start gap-1 w-full py-0.5 px-1.5"
class="flex items-center justify-start gap-1 w-full py-0.5 px-1.5"
>
<span
v-if="label.title"
@@ -44,8 +43,10 @@
{{ label.title }}
</span>
</div>
<!-- Label count display logic -->
<span
v-if="activeLabels.length > visibleLabels"
:title="hiddenLabelsTitle"
class="flex items-center py-0.5 px-1.5 font-medium text-slate-700 dark:text-slate-200 text-xs"
>
+{{ activeLabels.length - visibleLabels }}
@@ -59,16 +60,18 @@
import { mapGetters } from 'vuex';
import { getInboxClassByType } from 'dashboard/helper/inbox';
const MAX_LABELS = 5;
const MAX_LABELS = 6;
const MIN_LABELS = 1;
const BASE_WIDTH = 380;
const TOTAL_PADDING = 32;
const EFFECTIVE_WIDTH = BASE_WIDTH - TOTAL_PADDING;
// 24 is the padding + gap + label color width
const LABEL_ITEM_PADDING = 24;
const CHAR_WIDTH = 5.2;
const AVG_CHAR_WIDTH = 5.2;
// Clamp function to limit the number of labels to be displayed
const clamp = (value, min, max) => Math.min(Math.max(min, value), max);
export default {
@@ -85,6 +88,10 @@ export default {
type: Array,
default: () => [],
},
parentElementWidth: {
type: Number,
default: 0,
},
},
data() {
return {
@@ -98,25 +105,33 @@ export default {
return getInboxClassByType(type, phoneNumber);
},
activeLabels() {
return this.accountLabels.filter(({ title }) =>
this.conversationLabels.includes(title)
return this.accountLabels?.filter(({ title }) =>
this.conversationLabels?.includes(title)
);
},
hiddenLabelsTitle() {
if (this.activeLabels?.length <= this.visibleLabels) {
return '';
}
return (
this.activeLabels
.slice(this.visibleLabels)
.map(label => label.title)
.join(', ') || ''
);
},
},
mounted() {
this.$nextTick(() => {
this.updateLabelWidths();
});
watch: {
parentElementWidth() {
if (this.activeLabels.length > 1) {
this.updateLabelWidths();
}
},
},
methods: {
updateLabelWidths() {
this.$nextTick(() => {
const currentInfoEl = this.$refs.container;
// this is a hack, get the parent width as a prop, and on resize of the parent width
// update the prop value, that will cause a reactive change and the number of labels will fix
// itself
const parent = currentInfoEl.parentElement.parentElement;
const containerWidth = parent.clientWidth;
const containerWidth = this.parentElementWidth || EFFECTIVE_WIDTH;
if (containerWidth <= EFFECTIVE_WIDTH) {
this.visibleLabels = 1;
return;
@@ -124,17 +139,19 @@ export default {
let widthAvailable = containerWidth - EFFECTIVE_WIDTH;
// go through each label that is availble and calculate estimated width
let numberOfLabels = 0;
this.activeLabels.forEach(label => {
const numberOfChars = label.title.length;
const widthRequired = numberOfChars * CHAR_WIDTH + LABEL_ITEM_PADDING;
widthAvailable -= widthRequired;
if (widthAvailable > 0) {
numberOfLabels += 1;
}
});
// go through each label and calculate the width required
let numberOfLabels = this.activeLabels.reduce(
(acc, label) => {
const widthRequired =
label.title.length * AVG_CHAR_WIDTH + LABEL_ITEM_PADDING;
if (acc.widthAvailable >= widthRequired) {
acc.widthAvailable -= widthRequired;
acc.count += 1;
}
return acc;
},
{ count: 0, widthAvailable }
).count;
this.visibleLabels = clamp(numberOfLabels, MIN_LABELS, MAX_LABELS);
});