Fossil SCM

chat: reworked the drag/drop bits to take advantage of Firefox and Chrome already supporting drag/drop onto a file input element.

stephan 2020-12-23 15:00 chatroom-dev
Commit 7e48953c16df07d35c150ee1c3d14bfad7a79969d515f3f836e06132bc80521d
2 files changed +8 -14 +11 -50
+8 -14
--- src/chat.c
+++ src/chat.c
@@ -84,40 +84,36 @@
8484
@ #chat-input-file {
8585
@ display: flex;
8686
@ flex-direction: row;
8787
@ align-items: center;
8888
@ }
89
+ @ #chat-input-file > .help-buttonlet,
8990
@ #chat-input-file > input[type=file] {
9091
@ align-self: flex-start;
92
+ @ margin-right: 0.5em;
9193
@ flex: 1 1 auto;
9294
@ }
9395
@ #chat-input-file > input {
9496
@ flex: 1 0 auto;
9597
@ }
96
- @ #chat-input-file > *:nth-child(1) { margin-right: 0.5em; }
97
- @ #chat-input-file > *:nth-child(2) { margin-left: 0.5em; }
9898
@ .chat-timestamp {
9999
@ font-family: monospace;
100100
@ font-size: 0.8em;
101101
@ white-space: pre;
102102
@ text-align: left;
103103
@ opacity: 0.8;
104104
@ }
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 {
114106
@ border: 1px dashed green;
115107
@ }
116108
@ #chat-drop-details {
109
+ @ flex: 0 1 auto;
110
+ @ padding: 0.5em 1em;
111
+ @ margin-left: 0.5em;
117112
@ white-space: pre;
118113
@ font-family: monospace;
114
+ @ max-width: 50%%;
119115
@ }
120116
@ </style>
121117
@ <form accept-encoding="utf-8" id="chat-form">
122118
@ <div id='chat-input-area'>
123119
@ <div id='chat-input-line'>
@@ -125,13 +121,11 @@
125121
@ placeholder="Type message here.">
126122
@ <input type="submit" value="Send">
127123
@ </div>
128124
@ <div id='chat-input-file'>
129125
@ <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>
133127
@ </div>
134128
@ </div>
135129
@ </form>
136130
@ <hr>
137131
138132
--- 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 @@
33
let mxMsg = 0;
44
const F = window.fossil, D = F.dom;
55
const _me = F.user.name;
66
/* State for paste and drag/drop */
77
const BlobXferState = {
8
- dropZone: document.querySelector('#chat-drop-zone'),
98
dropDetails: document.querySelector('#chat-drop-details'),
109
blob: undefined
1110
};
1211
/** Updates the paste/drop zone with details of the pasted/dropped
1312
data. */
1413
const updateDropZoneContent = function(blob){
1514
const bx = BlobXferState, dd = bx.dropDetails;
1615
bx.blob = blob;
1716
D.clearElement(dd);
18
- if(!blob) return;
17
+ if(!blob){
18
+ form.file.value = '';
19
+ return;
20
+ }
1921
D.append(dd, "Name: ", blob.name,
2022
D.br(), "Size: ",blob.size);
2123
if(blob.type && blob.type.startsWith("image/")){
2224
const img = D.img();
2325
D.append(dd, D.br(), img);
@@ -27,53 +29,13 @@
2729
}
2830
const btn = D.button("Cancel");
2931
D.append(dd, D.br(), btn);
3032
btn.addEventListener('click', ()=>updateDropZoneContent(), false);
3133
};
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)
7537
});
7638
7739
form.addEventListener('submit',(e)=>{
7840
e.preventDefault();
7941
const fd = new FormData(form);
@@ -106,17 +68,16 @@
10668
item.getAsString((v)=>form.msg.value = v);
10769
}
10870
};
10971
if(true){/* Add help button for drag/drop/paste zone */
11072
const help = D.div();
111
- BlobXferState.dropDetails.parentNode.insertBefore(
112
- help,BlobXferState.dropDetails
113
- );
73
+ form.file.parentNode.insertBefore(help, form.file);
11474
F.helpButtonlets.create(
11575
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."
11879
);
11980
}
12081
12182
/* Injects element e as a new row in the chat, at the top of the list */
12283
const injectMessage = function f(e){
12384
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button