Fossil SCM
chat: reworked the drag/drop bits to take advantage of Firefox and Chrome already supporting drag/drop onto a file input element.
Commit
7e48953c16df07d35c150ee1c3d14bfad7a79969d515f3f836e06132bc80521d
Parent
7779535f04dd104…
2 files changed
+8
-14
+11
-50
+8
-14
| --- src/chat.c | ||
| +++ src/chat.c | ||
| @@ -84,40 +84,36 @@ | ||
| 84 | 84 | @ #chat-input-file { |
| 85 | 85 | @ display: flex; |
| 86 | 86 | @ flex-direction: row; |
| 87 | 87 | @ align-items: center; |
| 88 | 88 | @ } |
| 89 | + @ #chat-input-file > .help-buttonlet, | |
| 89 | 90 | @ #chat-input-file > input[type=file] { |
| 90 | 91 | @ align-self: flex-start; |
| 92 | + @ margin-right: 0.5em; | |
| 91 | 93 | @ flex: 1 1 auto; |
| 92 | 94 | @ } |
| 93 | 95 | @ #chat-input-file > input { |
| 94 | 96 | @ flex: 1 0 auto; |
| 95 | 97 | @ } |
| 96 | - @ #chat-input-file > *:nth-child(1) { margin-right: 0.5em; } | |
| 97 | - @ #chat-input-file > *:nth-child(2) { margin-left: 0.5em; } | |
| 98 | 98 | @ .chat-timestamp { |
| 99 | 99 | @ font-family: monospace; |
| 100 | 100 | @ font-size: 0.8em; |
| 101 | 101 | @ white-space: pre; |
| 102 | 102 | @ text-align: left; |
| 103 | 103 | @ opacity: 0.8; |
| 104 | 104 | @ } |
| 105 | - @ #chat-drop-zone { | |
| 106 | - @ box-sizing: content-box; | |
| 107 | - @ background-color: #e0e0e0; | |
| 108 | - @ flex: 1 1 auto; | |
| 109 | - @ padding: 0.5em 1em; | |
| 110 | - @ border: 1px solid #808080; | |
| 111 | - @ border-radius: 0.25em; | |
| 112 | - @ } | |
| 113 | - @ #chat-drop-zone.dragover { | |
| 105 | + @ .dragover { | |
| 114 | 106 | @ border: 1px dashed green; |
| 115 | 107 | @ } |
| 116 | 108 | @ #chat-drop-details { |
| 109 | + @ flex: 0 1 auto; | |
| 110 | + @ padding: 0.5em 1em; | |
| 111 | + @ margin-left: 0.5em; | |
| 117 | 112 | @ white-space: pre; |
| 118 | 113 | @ font-family: monospace; |
| 114 | + @ max-width: 50%%; | |
| 119 | 115 | @ } |
| 120 | 116 | @ </style> |
| 121 | 117 | @ <form accept-encoding="utf-8" id="chat-form"> |
| 122 | 118 | @ <div id='chat-input-area'> |
| 123 | 119 | @ <div id='chat-input-line'> |
| @@ -125,13 +121,11 @@ | ||
| 125 | 121 | @ placeholder="Type message here."> |
| 126 | 122 | @ <input type="submit" value="Send"> |
| 127 | 123 | @ </div> |
| 128 | 124 | @ <div id='chat-input-file'> |
| 129 | 125 | @ <input type="file" name="file"> |
| 130 | - @ <div id="chat-drop-zone"> | |
| 131 | - @ <div id="chat-drop-details"></div> | |
| 132 | - @ </div> | |
| 126 | + @ <div id="chat-drop-details"></div> | |
| 133 | 127 | @ </div> |
| 134 | 128 | @ </div> |
| 135 | 129 | @ </form> |
| 136 | 130 | @ <hr> |
| 137 | 131 | |
| 138 | 132 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -84,40 +84,36 @@ | |
| 84 | @ #chat-input-file { |
| 85 | @ display: flex; |
| 86 | @ flex-direction: row; |
| 87 | @ align-items: center; |
| 88 | @ } |
| 89 | @ #chat-input-file > input[type=file] { |
| 90 | @ align-self: flex-start; |
| 91 | @ flex: 1 1 auto; |
| 92 | @ } |
| 93 | @ #chat-input-file > input { |
| 94 | @ flex: 1 0 auto; |
| 95 | @ } |
| 96 | @ #chat-input-file > *:nth-child(1) { margin-right: 0.5em; } |
| 97 | @ #chat-input-file > *:nth-child(2) { margin-left: 0.5em; } |
| 98 | @ .chat-timestamp { |
| 99 | @ font-family: monospace; |
| 100 | @ font-size: 0.8em; |
| 101 | @ white-space: pre; |
| 102 | @ text-align: left; |
| 103 | @ opacity: 0.8; |
| 104 | @ } |
| 105 | @ #chat-drop-zone { |
| 106 | @ box-sizing: content-box; |
| 107 | @ background-color: #e0e0e0; |
| 108 | @ flex: 1 1 auto; |
| 109 | @ padding: 0.5em 1em; |
| 110 | @ border: 1px solid #808080; |
| 111 | @ border-radius: 0.25em; |
| 112 | @ } |
| 113 | @ #chat-drop-zone.dragover { |
| 114 | @ border: 1px dashed green; |
| 115 | @ } |
| 116 | @ #chat-drop-details { |
| 117 | @ white-space: pre; |
| 118 | @ font-family: monospace; |
| 119 | @ } |
| 120 | @ </style> |
| 121 | @ <form accept-encoding="utf-8" id="chat-form"> |
| 122 | @ <div id='chat-input-area'> |
| 123 | @ <div id='chat-input-line'> |
| @@ -125,13 +121,11 @@ | |
| 125 | @ placeholder="Type message here."> |
| 126 | @ <input type="submit" value="Send"> |
| 127 | @ </div> |
| 128 | @ <div id='chat-input-file'> |
| 129 | @ <input type="file" name="file"> |
| 130 | @ <div id="chat-drop-zone"> |
| 131 | @ <div id="chat-drop-details"></div> |
| 132 | @ </div> |
| 133 | @ </div> |
| 134 | @ </div> |
| 135 | @ </form> |
| 136 | @ <hr> |
| 137 | |
| 138 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -84,40 +84,36 @@ | |
| 84 | @ #chat-input-file { |
| 85 | @ display: flex; |
| 86 | @ flex-direction: row; |
| 87 | @ align-items: center; |
| 88 | @ } |
| 89 | @ #chat-input-file > .help-buttonlet, |
| 90 | @ #chat-input-file > input[type=file] { |
| 91 | @ align-self: flex-start; |
| 92 | @ margin-right: 0.5em; |
| 93 | @ flex: 1 1 auto; |
| 94 | @ } |
| 95 | @ #chat-input-file > input { |
| 96 | @ flex: 1 0 auto; |
| 97 | @ } |
| 98 | @ .chat-timestamp { |
| 99 | @ font-family: monospace; |
| 100 | @ font-size: 0.8em; |
| 101 | @ white-space: pre; |
| 102 | @ text-align: left; |
| 103 | @ opacity: 0.8; |
| 104 | @ } |
| 105 | @ .dragover { |
| 106 | @ border: 1px dashed green; |
| 107 | @ } |
| 108 | @ #chat-drop-details { |
| 109 | @ flex: 0 1 auto; |
| 110 | @ padding: 0.5em 1em; |
| 111 | @ margin-left: 0.5em; |
| 112 | @ white-space: pre; |
| 113 | @ font-family: monospace; |
| 114 | @ max-width: 50%%; |
| 115 | @ } |
| 116 | @ </style> |
| 117 | @ <form accept-encoding="utf-8" id="chat-form"> |
| 118 | @ <div id='chat-input-area'> |
| 119 | @ <div id='chat-input-line'> |
| @@ -125,13 +121,11 @@ | |
| 121 | @ placeholder="Type message here."> |
| 122 | @ <input type="submit" value="Send"> |
| 123 | @ </div> |
| 124 | @ <div id='chat-input-file'> |
| 125 | @ <input type="file" name="file"> |
| 126 | @ <div id="chat-drop-details"></div> |
| 127 | @ </div> |
| 128 | @ </div> |
| 129 | @ </form> |
| 130 | @ <hr> |
| 131 | |
| 132 |
+11
-50
| --- src/chat.js | ||
| +++ src/chat.js | ||
| @@ -3,21 +3,23 @@ | ||
| 3 | 3 | let mxMsg = 0; |
| 4 | 4 | const F = window.fossil, D = F.dom; |
| 5 | 5 | const _me = F.user.name; |
| 6 | 6 | /* State for paste and drag/drop */ |
| 7 | 7 | const BlobXferState = { |
| 8 | - dropZone: document.querySelector('#chat-drop-zone'), | |
| 9 | 8 | dropDetails: document.querySelector('#chat-drop-details'), |
| 10 | 9 | blob: undefined |
| 11 | 10 | }; |
| 12 | 11 | /** Updates the paste/drop zone with details of the pasted/dropped |
| 13 | 12 | data. */ |
| 14 | 13 | const updateDropZoneContent = function(blob){ |
| 15 | 14 | const bx = BlobXferState, dd = bx.dropDetails; |
| 16 | 15 | bx.blob = blob; |
| 17 | 16 | D.clearElement(dd); |
| 18 | - if(!blob) return; | |
| 17 | + if(!blob){ | |
| 18 | + form.file.value = ''; | |
| 19 | + return; | |
| 20 | + } | |
| 19 | 21 | D.append(dd, "Name: ", blob.name, |
| 20 | 22 | D.br(), "Size: ",blob.size); |
| 21 | 23 | if(blob.type && blob.type.startsWith("image/")){ |
| 22 | 24 | const img = D.img(); |
| 23 | 25 | D.append(dd, D.br(), img); |
| @@ -27,53 +29,13 @@ | ||
| 27 | 29 | } |
| 28 | 30 | const btn = D.button("Cancel"); |
| 29 | 31 | D.append(dd, D.br(), btn); |
| 30 | 32 | btn.addEventListener('click', ()=>updateDropZoneContent(), false); |
| 31 | 33 | }; |
| 32 | - //////////////////////////////////////////////////////////// | |
| 33 | - // File drag/drop. | |
| 34 | - // Adapted from: https://stackoverflow.com/a/58677161 | |
| 35 | - const dropHighlight = BlobXferState.dropZone /* target zone */; | |
| 36 | - const dropEvents = { | |
| 37 | - drop: function(ev){ | |
| 38 | - ev.preventDefault(); | |
| 39 | - D.removeClass(dropHighlight, 'dragover'); | |
| 40 | - const file = ev.dataTransfer.files[0]; | |
| 41 | - if(file) { | |
| 42 | - updateDropZoneContent(file); | |
| 43 | - } | |
| 44 | - }, | |
| 45 | - dragenter: function(ev){ | |
| 46 | - ev.preventDefault(); | |
| 47 | - ev.dataTransfer.dropEffect = "copy"; | |
| 48 | - D.addClass(dropHighlight, 'dragover'); | |
| 49 | - }, | |
| 50 | - dragover: function(ev){ | |
| 51 | - ev.preventDefault(); | |
| 52 | - }, | |
| 53 | - dragend: function(ev){ | |
| 54 | - ev.preventDefault(); | |
| 55 | - }, | |
| 56 | - dragleave: function(ev){ | |
| 57 | - ev.preventDefault(); | |
| 58 | - D.removeClass(dropHighlight, 'dragover'); | |
| 59 | - } | |
| 60 | - }; | |
| 61 | - /* | |
| 62 | - The idea here is to accept drops at multiple points or, ideally, | |
| 63 | - document.body, and apply them to P.e.taContent, but the precise | |
| 64 | - combination of event handling needed to pull this off is eluding | |
| 65 | - me. | |
| 66 | - */ | |
| 67 | - [BlobXferState.dropZone | |
| 68 | - /* ideally we'd link only to document.body, but the events seem to | |
| 69 | - get out of whack, with dropleave being triggered at unexpected | |
| 70 | - points. */ | |
| 71 | - ].forEach(function(e){ | |
| 72 | - Object.keys(dropEvents).forEach( | |
| 73 | - (k)=>e.addEventListener(k, dropEvents[k], true) | |
| 74 | - ); | |
| 34 | + form.file.addEventListener('change', function(ev){ | |
| 35 | + //console.debug("this =",this); | |
| 36 | + updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined) | |
| 75 | 37 | }); |
| 76 | 38 | |
| 77 | 39 | form.addEventListener('submit',(e)=>{ |
| 78 | 40 | e.preventDefault(); |
| 79 | 41 | const fd = new FormData(form); |
| @@ -106,17 +68,16 @@ | ||
| 106 | 68 | item.getAsString((v)=>form.msg.value = v); |
| 107 | 69 | } |
| 108 | 70 | }; |
| 109 | 71 | if(true){/* Add help button for drag/drop/paste zone */ |
| 110 | 72 | const help = D.div(); |
| 111 | - BlobXferState.dropDetails.parentNode.insertBefore( | |
| 112 | - help,BlobXferState.dropDetails | |
| 113 | - ); | |
| 73 | + form.file.parentNode.insertBefore(help, form.file); | |
| 114 | 74 | F.helpButtonlets.create( |
| 115 | 75 | help, |
| 116 | - "Drag/drop a file into this spot, or paste an image "+ | |
| 117 | - "from the clipboard if supported by your environment." | |
| 76 | + "Select a file to upload, drag/drop a file into this spot, ", | |
| 77 | + "or paste an image from the clipboard if supported by ", | |
| 78 | + "your environment." | |
| 118 | 79 | ); |
| 119 | 80 | } |
| 120 | 81 | |
| 121 | 82 | /* Injects element e as a new row in the chat, at the top of the list */ |
| 122 | 83 | const injectMessage = function f(e){ |
| 123 | 84 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -3,21 +3,23 @@ | |
| 3 | let mxMsg = 0; |
| 4 | const F = window.fossil, D = F.dom; |
| 5 | const _me = F.user.name; |
| 6 | /* State for paste and drag/drop */ |
| 7 | const BlobXferState = { |
| 8 | dropZone: document.querySelector('#chat-drop-zone'), |
| 9 | dropDetails: document.querySelector('#chat-drop-details'), |
| 10 | blob: undefined |
| 11 | }; |
| 12 | /** Updates the paste/drop zone with details of the pasted/dropped |
| 13 | data. */ |
| 14 | const updateDropZoneContent = function(blob){ |
| 15 | const bx = BlobXferState, dd = bx.dropDetails; |
| 16 | bx.blob = blob; |
| 17 | D.clearElement(dd); |
| 18 | if(!blob) return; |
| 19 | D.append(dd, "Name: ", blob.name, |
| 20 | D.br(), "Size: ",blob.size); |
| 21 | if(blob.type && blob.type.startsWith("image/")){ |
| 22 | const img = D.img(); |
| 23 | D.append(dd, D.br(), img); |
| @@ -27,53 +29,13 @@ | |
| 27 | } |
| 28 | const btn = D.button("Cancel"); |
| 29 | D.append(dd, D.br(), btn); |
| 30 | btn.addEventListener('click', ()=>updateDropZoneContent(), false); |
| 31 | }; |
| 32 | //////////////////////////////////////////////////////////// |
| 33 | // File drag/drop. |
| 34 | // Adapted from: https://stackoverflow.com/a/58677161 |
| 35 | const dropHighlight = BlobXferState.dropZone /* target zone */; |
| 36 | const dropEvents = { |
| 37 | drop: function(ev){ |
| 38 | ev.preventDefault(); |
| 39 | D.removeClass(dropHighlight, 'dragover'); |
| 40 | const file = ev.dataTransfer.files[0]; |
| 41 | if(file) { |
| 42 | updateDropZoneContent(file); |
| 43 | } |
| 44 | }, |
| 45 | dragenter: function(ev){ |
| 46 | ev.preventDefault(); |
| 47 | ev.dataTransfer.dropEffect = "copy"; |
| 48 | D.addClass(dropHighlight, 'dragover'); |
| 49 | }, |
| 50 | dragover: function(ev){ |
| 51 | ev.preventDefault(); |
| 52 | }, |
| 53 | dragend: function(ev){ |
| 54 | ev.preventDefault(); |
| 55 | }, |
| 56 | dragleave: function(ev){ |
| 57 | ev.preventDefault(); |
| 58 | D.removeClass(dropHighlight, 'dragover'); |
| 59 | } |
| 60 | }; |
| 61 | /* |
| 62 | The idea here is to accept drops at multiple points or, ideally, |
| 63 | document.body, and apply them to P.e.taContent, but the precise |
| 64 | combination of event handling needed to pull this off is eluding |
| 65 | me. |
| 66 | */ |
| 67 | [BlobXferState.dropZone |
| 68 | /* ideally we'd link only to document.body, but the events seem to |
| 69 | get out of whack, with dropleave being triggered at unexpected |
| 70 | points. */ |
| 71 | ].forEach(function(e){ |
| 72 | Object.keys(dropEvents).forEach( |
| 73 | (k)=>e.addEventListener(k, dropEvents[k], true) |
| 74 | ); |
| 75 | }); |
| 76 | |
| 77 | form.addEventListener('submit',(e)=>{ |
| 78 | e.preventDefault(); |
| 79 | const fd = new FormData(form); |
| @@ -106,17 +68,16 @@ | |
| 106 | item.getAsString((v)=>form.msg.value = v); |
| 107 | } |
| 108 | }; |
| 109 | if(true){/* Add help button for drag/drop/paste zone */ |
| 110 | const help = D.div(); |
| 111 | BlobXferState.dropDetails.parentNode.insertBefore( |
| 112 | help,BlobXferState.dropDetails |
| 113 | ); |
| 114 | F.helpButtonlets.create( |
| 115 | help, |
| 116 | "Drag/drop a file into this spot, or paste an image "+ |
| 117 | "from the clipboard if supported by your environment." |
| 118 | ); |
| 119 | } |
| 120 | |
| 121 | /* Injects element e as a new row in the chat, at the top of the list */ |
| 122 | const injectMessage = function f(e){ |
| 123 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -3,21 +3,23 @@ | |
| 3 | let mxMsg = 0; |
| 4 | const F = window.fossil, D = F.dom; |
| 5 | const _me = F.user.name; |
| 6 | /* State for paste and drag/drop */ |
| 7 | const BlobXferState = { |
| 8 | dropDetails: document.querySelector('#chat-drop-details'), |
| 9 | blob: undefined |
| 10 | }; |
| 11 | /** Updates the paste/drop zone with details of the pasted/dropped |
| 12 | data. */ |
| 13 | const updateDropZoneContent = function(blob){ |
| 14 | const bx = BlobXferState, dd = bx.dropDetails; |
| 15 | bx.blob = blob; |
| 16 | D.clearElement(dd); |
| 17 | if(!blob){ |
| 18 | form.file.value = ''; |
| 19 | return; |
| 20 | } |
| 21 | D.append(dd, "Name: ", blob.name, |
| 22 | D.br(), "Size: ",blob.size); |
| 23 | if(blob.type && blob.type.startsWith("image/")){ |
| 24 | const img = D.img(); |
| 25 | D.append(dd, D.br(), img); |
| @@ -27,53 +29,13 @@ | |
| 29 | } |
| 30 | const btn = D.button("Cancel"); |
| 31 | D.append(dd, D.br(), btn); |
| 32 | btn.addEventListener('click', ()=>updateDropZoneContent(), false); |
| 33 | }; |
| 34 | form.file.addEventListener('change', function(ev){ |
| 35 | //console.debug("this =",this); |
| 36 | updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined) |
| 37 | }); |
| 38 | |
| 39 | form.addEventListener('submit',(e)=>{ |
| 40 | e.preventDefault(); |
| 41 | const fd = new FormData(form); |
| @@ -106,17 +68,16 @@ | |
| 68 | item.getAsString((v)=>form.msg.value = v); |
| 69 | } |
| 70 | }; |
| 71 | if(true){/* Add help button for drag/drop/paste zone */ |
| 72 | const help = D.div(); |
| 73 | form.file.parentNode.insertBefore(help, form.file); |
| 74 | F.helpButtonlets.create( |
| 75 | help, |
| 76 | "Select a file to upload, drag/drop a file into this spot, ", |
| 77 | "or paste an image from the clipboard if supported by ", |
| 78 | "your environment." |
| 79 | ); |
| 80 | } |
| 81 | |
| 82 | /* Injects element e as a new row in the chat, at the top of the list */ |
| 83 | const injectMessage = function f(e){ |
| 84 |