start implementing nomadnet node page browser
This commit is contained in:
@@ -209,34 +209,256 @@
|
||||
<!-- chat view -->
|
||||
<div class="flex flex-col flex-1 py-2">
|
||||
|
||||
<!-- peer selected -->
|
||||
<div v-if="selectedPeer" class="flex flex-col h-full border rounded-xl bg-white shadow">
|
||||
<!-- peer -->
|
||||
<template v-if="!selectedNode">
|
||||
|
||||
<!-- header -->
|
||||
<div class="flex p-2 border-b border-gray-300">
|
||||
<!-- peer selected -->
|
||||
<div v-if="selectedPeer" class="flex flex-col h-full border rounded-xl bg-white shadow">
|
||||
|
||||
<!-- header -->
|
||||
<div class="flex p-2 border-b border-gray-300">
|
||||
|
||||
<!-- peer info -->
|
||||
<div>
|
||||
<div class="font-semibold">{{ selectedPeer.name }}</div>
|
||||
<div class="text-sm">@<{{ selectedPeer.destination_hash }}></div>
|
||||
</div>
|
||||
|
||||
<!-- delete button -->
|
||||
<div class="ml-auto my-auto mr-2">
|
||||
<div @click="deleteConversation" href="javascript:void(0)" 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>
|
||||
</div>
|
||||
|
||||
<!-- close button -->
|
||||
<div class="my-auto mr-2">
|
||||
<div @click="selectedPeer = null" href="javascript:void(0)" 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" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- peer info -->
|
||||
<div>
|
||||
<div class="font-semibold">{{ selectedPeer.name }}</div>
|
||||
<div class="text-sm">@<{{ selectedPeer.destination_hash }}></div>
|
||||
</div>
|
||||
|
||||
<!-- delete button -->
|
||||
<div class="ml-auto my-auto mr-2">
|
||||
<div @click="deleteConversation" href="javascript:void(0)" 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>
|
||||
<!-- chat items -->
|
||||
<div id="messages" class="h-full overflow-y-scroll px-3 sm:px-0">
|
||||
<div v-if="selectedPeerChatItems.length > 0" class="flex flex-col space-y-3 p-3">
|
||||
<div v-for="chatItem of selectedPeerChatItems" class="flex flex-col max-w-xl" :class="{ 'ml-auto pl-4 md:pl-16 items-end': chatItem.is_outbound, 'mr-auto pr-4 md:pr-16 items-start': !chatItem.is_outbound }">
|
||||
|
||||
<!-- sender name -->
|
||||
<div v-if="!chatItem.is_outbound" class="text-xs text-gray-500 ml-2">{{ selectedPeer.name }}</div>
|
||||
|
||||
<!-- message content -->
|
||||
<div @click="onChatItemClick(chatItem)" class="border border-gray-300 rounded-xl shadow overflow-hidden" :class="[ chatItem.lxmf_message.state === 'failed' ? 'bg-red-500 text-white' : chatItem.is_outbound ? 'bg-[#3b82f6] text-white' : 'bg-[#efefef]' ]">
|
||||
|
||||
<div class="w-full space-y-0.5 px-2.5 py-1">
|
||||
|
||||
<!-- content -->
|
||||
<div v-if="chatItem.lxmf_message.content" style="white-space:pre-wrap;word-wrap:break-word;font-family:inherit;">{{ chatItem.lxmf_message.content }}</div>
|
||||
|
||||
<!-- image field -->
|
||||
<div v-if="chatItem.lxmf_message.fields?.image">
|
||||
<img @click="openImage(`data:image/${chatItem.lxmf_message.fields.image.image_type};base64,${chatItem.lxmf_message.fields.image.image_bytes}`)" :src="`data:image/${chatItem.lxmf_message.fields.image.image_type};base64,${chatItem.lxmf_message.fields.image.image_bytes}`" class="w-full rounded-md cursor-pointer"/>
|
||||
</div>
|
||||
|
||||
<!-- file attachment fields -->
|
||||
<div v-if="chatItem.lxmf_message.fields?.file_attachments" class="space-y-1">
|
||||
<a target="_blank" :download="file_attachment.file_name" :href="`data:application/octet-stream;base64,${file_attachment.file_bytes}`" v-for="file_attachment of chatItem.lxmf_message.fields?.file_attachments ?? []" class="flex border border-gray-300 hover:bg-gray-100 rounded px-2 py-1 text-sm text-gray-700 font-semibold cursor-pointer space-x-2 bg-[#efefef]">
|
||||
<div class="my-auto">
|
||||
<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="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="my-auto w-full">{{ file_attachment.file_name }}</div>
|
||||
<div class="my-auto">
|
||||
<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="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- actions -->
|
||||
<div v-if="chatItem.is_actions_expanded" class="border-t p-1 bg-[#efefef] text-white">
|
||||
|
||||
<!-- delete message -->
|
||||
<button @click.stop="deleteChatItem(chatItem)" type="button" class="inline-flex items-center gap-x-1 rounded-md bg-red-500 px-2 py-1 text-xs font-semibold text-white shadow-sm hover:bg-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- message state -->
|
||||
<div v-if="chatItem.is_outbound" class="flex text-right" :class="[ chatItem.lxmf_message.state === 'failed' ? 'text-red-500' : 'text-gray-500' ]">
|
||||
<div class="flex ml-auto space-x-1">
|
||||
|
||||
<!-- state label -->
|
||||
<div class="my-auto space-x-1">
|
||||
<span>{{ chatItem.lxmf_message.state }}</span>
|
||||
<span v-if="chatItem.lxmf_message.state === 'sending'">{{ chatItem.lxmf_message.progress.toFixed(0) }}%</span>
|
||||
</div>
|
||||
|
||||
<!-- delivered icon -->
|
||||
<div v-if="chatItem.lxmf_message.state === 'delivered'" class="my-auto">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- failed icon -->
|
||||
<div v-else-if="chatItem.lxmf_message.state === 'failed'" class="my-auto">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- fallback icon -->
|
||||
<div v-else class="my-auto">
|
||||
<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="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- send message -->
|
||||
<div class="w-full border-gray-300 border-t p-2">
|
||||
<div class=mx-auto">
|
||||
|
||||
<!-- message composer -->
|
||||
<div>
|
||||
|
||||
<!-- image attachment -->
|
||||
<div v-if="newMessageImage" class="mb-2">
|
||||
<div class="w-32 h-32 rounded shadow border relative overflow-hidden">
|
||||
|
||||
<!-- image preview -->
|
||||
<img v-if="newMessageImageUrl" :src="newMessageImageUrl" class="w-full h-full"/>
|
||||
|
||||
<!-- remove button (top right) -->
|
||||
<div class="absolute top-0 right-0 p-1">
|
||||
<div @click="removeImageAttachment" href="javascript:void(0)" class="cursor-pointer">
|
||||
<div class="flex text-gray-700 bg-gray-100 hover:bg-gray-200 p-1 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
|
||||
<path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- image size (bottom left) -->
|
||||
<div class="absolute bottom-0 left-0 p-1">
|
||||
<div class="bg-gray-100 rounded border text-sm px-1">{{ formatBytes(newMessageImage.size) }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- file attachments -->
|
||||
<div v-if="newMessageFiles.length > 0" class="mb-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<div v-for="file in newMessageFiles" class="flex border border-gray-300 rounded text-gray-700 divide-x divide-gray-300 overflow-hidden">
|
||||
<div class="my-auto px-1">
|
||||
<span class="mr-1">{{ file.name }}</span>
|
||||
<span class="my-auto text-sm text-gray-500">{{ formatBytes(file.size) }}</span>
|
||||
</div>
|
||||
<div @click="removeFileAttachment(file)" class="flex my-auto text-sm text-gray-500 h-full px-1 hover:bg-gray-200 cursor-pointer">
|
||||
<svg class="w-5 h-5 my-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- text input -->
|
||||
<textarea id="message-input" :readonly="isSendingMessage" v-model="newMessageText" @keydown.enter.exact.native.prevent="onEnterPressed" @keydown.enter.shift.exact.native.prevent="onShiftEnterPressed" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" rows="3" placeholder="Send a message..."></textarea>
|
||||
|
||||
<!-- action button -->
|
||||
<div class="flex mt-2">
|
||||
|
||||
<!-- add files -->
|
||||
<button @click="addFilesToMessage" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2.5 py-1.5 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">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM12.75 12a.75.75 0 0 0-1.5 0v2.25H9a.75.75 0 0 0 0 1.5h2.25V18a.75.75 0 0 0 1.5 0v-2.25H15a.75.75 0 0 0 0-1.5h-2.25V12Z" clip-rule="evenodd" />
|
||||
<path d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z" />
|
||||
</svg>
|
||||
<span class="ml-1">Add Files</span>
|
||||
</button>
|
||||
|
||||
<!-- add image -->
|
||||
<button @click="addImageToMessage" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2.5 py-1.5 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">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="ml-1">Add Image</span>
|
||||
</button>
|
||||
|
||||
<!-- send message -->
|
||||
<button @click="sendMessage" :disabled="!canSendMessage" type="button" class="ml-auto my-auto inline-flex items-center gap-x-1 rounded-md px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" :class="[ canSendMessage ? 'bg-blue-500 hover:bg-blue-400 focus-visible:outline-blue-500' : 'bg-gray-400 focus-visible:outline-gray-500 cursor-not-allowed']">
|
||||
Send
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- no peer selected -->
|
||||
<div v-else class="flex flex-col mx-auto my-auto text-center leading-5">
|
||||
<div class="mx-auto mb-1">
|
||||
<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="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h3.218a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="font-semibold">No Active Chat</div>
|
||||
<div>Select a Peer to start chatting!</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- node -->
|
||||
<div v-if="selectedNode" class="flex flex-col h-full border rounded-xl bg-white shadow">
|
||||
|
||||
<!-- header -->
|
||||
<!-- todo: peer icon -->
|
||||
<!-- todo: node icon -->
|
||||
<div class="flex p-2 border-b border-gray-300">
|
||||
|
||||
<!-- node info -->
|
||||
<div>
|
||||
<div class="font-semibold">{{ selectedNode.name }}</div>
|
||||
<div class="text-sm">@<{{ selectedNode.destination_hash }}></div>
|
||||
</div>
|
||||
|
||||
<!-- close button -->
|
||||
<div class="my-auto mr-2">
|
||||
<div @click="selectedPeer = null" href="javascript:void(0)" class="cursor-pointer">
|
||||
<div class="my-auto ml-auto mr-2">
|
||||
<div @click="selectedNode = null" href="javascript:void(0)" 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" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
@@ -249,192 +471,18 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- chat items -->
|
||||
<div id="messages" class="h-full overflow-y-scroll px-3 sm:px-0">
|
||||
<div v-if="selectedPeerChatItems.length > 0" class="flex flex-col space-y-3 p-3">
|
||||
<div v-for="chatItem of selectedPeerChatItems" class="flex flex-col max-w-xl" :class="{ 'ml-auto pl-4 md:pl-16 items-end': chatItem.is_outbound, 'mr-auto pr-4 md:pr-16 items-start': !chatItem.is_outbound }">
|
||||
<!-- browser navigation -->
|
||||
<div class="w-full border-gray-300 border-b p-2">
|
||||
NAV BAR
|
||||
</div>
|
||||
|
||||
<!-- sender name -->
|
||||
<div v-if="!chatItem.is_outbound" class="text-xs text-gray-500 ml-2">{{ selectedPeer.name }}</div>
|
||||
|
||||
<!-- message content -->
|
||||
<div @click="onChatItemClick(chatItem)" class="border border-gray-300 rounded-xl shadow overflow-hidden" :class="[ chatItem.lxmf_message.state === 'failed' ? 'bg-red-500 text-white' : chatItem.is_outbound ? 'bg-[#3b82f6] text-white' : 'bg-[#efefef]' ]">
|
||||
|
||||
<div class="w-full space-y-0.5 px-2.5 py-1">
|
||||
|
||||
<!-- content -->
|
||||
<div v-if="chatItem.lxmf_message.content" style="white-space:pre-wrap;word-wrap:break-word;font-family:inherit;">{{ chatItem.lxmf_message.content }}</div>
|
||||
|
||||
<!-- image field -->
|
||||
<div v-if="chatItem.lxmf_message.fields?.image">
|
||||
<img @click="openImage(`data:image/${chatItem.lxmf_message.fields.image.image_type};base64,${chatItem.lxmf_message.fields.image.image_bytes}`)" :src="`data:image/${chatItem.lxmf_message.fields.image.image_type};base64,${chatItem.lxmf_message.fields.image.image_bytes}`" class="w-full rounded-md cursor-pointer"/>
|
||||
</div>
|
||||
|
||||
<!-- file attachment fields -->
|
||||
<div v-if="chatItem.lxmf_message.fields?.file_attachments" class="space-y-1">
|
||||
<a target="_blank" :download="file_attachment.file_name" :href="`data:application/octet-stream;base64,${file_attachment.file_bytes}`" v-for="file_attachment of chatItem.lxmf_message.fields?.file_attachments ?? []" class="flex border border-gray-300 hover:bg-gray-100 rounded px-2 py-1 text-sm text-gray-700 font-semibold cursor-pointer space-x-2 bg-[#efefef]">
|
||||
<div class="my-auto">
|
||||
<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="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="my-auto w-full">{{ file_attachment.file_name }}</div>
|
||||
<div class="my-auto">
|
||||
<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="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- actions -->
|
||||
<div v-if="chatItem.is_actions_expanded" class="border-t p-1 bg-[#efefef] text-white">
|
||||
|
||||
<!-- delete message -->
|
||||
<button @click.stop="deleteChatItem(chatItem)" type="button" class="inline-flex items-center gap-x-1 rounded-md bg-red-500 px-2 py-1 text-xs font-semibold text-white shadow-sm hover:bg-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- message state -->
|
||||
<div v-if="chatItem.is_outbound" class="flex text-right" :class="[ chatItem.lxmf_message.state === 'failed' ? 'text-red-500' : 'text-gray-500' ]">
|
||||
<div class="flex ml-auto space-x-1">
|
||||
|
||||
<!-- state label -->
|
||||
<div class="my-auto space-x-1">
|
||||
<span>{{ chatItem.lxmf_message.state }}</span>
|
||||
<span v-if="chatItem.lxmf_message.state === 'sending'">{{ chatItem.lxmf_message.progress.toFixed(0) }}%</span>
|
||||
</div>
|
||||
|
||||
<!-- delivered icon -->
|
||||
<div v-if="chatItem.lxmf_message.state === 'delivered'" class="my-auto">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- failed icon -->
|
||||
<div v-else-if="chatItem.lxmf_message.state === 'failed'" class="my-auto">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- fallback icon -->
|
||||
<div v-else class="my-auto">
|
||||
<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="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- page content -->
|
||||
<div class="h-full overflow-y-scroll px-3 sm:px-0">
|
||||
<div class="flex flex-col space-y-3 p-3">
|
||||
{{ nodePageContent }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- send message -->
|
||||
<div class="w-full border-gray-300 border-t p-2">
|
||||
<div class=mx-auto">
|
||||
|
||||
<!-- message composer -->
|
||||
<div>
|
||||
|
||||
<!-- image attachment -->
|
||||
<div v-if="newMessageImage" class="mb-2">
|
||||
<div class="w-32 h-32 rounded shadow border relative overflow-hidden">
|
||||
|
||||
<!-- image preview -->
|
||||
<img v-if="newMessageImageUrl" :src="newMessageImageUrl" class="w-full h-full"/>
|
||||
|
||||
<!-- remove button (top right) -->
|
||||
<div class="absolute top-0 right-0 p-1">
|
||||
<div @click="removeImageAttachment" href="javascript:void(0)" class="cursor-pointer">
|
||||
<div class="flex text-gray-700 bg-gray-100 hover:bg-gray-200 p-1 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
|
||||
<path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- image size (bottom left) -->
|
||||
<div class="absolute bottom-0 left-0 p-1">
|
||||
<div class="bg-gray-100 rounded border text-sm px-1">{{ formatBytes(newMessageImage.size) }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- file attachments -->
|
||||
<div v-if="newMessageFiles.length > 0" class="mb-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<div v-for="file in newMessageFiles" class="flex border border-gray-300 rounded text-gray-700 divide-x divide-gray-300 overflow-hidden">
|
||||
<div class="my-auto px-1">
|
||||
<span class="mr-1">{{ file.name }}</span>
|
||||
<span class="my-auto text-sm text-gray-500">{{ formatBytes(file.size) }}</span>
|
||||
</div>
|
||||
<div @click="removeFileAttachment(file)" class="flex my-auto text-sm text-gray-500 h-full px-1 hover:bg-gray-200 cursor-pointer">
|
||||
<svg class="w-5 h-5 my-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- text input -->
|
||||
<textarea id="message-input" :readonly="isSendingMessage" v-model="newMessageText" @keydown.enter.exact.native.prevent="onEnterPressed" @keydown.enter.shift.exact.native.prevent="onShiftEnterPressed" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" rows="3" placeholder="Send a message..."></textarea>
|
||||
|
||||
<!-- action button -->
|
||||
<div class="flex mt-2">
|
||||
|
||||
<!-- add files -->
|
||||
<button @click="addFilesToMessage" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2.5 py-1.5 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">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM12.75 12a.75.75 0 0 0-1.5 0v2.25H9a.75.75 0 0 0 0 1.5h2.25V18a.75.75 0 0 0 1.5 0v-2.25H15a.75.75 0 0 0 0-1.5h-2.25V12Z" clip-rule="evenodd" />
|
||||
<path d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z" />
|
||||
</svg>
|
||||
<span class="ml-1">Add Files</span>
|
||||
</button>
|
||||
|
||||
<!-- add image -->
|
||||
<button @click="addImageToMessage" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2.5 py-1.5 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">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="ml-1">Add Image</span>
|
||||
</button>
|
||||
|
||||
<!-- send message -->
|
||||
<button @click="sendMessage" :disabled="!canSendMessage" type="button" class="ml-auto my-auto inline-flex items-center gap-x-1 rounded-md px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" :class="[ canSendMessage ? 'bg-blue-500 hover:bg-blue-400 focus-visible:outline-blue-500' : 'bg-gray-400 focus-visible:outline-gray-500 cursor-not-allowed']">
|
||||
Send
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- no peer selected -->
|
||||
<div v-else class="flex flex-col mx-auto my-auto text-center leading-5">
|
||||
<div class="mx-auto mb-1">
|
||||
<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="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h3.218a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="font-semibold">No Active Chat</div>
|
||||
<div>Select a Peer to start chatting!</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -478,6 +526,8 @@
|
||||
lxmfMessagesRequestSequence: 0,
|
||||
chatItems: [],
|
||||
|
||||
nodePageContent: null,
|
||||
|
||||
nomadnetPageDownloadCallbacks: {},
|
||||
nomadnetFileDownloadCallbacks: {},
|
||||
|
||||
@@ -780,7 +830,7 @@
|
||||
"display_name": this.displayName,
|
||||
});
|
||||
},
|
||||
async downloadNomadNetFile(destinationHash, filePath, onSuccessCallback, onFailureCallback, onProgressCallback) {
|
||||
downloadNomadNetFile(destinationHash, filePath, onSuccessCallback, onFailureCallback, onProgressCallback) {
|
||||
|
||||
// do nothing if not connected to websocket
|
||||
if(!this.isWebsocketConnected){
|
||||
@@ -811,7 +861,7 @@
|
||||
}
|
||||
|
||||
},
|
||||
async downloadNomadNetPage(destinationHash, pagePath, onSuccessCallback, onFailureCallback, onProgressCallback) {
|
||||
downloadNomadNetPage(destinationHash, pagePath, onSuccessCallback, onFailureCallback, onProgressCallback) {
|
||||
|
||||
// do nothing if not connected to websocket
|
||||
if(!this.isWebsocketConnected){
|
||||
@@ -948,6 +998,19 @@
|
||||
// do nothing if failed to load messages
|
||||
}
|
||||
},
|
||||
async loadNodeIndexPage(node) {
|
||||
this.nodePageContent = "Requesting page: /page/index.mu";
|
||||
this.downloadNomadNetPage(node.destination_hash, "/page/index.mu", (pageContent) => {
|
||||
console.log("pageContent", pageContent);
|
||||
this.nodePageContent = pageContent;
|
||||
}, (failureReason) => {
|
||||
console.log("failureReason", failureReason);
|
||||
this.nodePageContent = `Failed loading page: ${failureReason}`;
|
||||
}, (progress) => {
|
||||
console.log("progress", progress);
|
||||
this.nodePageContent = `Loading page: ${progress}`;
|
||||
});
|
||||
},
|
||||
async deleteChatItem(chatItem) {
|
||||
try {
|
||||
|
||||
@@ -1012,6 +1075,7 @@
|
||||
onNodeClick: function(node) {
|
||||
this.selectedPeer = null;
|
||||
this.selectedNode = node;
|
||||
this.loadNodeIndexPage(node);
|
||||
},
|
||||
parseSeconds: function(secondsToFormat) {
|
||||
secondsToFormat = Number(secondsToFormat);
|
||||
|
||||
Reference in New Issue
Block a user