implement sending file attachments via web ui
This commit is contained in:
@@ -253,12 +253,46 @@
|
||||
|
||||
<!-- message composer -->
|
||||
<div>
|
||||
|
||||
<!-- file attachments -->
|
||||
<div 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>
|
||||
<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">
|
||||
|
||||
<!-- action button -->
|
||||
<div class="flex mt-2">
|
||||
|
||||
<!-- add files -->
|
||||
<button @click="addFilesToMessage" type="button" class="my-auto 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>
|
||||
|
||||
<!-- send message -->
|
||||
<button @click="sendMessage" type="button" class="ml-auto 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>
|
||||
@@ -281,6 +315,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- hidden file input for selecting files -->
|
||||
<input ref="file-input" @change="onFileInputChange" type="file" multiple style="display:none"/>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
Vue.createApp({
|
||||
@@ -291,6 +328,7 @@
|
||||
autoReconnectWebsocket: true,
|
||||
|
||||
newMessageText: "",
|
||||
newMessageFiles: [],
|
||||
isSendingMessage: false,
|
||||
autoScrollOnNewMessage: true,
|
||||
|
||||
@@ -535,15 +573,37 @@
|
||||
|
||||
try {
|
||||
|
||||
// build fields
|
||||
const fields = {};
|
||||
|
||||
// add file attachments
|
||||
if(this.newMessageFiles.length > 0){
|
||||
const fileAttachments = [];
|
||||
var fileAttachmentsTotalSize = 0;
|
||||
for(const file of this.newMessageFiles){
|
||||
fileAttachmentsTotalSize += file.size;
|
||||
fileAttachments.push({
|
||||
"file_name": file.name,
|
||||
"file_bytes": this.arrayBufferToBase64(await file.arrayBuffer()),
|
||||
});
|
||||
}
|
||||
fields["file_attachments"] = fileAttachments;
|
||||
}
|
||||
|
||||
// send message to reticulum via websocket
|
||||
this.ws.send(JSON.stringify({
|
||||
"type": "lxmf.delivery",
|
||||
"destination_hash": this.selectedPeer.destination_hash,
|
||||
"message": messageText,
|
||||
"lxmf_message": {
|
||||
"destination_hash": this.selectedPeer.destination_hash,
|
||||
"content": messageText,
|
||||
"fields": fields,
|
||||
},
|
||||
}));
|
||||
|
||||
// clear message input
|
||||
// clear message inputs
|
||||
this.newMessageText = "";
|
||||
this.newMessageFiles = [];
|
||||
this.clearFileInput();
|
||||
|
||||
} catch(e) {
|
||||
|
||||
@@ -755,6 +815,46 @@
|
||||
getNomadnetFileDownloadCallbackKey: function(destinationHash, filePath) {
|
||||
return `${destinationHash}:${filePath}`;
|
||||
},
|
||||
addFilesToMessage: function() {
|
||||
this.$refs["file-input"].click();
|
||||
},
|
||||
onFileInputChange: function(event) {
|
||||
for(const file of event.target.files){
|
||||
this.newMessageFiles.push(file);
|
||||
}
|
||||
},
|
||||
clearFileInput: function() {
|
||||
this.$refs["file-input"].value = null;
|
||||
},
|
||||
removeFileAttachment: function(file) {
|
||||
this.newMessageFiles = this.newMessageFiles.filter((newMessageFile) => {
|
||||
return newMessageFile !== file;
|
||||
});
|
||||
},
|
||||
formatBytes: function(bytes) {
|
||||
|
||||
if(bytes === 0){
|
||||
return '0 Bytes';
|
||||
}
|
||||
|
||||
const k = 1024;
|
||||
const decimals = 0;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
|
||||
|
||||
},
|
||||
arrayBufferToBase64: function(arrayBuffer) {
|
||||
var binary = '';
|
||||
var bytes = new Uint8Array(arrayBuffer);
|
||||
var len = bytes.byteLength;
|
||||
for(var i = 0; i < len; i++){
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
|
||||
5
web.py
5
web.py
@@ -103,7 +103,10 @@ class ReticulumWebChat:
|
||||
async def ws(request):
|
||||
|
||||
# prepare websocket response
|
||||
websocket_response = web.WebSocketResponse()
|
||||
websocket_response = web.WebSocketResponse(
|
||||
# set max message size accepted by server to 50 megabytes
|
||||
max_msg_size=50 * 1024 * 1024,
|
||||
)
|
||||
await websocket_response.prepare(request)
|
||||
|
||||
# add client to connected clients list
|
||||
|
||||
Reference in New Issue
Block a user