import { Controller } from "@hotwired/stimulus"
import FileUploader from "models/file_uploader"
import { onNextEventLoopTick, nextFrame } from "helpers/timing_helpers"
import { escapeHTML } from "helpers/dom_helpers"
export default class extends Controller {
static classes = ["toolbar"]
static targets = [ "clientid", "fields", "fileList", "text" ]
static values = { roomId: Number }
static outlets = [ "messages" ]
#files = []
connect() {
if (!this.#usingTouchDevice) {
onNextEventLoopTick(() => this.textTarget.focus())
}
}
submit(event) {
event.preventDefault()
if (!this.fieldsTarget.disabled) {
this.#submitFiles()
this.#submitMessage()
this.collapseToolbar()
this.textTarget.focus()
}
}
submitEnd(event) {
if (!event.detail.success) {
this.messagesOutlet.failPendingMessage(this.clientidTarget.value)
}
}
toggleToolbar() {
this.element.classList.toggle(this.toolbarClass)
this.textTarget.focus()
}
collapseToolbar() {
this.element.classList.remove(this.toolbarClass)
}
replaceMessageContent(content) {
const editor = this.textTarget.editor
editor.recordUndoEntry("Format reply")
editor.setSelectedRange([0, editor.getDocument().toString().length])
editor.deleteInDirection("forward")
editor.insertHTML(content)
editor.setSelectedRange([editor.getDocument().toString().length - 1])
}
submitByKeyboard(event) {
const toolbarVisible = this.element.classList.contains(this.toolbarClass)
const metaEnter = event.key == "Enter" && (event.metaKey || event.ctrlKey)
const plainEnter = event.keyCode == 13 && !event.shiftKey && !event.isComposing
if (!this.#usingTouchDevice && (metaEnter || (plainEnter && !toolbarVisible))) {
this.submit(event)
}
}
filePicked(event) {
for (const file of event.target.files) {
this.#files.push(file)
}
event.target.value = null
this.#updateFileList()
}
fileUnpicked(event) {
this.#files.splice(event.params.index, 1)
this.#updateFileList()
}
pasteFiles(event) {
if (event.clipboardData.files.length > 0) {
event.preventDefault()
}
for (const file of event.clipboardData.files) {
this.#files.push(file)
}
this.#updateFileList()
}
dropFiles({ detail: { files } }) {
for (const file of files) {
this.#files.push(file)
}
this.#updateFileList()
}
preventAttachment(event) {
event.preventDefault()
}
online() {
this.fieldsTarget.disabled = false
}
offline() {
this.fieldsTarget.disabled = true
}
get #usingTouchDevice() {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
}
async #submitMessage() {
if (this.#validInput()) {
const clientMessageId = this.#generateClientId()
await this.messagesOutlet.insertPendingMessage(clientMessageId, this.textTarget)
await nextFrame()
this.clientidTarget.value = clientMessageId
this.element.requestSubmit()
this.#reset()
}
}
#validInput() {
return this.textTarget.textContent.trim().length > 0
}
async #submitFiles() {
const files = this.#files
this.#files = []
this.#updateFileList()
for (const file of files) {
const clientMessageId = this.#generateClientId()
const uploader = new FileUploader(file, this.element.action, clientMessageId, this.#uploadProgress.bind(this))
const body = this.#pendingUploadProgress(file.name)
await this.messagesOutlet.insertPendingMessage(clientMessageId, body)
const resp = await uploader.upload()
Turbo.renderStreamMessage(resp)
}
}
#uploadProgress(percent, clientMessageId, file) {
const body = this.#pendingUploadProgress(file.name, percent)
this.messagesOutlet.updatePendingMessage(clientMessageId, body)
}
#generateClientId() {
return Math.random().toString(36).slice(2)
}
#reset() {
this.textTarget.value = ""
}
#updateFileList() {
this.#files.sort((a, b) => a.name.localeCompare(b.name))
const fileNodes = this.#files.map((file, index) => {
const filename = file.name.split(".").slice(0, -1).join(".")
const extension = file.name.split(".").pop()
const node = document.createElement("button")
node.setAttribute("type","button")
node.setAttribute("style","gap: 0")
node.dataset.action = "composer#fileUnpicked"
node.dataset.composerIndexParam = index
node.className = "btn btn--plain composer__file txt-normal position-relative unpad flex-column"
node.innerHTML = file.type.match(/^image\/.*/) ? `` : ``
node.innerHTML += `${escapeHTML(filename)}.${escapeHTML(extension)}`
return node
})
this.fileListTarget.replaceChildren(...fileNodes)
}
#pendingUploadProgress(filename, percent=0) {
return `