chat ui improvements

This commit is contained in:
liamcottle
2024-04-29 22:06:06 +12:00
parent 52cd539f93
commit 27ae81180e
3 changed files with 148 additions and 67 deletions

View File

@@ -16,11 +16,11 @@
</head>
<body class="bg-gray-100">
<div id="app" class="h-full flex flex-col">
<div id="app" class="h-full w-full flex flex-col">
<!-- header -->
<div class="flex bg-white p-2 border-gray-300 border-b">
<div class="max-w-xl mx-auto flex w-full">
<div class="flex w-full">
<div class="hidden sm:flex my-auto border border-gray-300 rounded-md w-10 h-10 mr-3 shadow">
<div class="flex mx-auto my-auto">
<img class="w-9 h-9" src="https://reticulum.network/gfx/reticulum_logo_512.png"/>
@@ -61,82 +61,151 @@
</div>
</div>
<div>
<div @click="onPeerClick(peer)" v-for="peer of Object.values(peers)" class="cursor-pointer">
<span><{{ peer.destination_hash }}> {{ peer.app_data }}</span>
</div>
</div>
<!-- chat items -->
<div id="messages" class="h-full overflow-y-scroll px-3 sm:px-0">
<div class="max-w-xl mx-auto">
<div v-if="chatItems.length > 0" class="flex flex-col space-y-3 py-4">
<div v-for="chatItem of chatItems">
<div class="flex space-x-2 border border-gray-300 rounded-lg shadow px-2 py-1.5 bg-white">
<div>
<!-- error -->
<div v-if="chatItem.source_hash === 'error'" class="bg-red-500 text-white rounded-full p-1 shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
</div>
<!-- user -->
<div v-else class="bg-blue-500 text-white rounded-full p-1 shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
</div>
<!-- middle -->
<div class="flex h-full w-full px-2 space-x-2">
<!-- peers -->
<div class="w-72 h-full py-2">
<div class="border rounded-xl bg-white h-full shadow">
<div class="flex border-b border-gray-300 text-gray-700 p-2">
<div class="mr-2">
<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 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z" />
</svg>
</div>
<div>Peers Discovered ({{ Object.keys(peers).length }})</div>
</div>
<div class="divide-y divide-gray-100">
<div @click="onPeerClick(peer)" v-for="peer of Object.values(peers)" class="flex cursor-pointer p-2" :class="[ peer.destination_hash === selectedPeer?.destination_hash ? 'bg-gray-100' : 'bg-white hover:bg-gray-50' ]">
<div class="my-auto mr-2">
<img class="w-9 h-9 rounded-full" src="assets/images/user.png"/>
</div>
<div class="w-full">
<div class="font-semibold leading-5">
<span v-if="chatItem.is_outbound">You</span>
<span v-else-if="chatItem.source_hash === 'error'">Error</span>
<span v-else>@<{{ chatItem.source_hash }}></span>
</div>
<div v-if="chatItem.message.content" style="white-space:pre-wrap;word-wrap:break-word;font-family:inherit;">{{ chatItem.message.content }}</div>
<div v-if="chatItem.message.fields?.image" class="grid grid-cols-3 gap-2">
<img @click="openImage(`data:image/${chatItem.message.fields.image.image_type};base64,${chatItem.message.fields.image.image_bytes}`)" :src="`data:image/${chatItem.message.fields.image.image_type};base64,${chatItem.message.fields.image.image_bytes}`" class="w-full rounded-md shadow-md cursor-pointer"/>
</div>
<div v-if="chatItem.message.fields?.file_attachments">
<a target="_blank" :download="file_attachment.file_name" :href="`data:application/octet-stream;base64,${file_attachment.file_bytes}`" v-for="file_attachment of chatItem.message.fields?.file_attachments ?? []" class="flex border border-gray-200 hover:bg-gray-100 rounded px-2 py-1 text-sm text-gray-700 font-semibold cursor-pointer">
<div class="mr-2 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">{{ file_attachment.file_name }}</div>
<div class="ml-auto 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>
<div class="text-gray-900">{{ peer.app_data || "Anonymous Peer" }}</div>
<div class="text-gray-500 text-sm">@<{{ peer.destination_hash.substring(0, 8) }}></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- send message -->
<div class="bg-white border-gray-300 border-t p-2">
<div class="max-w-xl mx-auto">
<!-- chat view -->
<div class="flex flex-col flex-1">
<!-- peer selected -->
<div v-if="selectedPeer" class="flex flex-col h-full my-2 border rounded-xl bg-white shadow">
<!-- header -->
<div class="flex p-2 border-b border-gray-300">
<!-- peer info -->
<div>
<div>{{ selectedPeer.app_data || "Anonymous Peer" }}</div>
<div class="text-sm">@<{{ selectedPeer.destination_hash }}></div>
</div>
<!-- close button -->
<div class="ml-auto 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-1 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>
<!-- message composer -->
<div>
<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>
<div class="flex">
<button @click="sendMessage" type="button" class="ml-auto mt-2 my-auto inline-flex items-center gap-x-1 rounded-md bg-blue-500 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500">
Send
</button>
</div>
<!-- chat items -->
<div id="messages" class="h-full overflow-y-scroll px-3 sm:px-0">
<div class="max-w-xl mx-auto">
<div v-if="chatItems.length > 0" class="flex flex-col space-y-3 py-4">
<div v-for="chatItem of chatItems">
<div class="flex space-x-2 border border-gray-300 rounded-lg shadow px-2 py-1.5 bg-white">
<div>
<!-- error -->
<div v-if="chatItem.source_hash === 'error'" class="bg-red-500 text-white rounded-full p-1 shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
</div>
<!-- user -->
<div v-else class="bg-blue-500 text-white rounded-full p-1 shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
</div>
</div>
<div class="w-full">
<div class="font-semibold leading-5">
<span v-if="chatItem.is_outbound">You</span>
<span v-else-if="chatItem.source_hash === 'error'">Error</span>
<span v-else>@<{{ chatItem.source_hash }}></span>
</div>
<div v-if="chatItem.message.content" style="white-space:pre-wrap;word-wrap:break-word;font-family:inherit;">{{ chatItem.message.content }}</div>
<div v-if="chatItem.message.fields?.image" class="grid grid-cols-3 gap-2">
<img @click="openImage(`data:image/${chatItem.message.fields.image.image_type};base64,${chatItem.message.fields.image.image_bytes}`)" :src="`data:image/${chatItem.message.fields.image.image_type};base64,${chatItem.message.fields.image.image_bytes}`" class="w-full rounded-md shadow-md cursor-pointer"/>
</div>
<div v-if="chatItem.message.fields?.file_attachments">
<a target="_blank" :download="file_attachment.file_name" :href="`data:application/octet-stream;base64,${file_attachment.file_bytes}`" v-for="file_attachment of chatItem.message.fields?.file_attachments ?? []" class="flex border border-gray-200 hover:bg-gray-100 rounded px-2 py-1 text-sm text-gray-700 font-semibold cursor-pointer">
<div class="mr-2 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">{{ file_attachment.file_name }}</div>
<div class="ml-auto 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>
</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>
<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>
<div class="flex">
<button @click="sendMessage" type="button" class="ml-auto mt-2 my-auto inline-flex items-center gap-x-1 rounded-md bg-blue-500 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500">
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>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

16
web.py
View File

@@ -3,6 +3,8 @@
import argparse
import http
import json
import mimetypes
import os
import RNS
import LXMF
@@ -43,7 +45,7 @@ class ReticulumWebChat:
# handle serving custom http paths
async def process_request(self, path, request_headers):
# serve index.html
if path == "/":
with open("index.html") as f:
@@ -51,10 +53,20 @@ class ReticulumWebChat:
return http.HTTPStatus.OK, [
('Content-Type', 'text/html')
], file_content.encode("utf-8")
# serve anything in public folder
public_file_path = os.path.join("public", path.lstrip("/"))
if os.path.isfile(public_file_path):
mime_type, _ = mimetypes.guess_type(public_file_path)
with open(public_file_path, "rb") as f:
file_content = f.read()
return http.HTTPStatus.OK, [
('Content-Type', mime_type)
], file_content
# by default, websocket is always served, but we only want it to be available at /ws
# so we will return 404 for everything other than /ws
elif path != "/ws":
if path != "/ws":
return http.HTTPStatus.NOT_FOUND, [
('Content-Type', 'text/html')
], b"Not Found"