efront 4.26.2 → 4.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,10 @@ function msg(elem, { m }, parentScopes) {
25
25
  elem.files = files;
26
26
  onclick(elem, clickfile);
27
27
  break;
28
+ case "rtc-video":
29
+ elem.setAttribute('rtc', 'video');
30
+ elem.innerHTML = "视频通话";
31
+ break;
28
32
  default:
29
33
  elem.innerText = m.content;
30
34
  }
@@ -101,6 +105,120 @@ var dragpage = {
101
105
  render.refresh();
102
106
  }
103
107
  }
108
+
109
+ var rtcMap = Object.create(null);
110
+ var getRtc = function (userid) {
111
+ var rtc = rtcMap[userid];
112
+ if (!rtc) rtc = rtcMap[userid] = new ChatRTC;
113
+ return rtc;
114
+ }
115
+ async function pullFileWithRTC(scope, file) {
116
+ var userid = scope.user.id;
117
+ var rtc = getRtc(userid);
118
+ var h = await window.showSaveFilePicker({ suggestedName: file.name });
119
+ var writable = await h.createWritable();
120
+ var channel = await rtc.createChannel();
121
+ channel.binaryType = 'arraybuffer';
122
+ var writed = 0;
123
+ var span = document.createElement('div');
124
+ span.style.textAlign = 'left';
125
+ span.innerText = '接收' + file.name;
126
+ var msg = document.createElement('span');
127
+ msg.style = 'font-size:10px;font-family: Consolas, "Courier New", monospace, sansif;'
128
+ appendChild(span, msg);
129
+ var tipbox = alert(span, false);
130
+ var report = lazy(function () {
131
+ // <!-- console.log("接收端发送",writed); -->
132
+ msg.innerText = ` (${size(file.size, 2)}\\${size(writed, 2)})`;
133
+ var a = new Uint8Array(16);
134
+ var high = writed / 0x100000000 | 0;
135
+ var low = writed & 0xffffffff;
136
+ a[0] = low & 0xff;
137
+ a[1] = low >>> 8 & 0xff;
138
+ a[2] = low >>> 16 & 0xff;
139
+ a[3] = low >>> 24 & 0xff;
140
+ a[4] = high & 0xff;
141
+ a[5] = high >>> 8 & 0xff;
142
+ a[6] = high >>> 16 & 0xff;
143
+ a[7] = high >>> 24 & 0xff;
144
+ channel.send(a);
145
+ }, -60);
146
+ channel.onopen = async function () {
147
+ // <!-- console.log('接收端打开') -->
148
+ report();
149
+ };
150
+ channel.onclose = function () {
151
+ // <!-- console.log('接收端关闭') -->
152
+ if (writed === file.size) tipbox.setText(`接收完成`, 'success');
153
+ else tipbox.setText('接收异常', "error");
154
+ };
155
+ channel.onerror = function (event) {
156
+ // <!-- console.log('接收端异常',event) -->
157
+ };
158
+ var ondate = function (date) {
159
+ scope.send("didate", date, userid);
160
+ };
161
+ var offer = await rtc.init(ondate);
162
+ scope.send('accept', { file: file.id, channel: channel.id, offer })
163
+ channel.onmessage = async function (event) {
164
+ var buff = new Uint8Array(event.data);
165
+ writed += buff.length;
166
+ // <!-- console.log('接收端收到', writed, file.size); -->
167
+ await writable.write(buff);
168
+ report();
169
+ if (writed >= file.size) {
170
+ writable.close();
171
+ }
172
+ }
173
+ }
174
+ /**
175
+ * @param {File} file
176
+ */
177
+ async function pushFileWithRTC(scope, file, msg) {
178
+ var sender = msg.sender;
179
+ var rtc = getRtc(sender);
180
+ var reader = file.stream().getReader();
181
+ var ondate = function (date) {
182
+ scope.send("didate", date, sender);
183
+ }
184
+ var answer = await rtc.init(ondate, msg.offer);
185
+ scope.send('takeup', answer, sender);
186
+ var remote = await rtc.waitChannel();
187
+ remote.binaryType = 'arraybuffer';
188
+ var writed = 0, reported = 0;
189
+ var readed = reader.read();
190
+ var sizeLimit = 65536;
191
+ remote.onmessage = async function (event) {
192
+ var [low, high] = new Uint32Array(event.data);
193
+ reported = high * 0x100000000 + low;
194
+ // <!-- console.log('发送端收到', size(writed), size(reported)); -->
195
+ if (reported < writed) return;
196
+ var readed1 = readed;
197
+ readed = reader.read();
198
+ var { done, value } = await readed1;
199
+ if (done) {
200
+ if (writed < file.size) console.error('发送未完成', size(file.size), size(writed), value);
201
+ remote.close();
202
+ return;
203
+ }
204
+ // <!-- console.log("发送",size(value.length)); -->
205
+ writed += value.length;
206
+ for (var cx = 0, dx = value.length; cx < dx;) {
207
+ remote.send(value.slice(cx, cx += sizeLimit));
208
+ }
209
+ }
210
+ remote.onclose = function () {
211
+ // <!-- console.log("发送端关闭") -->
212
+ }
213
+ remote.onerror = function (event) {
214
+ // <!-- console.log("发送端异常",event) -->
215
+ }
216
+ remote.onopen = async function () {
217
+ // <!-- console.log("发送端打开") -->
218
+ };
219
+ }
220
+
221
+
104
222
  function chat(title = '会话窗口') {
105
223
  var page = view();
106
224
  page.innerHTML = template;
@@ -122,12 +240,36 @@ function chat(title = '会话窗口') {
122
240
  ps.totalunread += msgs.length;
123
241
  }
124
242
  }
243
+ var msgTemp = Object.create(null);
125
244
  page.push = function (msgs) {
126
245
  var { msglist } = ps;
127
246
  var userMap = null;
247
+ var cached = [], cachedi = 0;
128
248
  msgs = msgs.filter(m => {
129
249
  if (!m) return false;
130
- if (isString(m)) return true;
250
+ if (isString(m)) {
251
+ if (/^\|/.test(m)) {
252
+ var a = /^\|(\d+)\|(\d+)\|(\d+)\|/.exec(m);
253
+ if (a) var [, msgid, total, index] = a;
254
+ total = +total, index = +index, msgid = +msgid;
255
+ var tmp = msgTemp[msgid];
256
+ if (!tmp) {
257
+ tmp = msgTemp[msgid] = Array(total);
258
+ tmp.count = 0;
259
+ }
260
+ if (!tmp[index]) {
261
+ tmp[index] = m.slice(a.index + a[0].length);
262
+ tmp.count++;
263
+ }
264
+ if (tmp.count === total) {
265
+ cached.push([cachedi, tmp.join('')]);
266
+ delete msgTemp[msgid];
267
+ }
268
+ return false;
269
+ }
270
+ cachedi++;
271
+ return true;
272
+ }
131
273
  switch (m.type) {
132
274
  case 'user':
133
275
  if (!userMap) userMap = Object.create(null);
@@ -136,7 +278,11 @@ function chat(title = '会话窗口') {
136
278
  break;
137
279
  }
138
280
  return false;
139
- }).map(m => JSAM.parse(encode62.packdecode(m))).filter(m => {
281
+ });
282
+ backEach(cached, function ([i, m]) {
283
+ msgs.splice(i, 0, m);
284
+ });
285
+ msgs = msgs.map(m => JSAM.parse(encode62.packdecode(m))).filter(m => {
140
286
  if (m.type === 'accept') {
141
287
  ps.pushFile(m.content);
142
288
  return false;
@@ -152,6 +298,30 @@ function chat(title = '会话窗口') {
152
298
  var msgMap = Object.create(null);
153
299
  for (var m of msgs) {
154
300
  var { sender } = m;
301
+ if (m.type === 'rtc-video') {
302
+ }
303
+ switch (m.type) {
304
+ case "rtc-close":
305
+ case "rtc-accept":
306
+ case "rtc-didate":
307
+ if (ps.calling) cast(ps.calling, [m.type, m.sender, m.content]);
308
+ continue;
309
+ case "didate":
310
+ var rtc = rtcMap[m.sender];
311
+ if (rtc) rtc.addDidate(m.content);
312
+ continue;
313
+ case "takeup":
314
+ var rtc = rtcMap[m.sender];
315
+ if (rtc) rtc.setAnswer(m.content);
316
+ continue;
317
+ case "rtc-video":
318
+ if (ps.calling) {
319
+ ps.send("rtc-close", i18n`正在通话中..`, m.sender);
320
+ continue;
321
+ }
322
+ ps.call(sender, m.content);
323
+ break;
324
+ }
155
325
  if (sender) {
156
326
  if (!msgMap[sender]) msgMap[sender] = [];
157
327
  msgMap[sender].push(m);
@@ -181,6 +351,25 @@ function chat(title = '会话窗口') {
181
351
  page.setAttribute('ng-class', "{showList:showList}");
182
352
  var fid = 0;
183
353
  var filesMap = Object.create(null);
354
+ function rtcMessage([type, data]) {
355
+ switch (type) {
356
+ case "offer":
357
+ ps.send('rtc-video', data);
358
+ break;
359
+ case "hangup":
360
+ ps.send('rtc-close', "", ps.remote);
361
+ break;
362
+ case "accept":
363
+ ps.send('rtc-accept', data, ps.remote);
364
+ break;
365
+ case "didate":
366
+ ps.send('rtc-didate', data, ps.remote);
367
+ break;
368
+ }
369
+ }
370
+ function send1(msg, sendto) {
371
+ cast(page, 'send', [sendto, msg]);
372
+ }
184
373
  var ps = {
185
374
  chat: zimoli$list,
186
375
  title,
@@ -188,9 +377,33 @@ function chat(title = '会话窗口') {
188
377
  showList: 0,
189
378
  users,
190
379
  text: '',
380
+ calling: null,
381
+ remote: null,
191
382
  localid,
192
383
  totalunread: 0,
193
384
  _user: null,
385
+ call(remote = this.user, offer) {
386
+ if (this.calling) return;
387
+ this.remote = isObject(remote) ? remote.id : remote;
388
+ if (typeof remote === 'string') {
389
+ for (var u of this.users) {
390
+ if (u.id === remote) {
391
+ remote = u;
392
+ break;
393
+ }
394
+ }
395
+ }
396
+ if (!remote) return;
397
+ var c = chatRtc(remote, this.localid, offer);
398
+ this.calling = c;
399
+
400
+ on('remove')(c, function () {
401
+ ps.calling = null;
402
+ ps.remote = null;
403
+ })
404
+ care(c, rtcMessage);
405
+ popup(c);
406
+ },
194
407
  fileIcon: shapes$file,
195
408
  set user(v) {
196
409
  if (!v.msglist) v.msglist = []
@@ -209,9 +422,14 @@ function chat(title = '会话窗口') {
209
422
  remove(page);
210
423
  },
211
424
  async pullFile(f) {
425
+ if (!f) return;
426
+ if (f.rtc && ChatRTC.enabled && window.showSaveFilePicker) {
427
+ return pullFileWithRTC(this, f);
428
+ }
212
429
  cast(page, 'pullfile', f);
213
430
  },
214
431
  async pushFile(msg) {
432
+ if (msg.offer && ChatRTC.enabled) return pushFileWithRTC(this, filesMap[msg.file], msg);
215
433
  cast(page, 'pushfile', [msg.channel, filesMap[msg.file]]);
216
434
  },
217
435
  async chooseFile() {
@@ -239,7 +457,7 @@ function chat(title = '会话窗口') {
239
457
  f.icon = canvas.toDataURL();
240
458
  URL.revokeObjectURL(u);
241
459
  }
242
- flist.push({ name: f.name, icon: f.icon, size: f.size, id: ++fid, mtime: +f.lastModified });
460
+ flist.push({ name: f.name, rtc: ChatRTC.enabled, icon: f.icon, size: f.size, id: ++fid, mtime: +f.lastModified });
243
461
  filesMap[fid] = f;
244
462
  }
245
463
  return this.send('file', flist);
@@ -252,22 +470,41 @@ function chat(title = '会话窗口') {
252
470
  body.resizeCell(textarea, 'top', textarea.clientHeight - targetHeight - 2);
253
471
  },
254
472
 
255
- send(type, content) {
256
- if (!content) return;
473
+ send(type, content, sendto = page.userid) {
257
474
  var msg = {
258
475
  type,
259
476
  sender: this.localid,
260
477
  content,
261
478
  };
262
479
  var data = JSAM.stringify(msg);
263
- if (data.length > 2000) {
480
+ data = encode62.packencode(data);
481
+ if (data.length > 16000) {
264
482
  return alert("信息太长,无法发送!");
265
483
  }
266
- if (this.user && this.user.id !== this.localid && type !== "accept") {
484
+ if (this.user && this.user.id !== this.localid) a: {
485
+ switch (type) {
486
+ case "accept":
487
+ case "didate":
488
+ case "takeup":
489
+ case "rtc-close":
490
+ case "rtc-accept":
491
+ case "rtc-didate":
492
+ break a;
493
+ }
494
+ if (!content) return;
267
495
  addToMsgList(this.msglist, [msg]);
268
496
  }
269
- data = encode62.packencode(data);
270
- cast(page, "send", data);
497
+ if (data.length > 2000) {
498
+ var count = Math.ceil(data.length / 2000);
499
+ var msgid = Date.now().toString().slice(-8) + "|" + count + "|";
500
+ for (var cx = 0, ci = 0, dx = data.length; cx < dx;) {
501
+ var d = data.slice(cx, cx += 2000);
502
+ send1("|" + msgid + ci++ + "|" + d, sendto);
503
+ }
504
+ }
505
+ else {
506
+ send1(data, sendto);
507
+ }
271
508
  this.body.lastElementChild.focus();
272
509
  this.text = '';
273
510
  }
@@ -166,6 +166,10 @@ msg {
166
166
  background-color: #77ddaa;
167
167
  color: #333;
168
168
  }
169
+
170
+ &[rtc] {
171
+ color: #0006;
172
+ }
169
173
  }
170
174
 
171
175
  msg[files] {
@@ -1,3 +1,9 @@
1
+ // 早上梦到我的小学同学,我知道那不是真的她。
2
+ // 因为真的她长什么样,我已十分模糊了。
3
+ // 梦中的她只是集合了我所有喜欢过的人的优点,然后被扣上了她的身份和名字。
4
+ // 然而,当她没带手机充电线,我想要把充电线借给她时,看到她那厌恶的眼神。
5
+ // 我知道,那是真的厌恶,不是调皮。
6
+ // 离开时,她为了避开我,还故意将她的物品托付给另一个男的。
1
7
  var styles = {
2
8
  blue: "#2a83cd",
3
9
  green: "#228B22",
@@ -17,7 +17,6 @@ var getOffset = function (e) {
17
17
  };
18
18
 
19
19
  var getMouse = function (e) {
20
- console.log(e)
21
20
  return [e.clientX, e.clientY];
22
21
  };
23
22
  var z;
@@ -63,6 +62,7 @@ function drag(target, initialEvent, preventOverflow, isMovingSource) {
63
62
  var saved_height = target.outerHeight;
64
63
  }
65
64
  var extraClones;
65
+ var tgz = target.style?.zIndex;
66
66
  var mousemove = function (event) {
67
67
  if (+event.moveLocked === 1) return;
68
68
  if (/resize/i.test(getComputedStyle(document.body).cursor)) return;
@@ -76,10 +76,12 @@ function drag(target, initialEvent, preventOverflow, isMovingSource) {
76
76
  z = zIndex(0) + 1;
77
77
  addZIndex(clone);
78
78
  appendChild(document.body, clone);
79
+ tgz = null;
79
80
  } else {
80
81
  clone = target;
81
82
  extraTargets = [];
82
83
  if (target.style) css(target, { zIndex: z });
84
+ else tgz = null;
83
85
  }
84
86
  drag.shadow = clone;
85
87
  var [clone_left, clone_top] = getOffset(clone);
@@ -112,6 +114,7 @@ function drag(target, initialEvent, preventOverflow, isMovingSource) {
112
114
  if (clone !== target) remove(clone), css(target, { opacity: saved_opacity, filter: saved_filter });
113
115
  remove(extraClones);
114
116
  extraTargets.map((target, cx) => css(target, extraStyles[cx]));
117
+ if (tgz != null) css(target, { zIndex: tgz });
115
118
  if (saved_delta.ing) target.removeAttribute("dragging"), dispatch("dragend", target);
116
119
  drag.target = null;
117
120
  saved_delta = null;
@@ -151,9 +154,11 @@ drag.on = function (target, actionTarget = target.dragTarget) {
151
154
  var _mousedrag = mousedrag;
152
155
  var _touchdrag = touchdrag;
153
156
  }
154
- onmousedown(actionTarget || target, setZIndex);
155
- ontouchstart(actionTarget || target, setZIndex);
156
- on("drop")(actionTarget || target, setZIndex);
157
+ if (actionTarget !== false) {
158
+ onmousedown(actionTarget || target, setZIndex);
159
+ ontouchstart(actionTarget || target, setZIndex);
160
+ on("drop")(actionTarget || target, setZIndex);
161
+ }
157
162
  onmousedown(target, _mousedrag);
158
163
  ontouchstart(target, _touchdrag);
159
164
  };
@@ -13,7 +13,7 @@ var fullscreen = {
13
13
  return hasTarget();
14
14
  }(),
15
15
  is() {
16
- if(/Android|iPad|iPhone/i.test(navigator.userAgent)){
16
+ if (/Android|iPad|iPhone/i.test(navigator.userAgent)) {
17
17
  return hasTarget();
18
18
  }
19
19
  if (/chrome/i.test(navigator.userAgent)) {//webkit
@@ -31,6 +31,12 @@ var fullscreen = {
31
31
  exec(element) {
32
32
  requestFullScreen(element);
33
33
  },
34
+ open() {
35
+ if (!this.is()) this.change();
36
+ },
37
+ close() {
38
+ this.exit();
39
+ },
34
40
  change() {
35
41
  if (this.is()) this.exit(alert);
36
42
  else this.exec(arguments[0] || document.documentElement);
@@ -94,7 +94,7 @@ function move(offsetLeft, offsetTop, preventOverflow, keepOriginIfPosible) {
94
94
  }
95
95
  return [offsetLeft, offsetTop];
96
96
  }
97
- move.getPosition = function (target) {
97
+ var getPosition = move.getPosition = function (target) {
98
98
  var {
99
99
  offsetLeft,
100
100
  offsetTop,
@@ -132,7 +132,11 @@ var setPosition = move.setPosition = function (target, [x, y]) {
132
132
  var offsetTop = getOffsetLeft(y, offsetHeight, clientHeight);
133
133
  move.call(target, offsetLeft, offsetTop, undefined, false);
134
134
  };
135
-
135
+ var setSize = move.setSize = function (target, [w, h]) {
136
+ var p = getPosition(target);
137
+ css(target, { width: w, height: h });
138
+ setPosition(target, p);
139
+ };
136
140
  var fixPosition = move.fixPosition = function (target) {
137
141
  var computed = getComputedStyle(target);
138
142
  var {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efront",
3
- "version": "4.26.2",
3
+ "version": "4.28.0",
4
4
  "description": "一个开发环境,提供一种自由的前端开发模式,也可作为辅助工具使用。",
5
5
  "main": "public/efront.js",
6
6
  "directories": {