implement drop down menu for conversations

This commit is contained in:
liamcottle 2024-12-23 21:27:58 +13:00
commit 6f4084451b
5 changed files with 183 additions and 28 deletions

View file

@ -0,0 +1,92 @@
<template>
<div v-click-outside="{ handler: onClickOutsideMenu, capture: true }" class="cursor-default relative inline-block text-left">
<!-- menu button -->
<div ref="dropdown-button" @click.stop="toggleMenu">
<slot name="button"/>
</div>
<!-- drop down menu -->
<Transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95">
<div v-if="isShowingMenu" @click.stop="hideMenu" class="overflow-hidden absolute right-0 z-10 mr-4 w-56 rounded-md bg-white shadow-md border border-gray-200 focus:outline-none" :class="[ dropdownClass ]">
<slot name="items"/>
</div>
</Transition>
</div>
</template>
<script>
export default {
name: 'DropDownMenu',
data() {
return {
isShowingMenu: false,
dropdownClass: null,
};
},
methods: {
toggleMenu() {
if(this.isShowingMenu){
this.hideMenu();
} else {
this.showMenu();
}
},
showMenu() {
this.isShowingMenu = true;
this.adjustDropdownPosition();
},
hideMenu() {
this.isShowingMenu = false;
},
onClickOutsideMenu(event) {
if(this.isShowingMenu){
event.preventDefault();
this.hideMenu();
}
},
adjustDropdownPosition() {
this.$nextTick(() => {
// find button and dropdown
const button = this.$refs["dropdown-button"];
const dropdown = button.nextElementSibling;
// do nothing if not found
if(!button || !dropdown){
return;
}
// get bounding box of button and dropdown
const buttonRect = button.getBoundingClientRect();
const dropdownRect = dropdown.getBoundingClientRect();
// calculate how much space is under and above the button
const spaceBelowButton = window.innerHeight - buttonRect.bottom;
const spaceAboveButton = buttonRect.top;
// calculate if there is enough space available to show dropdown
const hasEnoughSpaceAboveButton = spaceAboveButton > dropdownRect.height;
const hasEnoughSpaceBelowButton = spaceBelowButton > dropdownRect.height;
// show dropdown above button
if(hasEnoughSpaceAboveButton && !hasEnoughSpaceBelowButton){
this.dropdownClass = "bottom-0 mb-12";
return;
}
// otherwise fallback to showing dropdown below button
this.dropdownClass = "top-0 mt-12";
});
},
},
}
</script>

View file

@ -0,0 +1,11 @@
<template>
<div class="cursor-pointer flex p-3 space-x-2 text-sm text-gray-500 hover:bg-gray-100">
<slot/>
</div>
</template>
<script>
export default {
name: 'DropDownMenuItem',
}
</script>

View file

@ -0,0 +1,11 @@
<template>
<button type="button" class="p-2 rounded-full text-gray-700 bg-gray-100 hover:bg-gray-200">
<slot/>
</button>
</template>
<script>
export default {
name: 'IconButton',
}
</script>

View file

@ -0,0 +1,65 @@
<template>
<DropDownMenu>
<template v-slot:button>
<IconButton>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 12.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 18.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z" />
</svg>
</IconButton>
</template>
<template v-slot:items>
<!-- delete message history button -->
<DropDownMenuItem @click="onDeleteMessageHistory">
<svg class="size-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022.841 10.518A2.75 2.75 0 0 0 7.596 19h4.807a2.75 2.75 0 0 0 2.742-2.53l.841-10.52.149.023a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 0-1.5.06l.3 7.5a.75.75 0 1 0 1.5-.06l-.3-7.5Zm4.34.06a.75.75 0 1 0-1.5-.06l-.3 7.5a.75.75 0 1 0 1.5.06l.3-7.5Z" clip-rule="evenodd" />
</svg>
<span class="text-red-500">Delete Message History</span>
</DropDownMenuItem>
</template>
</DropDownMenu>
</template>
<script>
import DropDownMenu from "../DropDownMenu.vue";
import DropDownMenuItem from "../DropDownMenuItem.vue";
import IconButton from "../IconButton.vue";
import DialogUtils from "../../js/DialogUtils";
export default {
name: 'ConversationDropDownMenu',
components: {
IconButton,
DropDownMenuItem,
DropDownMenu,
},
props: {
peer: Object,
},
emits: [
"conversation-deleted",
],
methods: {
async onDeleteMessageHistory() {
// ask user to confirm deleting conversation history
if(!confirm("Are you sure you want to delete all messages in this conversation? This can not be undone!")){
return;
}
// delete all lxmf messages from "us to destination" and from "destination to us"
try {
await window.axios.delete(`/api/v1/lxmf-messages/conversation/${this.peer.destination_hash}`);
} catch(e) {
DialogUtils.alert("failed to delete conversation");
console.log(e);
}
// fire callback
this.$emit("conversation-deleted");
},
},
}
</script>

View file

@ -76,15 +76,7 @@
<!-- delete button -->
<div class="my-auto mr-2">
<div @click="deleteConversation" class="cursor-pointer">
<div class="flex text-gray-700 bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
<div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</div>
</div>
</div>
<ConversationDropDownMenu v-if="selectedPeer" :peer="selectedPeer" @conversation-deleted="onConversationDeleted"/>
</div>
<!-- close button -->
@ -414,10 +406,12 @@ import AddAudioButton from "./AddAudioButton.vue";
import moment from "moment";
import SendMessageButton from "./SendMessageButton.vue";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import ConversationDropDownMenu from "./ConversationDropDownMenu.vue";
export default {
name: 'ConversationViewer',
components: {
ConversationDropDownMenu,
MaterialDesignIcon,
SendMessageButton,
AddAudioButton,
@ -844,25 +838,7 @@ export default {
}
},
async deleteConversation() {
// do nothing if no peer selected
if(!this.selectedPeer){
return;
}
// ask user to confirm deleting conversation history
if(!confirm("Are you sure you want to delete all messages from this conversation? This can not be undone!")){
return;
}
// delete all lxmf messages from "us to destination" and from "destination to us"
try {
await window.axios.delete(`/api/v1/lxmf-messages/conversation/${this.selectedPeer.destination_hash}`);
} catch(e) {
DialogUtils.alert("failed to delete conversation");
console.log(e);
}
async onConversationDeleted() {
// reload conversation
await this.initialLoad();