start implementing nomadnet node page browser

This commit is contained in:
liamcottle
2024-05-06 00:43:25 +12:00
parent d95f4270ab
commit 8da806fb7f

View File

@@ -209,34 +209,256 @@
<!-- chat view --> <!-- chat view -->
<div class="flex flex-col flex-1 py-2"> <div class="flex flex-col flex-1 py-2">
<!-- peer selected --> <!-- peer -->
<div v-if="selectedPeer" class="flex flex-col h-full border rounded-xl bg-white shadow"> <template v-if="!selectedNode">
<!-- header --> <!-- peer selected -->
<div class="flex p-2 border-b border-gray-300"> <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> </div>
<!-- delete button --> <!-- chat items -->
<div class="ml-auto my-auto mr-2"> <div id="messages" class="h-full overflow-y-scroll px-3 sm:px-0">
<div @click="deleteConversation" href="javascript:void(0)" class="cursor-pointer"> <div v-if="selectedPeerChatItems.length > 0" class="flex flex-col space-y-3 p-3">
<div class="flex text-gray-700 bg-gray-100 hover:bg-gray-200 p-2 rounded-full"> <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 }">
<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"> <!-- sender name -->
<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" /> <div v-if="!chatItem.is_outbound" class="text-xs text-gray-500 ml-2">{{ selectedPeer.name }}</div>
</svg>
<!-- 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> </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> </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 --> <!-- close button -->
<div class="my-auto mr-2"> <div class="my-auto ml-auto mr-2">
<div @click="selectedPeer = null" href="javascript:void(0)" class="cursor-pointer"> <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 class="flex text-gray-700 bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
<div> <div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <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> </div>
<!-- chat items --> <!-- browser navigation -->
<div id="messages" class="h-full overflow-y-scroll px-3 sm:px-0"> <div class="w-full border-gray-300 border-b p-2">
<div v-if="selectedPeerChatItems.length > 0" class="flex flex-col space-y-3 p-3"> NAV BAR
<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 }"> </div>
<!-- sender name --> <!-- page content -->
<div v-if="!chatItem.is_outbound" class="text-xs text-gray-500 ml-2">{{ selectedPeer.name }}</div> <div class="h-full overflow-y-scroll px-3 sm:px-0">
<div class="flex flex-col space-y-3 p-3">
<!-- message content --> {{ nodePageContent }}
<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>
</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>
</div> </div>
@@ -478,6 +526,8 @@
lxmfMessagesRequestSequence: 0, lxmfMessagesRequestSequence: 0,
chatItems: [], chatItems: [],
nodePageContent: null,
nomadnetPageDownloadCallbacks: {}, nomadnetPageDownloadCallbacks: {},
nomadnetFileDownloadCallbacks: {}, nomadnetFileDownloadCallbacks: {},
@@ -780,7 +830,7 @@
"display_name": this.displayName, "display_name": this.displayName,
}); });
}, },
async downloadNomadNetFile(destinationHash, filePath, onSuccessCallback, onFailureCallback, onProgressCallback) { downloadNomadNetFile(destinationHash, filePath, onSuccessCallback, onFailureCallback, onProgressCallback) {
// do nothing if not connected to websocket // do nothing if not connected to websocket
if(!this.isWebsocketConnected){ 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 // do nothing if not connected to websocket
if(!this.isWebsocketConnected){ if(!this.isWebsocketConnected){
@@ -948,6 +998,19 @@
// do nothing if failed to load messages // 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) { async deleteChatItem(chatItem) {
try { try {
@@ -1012,6 +1075,7 @@
onNodeClick: function(node) { onNodeClick: function(node) {
this.selectedPeer = null; this.selectedPeer = null;
this.selectedNode = node; this.selectedNode = node;
this.loadNodeIndexPage(node);
}, },
parseSeconds: function(secondsToFormat) { parseSeconds: function(secondsToFormat) {
secondsToFormat = Number(secondsToFormat); secondsToFormat = Number(secondsToFormat);