From dddcd10e290d31f1f7fcf2b01846a413f705e0ca Mon Sep 17 00:00:00 2001 From: Rui Carmo Date: Tue, 12 Mar 2024 09:33:00 +0000 Subject: [PATCH] merge --- client/index.html | 122 ++++++++++++++----------- client/scripts/jquery.min.js | 2 + client/scripts/network.js | 170 ++++++++++++++++++++++++++--------- client/scripts/ui.js | 164 ++++++++++++++++++++++++++++----- client/styles.css | 11 ++- package.json | 4 +- 6 files changed, 355 insertions(+), 118 deletions(-) create mode 100644 client/scripts/jquery.min.js diff --git a/client/index.html b/client/index.html index c7474028..7deb0698 100644 --- a/client/index.html +++ b/client/index.html @@ -10,16 +10,12 @@ - + - - - - @@ -48,7 +44,6 @@ - @@ -72,26 +66,31 @@

Open Snapdrop on other devices to send files

-
-
You can be discovered by everyone on this network
+
+ + +
+ +
+
+
You can be discovered by everyone on this network
-

File Received

-
Filename
+

File Received

+

From:

+
Filename
- +
- Save - + Save +
@@ -101,11 +100,11 @@

File Received

-

Send a Message

-
+

Send a Message

+
- - Cancel + + Cancel
@@ -115,18 +114,50 @@

Send a Message

-

Message Received

+

Message Received

+

From:

- - + +
+ + + + + +

You are known as

+
+
+ + Cancel +
+
+
+ +
+ + +
+ + +

Room Number(6 Digits)

+
Note: No need to input to create a room.
+ +
+ + Cancel +
+
+
+
+
- File Transfer Completed + File Transfer Completed
@@ -138,39 +169,29 @@

Message Received

- +

Snapdrop

-
Local file transfers.
+
- + -
@@ -189,9 +210,6 @@

Snapdrop

- - - @@ -204,9 +222,7 @@

Snapdrop

- - - + @@ -217,10 +233,16 @@

Snapdrop

- + + + + + + + @@ -248,4 +270,4 @@

Snapdrop works only with JavaScript

} - + \ No newline at end of file diff --git a/client/scripts/jquery.min.js b/client/scripts/jquery.min.js new file mode 100644 index 00000000..7f37b5d9 --- /dev/null +++ b/client/scripts/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 console.log('WS: server connected'); ws.onmessage = e => this._onMessage(e.data); @@ -24,10 +29,10 @@ class ServerConnection { _onMessage(msg) { msg = JSON.parse(msg); - console.log('WS:', msg); switch (msg.type) { case 'peers': - Events.fire('peers', msg.peers); + // Events.fire('peers', msg.peers); + Events.fire('peers', msg); break; case 'peer-joined': Events.fire('peer-joined', msg.peer); @@ -44,6 +49,9 @@ class ServerConnection { case 'display-name': Events.fire('display-name', msg); break; + case 'peer-modify-name': + Events.fire('peer-modify-name', msg.peer); + break; default: console.error('WS: unkown message type', msg); } @@ -91,45 +99,76 @@ class ServerConnection { class Peer { - constructor(serverConnection, peerId) { + constructor(serverConnection, peerId, peerDisplayname) { this._server = serverConnection; this._peerId = peerId; + this._peerDisplayname = peerDisplayname this._filesQueue = []; this._busy = false; + this._cancel = false; } sendJSON(message) { this._send(JSON.stringify(message)); } - sendFiles(files) { + sendFiles(files,sender) { for (let i = 0; i < files.length; i++) { this._filesQueue.push(files[i]); } if (this._busy) return; - this._dequeueFile(); + this._dequeueFile(sender); } - _dequeueFile() { - if (!this._filesQueue.length) return; - this._busy = true; + _dequeueFile(sender) { + if (!this._filesQueue.length && this._cancel) { + Events.fire('close-progress',{sender: this._peerId}); + this._sendCancelFile(this._peerId) + return + } + this._sendClearCancel() + Events.fire('clear-cancel', {sender: this._peerId}); + this._cancel = false const file = this._filesQueue.shift(); - this._sendFile(file); + this._sendFile(file,sender); + } + + _sendCancelFile(sender) { + this.sendJSON({ + type: 'cancel-send', + sender: sender + }); } - _sendFile(file) { + _sendClearCancel() { + this.sendJSON({ + type: 'm-clear-cancel' + }); + } + + _sendFile(file, sender) { + if(!file) return this.sendJSON({ type: 'header', name: file.name, mime: file.type, - size: file.size + size: file.size, + sender: sender }); this._chunker = new FileChunker(file, - chunk => this._send(chunk), + chunk => { + this._send(chunk) + }, offset => this._onPartitionEnd(offset)); this._chunker.nextPartition(); } + cancelSend() { + this._cancel = true + this._busy = false; + this._dequeueFile(); + } + _onPartitionEnd(offset) { this.sendJSON({ type: 'partition', offset: offset }); } @@ -139,7 +178,7 @@ class Peer { } _sendNextPartition() { - if (!this._chunker || this._chunker.isFileEnd()) return; + if (!this._chunker || this._chunker.isFileEnd() || this._cancel) return; this._chunker.nextPartition(); } @@ -152,11 +191,15 @@ class Peer { this._onChunkReceived(message); return; } + let sender = ''; message = JSON.parse(message); console.log('RTC:', message); + if(message.sender){ + sender = message.sender + } switch (message.type) { case 'header': - this._onFileHeader(message); + this._onFileHeader(message, sender); break; case 'partition': this._onReceivedPartitionEnd(message); @@ -168,10 +211,16 @@ class Peer { this._onDownloadProgress(message.progress); break; case 'transfer-complete': - this._onTransferCompleted(); + this._onTransferCompleted(sender); + break; + case 'cancel-send': + Events.fire('close-progress', {recipient: this._peerId}); + break; + case 'm-clear-cancel': + Events.fire('clear-cancel', {recipient: this._peerId}); break; case 'text': - this._onTextReceived(message); + this._onTextReceived(message,sender); break; } } @@ -182,7 +231,7 @@ class Peer { name: header.name, mime: header.mime, size: header.size - }, file => this._onFileReceived(file)); + }, file => this._onFileReceived(file, header.sender)); } _onChunkReceived(chunk) { @@ -202,34 +251,37 @@ class Peer { Events.fire('file-progress', { sender: this._peerId, progress: progress }); } - _onFileReceived(proxyFile) { - Events.fire('file-received', proxyFile); - this.sendJSON({ type: 'transfer-complete' }); + _onFileReceived(proxyFile, sender) { + Events.fire('file-received', {file:proxyFile, sender:sender}); + Events.fire('clear-cancel', {recipient: this._peerId}); + this.sendJSON({ type: 'transfer-complete', sender: sender}); } - _onTransferCompleted() { + _onTransferCompleted(sender) { this._onDownloadProgress(1); this._reader = null; this._busy = false; - this._dequeueFile(); + this._dequeueFile(sender); Events.fire('notify-user', 'File transfer completed.'); } - sendText(text) { + sendText(text,sender) { const unescaped = btoa(unescape(encodeURIComponent(text))); - this.sendJSON({ type: 'text', text: unescaped }); + const unescapedSender = btoa(unescape(encodeURIComponent(sender))); + this.sendJSON({ type: 'text', text: unescaped, sender: unescapedSender}); } - _onTextReceived(message) { + _onTextReceived(message,sender) { const escaped = decodeURIComponent(escape(atob(message.text))); - Events.fire('text-received', { text: escaped, sender: this._peerId }); + const escapedSender = decodeURIComponent(escape(atob(sender))); + Events.fire('text-received', { text: escaped, sender: escapedSender }); } } class RTCPeer extends Peer { - constructor(serverConnection, peerId) { - super(serverConnection, peerId); + constructor(serverConnection, peerId, peerDisplayname) { + super(serverConnection, peerId, peerDisplayname); if (!peerId) return; // we will listen for a caller this._connect(peerId, true); } @@ -369,6 +421,8 @@ class PeersManager { Events.on('files-selected', e => this._onFilesSelected(e.detail)); Events.on('send-text', e => this._onSendText(e.detail)); Events.on('peer-left', e => this._onPeerLeft(e.detail)); + Events.on('peer-name',e => this._onModifyName(e.detail)) + Events.on('cancel-send',e => this._onCancelSend(e.detail)) } _onMessage(message) { @@ -378,14 +432,18 @@ class PeersManager { this.peers[message.sender].onServerMessage(message); } - _onPeers(peers) { + _onPeers(msg) { + const peers = msg.peers; peers.forEach(peer => { if (this.peers[peer.id]) { - this.peers[peer.id].refresh(); - return; + // this.peers[peer.id].refresh(); + // return; + + // Delete conn, will re-create the conn later... + this._onPeerLeft(peer.id) } if (window.isRtcSupported && peer.rtcSupported) { - this.peers[peer.id] = new RTCPeer(this._server, peer.id); + this.peers[peer.id] = new RTCPeer(this._server, peer.id, peer.name.displayName); } else { this.peers[peer.id] = new WSPeer(this._server, peer.id); } @@ -397,11 +455,15 @@ class PeersManager { } _onFilesSelected(message) { - this.peers[message.to].sendFiles(message.files); + this.peers[message.to].sendFiles(message.files,message.sender); } _onSendText(message) { - this.peers[message.to].sendText(message.text); + this.peers[message.to].sendText(message.text,message.from); + } + + _onCancelSend(message) { + this.peers[message.to].cancelSend() } _onPeerLeft(peerId) { @@ -411,9 +473,20 @@ class PeersManager { peer._peer.close(); } + //修改peer的名字 + _onModifyName(name) { + const message = {displayName: name} + this._server.send(message) + } + + //取消发送 + _onCancelSend(message) { + this.peers[message.to].cancelSend() + } + } -class WSPeer { +class WSPeer extends Peer { _send(message) { message.to = this._peerId; this._server.send(message); @@ -522,7 +595,24 @@ class Events { RTCPeer.config = { 'sdpSemantics': 'unified-plan', - 'iceServers': [{ - urls: 'stun:stun.l.google.com:19302' - }] -} + 'iceServers': [ + { + urls: 'stun:stun.l.google.com:19302', + }, + { + urls: "turn:openrelay.metered.ca:80", + username: "openrelayproject", + credential: "openrelayproject", + }, + { + urls: "turn:openrelay.metered.ca:443", + username: "openrelayproject", + credential: "openrelayproject", + }, + { + urls: "turn:openrelay.metered.ca:443?transport=tcp", + username: "openrelayproject", + credential: "openrelayproject", + }, + ], +} \ No newline at end of file diff --git a/client/scripts/ui.js b/client/scripts/ui.js index 1794aeb8..01c73239 100644 --- a/client/scripts/ui.js +++ b/client/scripts/ui.js @@ -9,10 +9,19 @@ window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; Events.on('display-name', e => { const me = e.detail.message; const $displayName = $('displayName') - $displayName.textContent = 'You are known as ' + me.displayName; + $displayName.textContent = me.displayName $displayName.title = me.deviceName; }); +Events.on('edit-name-commit',e => { + const name = e.detail.text + const $displayName = $('displayName') + $displayName.textContent = name + Events.fire('peer-name',name) +}) +$('editNameBt').addEventListener('click',e => { + Events.fire('edit-name') +}) class PeersUI { constructor() { @@ -21,18 +30,31 @@ class PeersUI { Events.on('peers', e => this._onPeers(e.detail)); Events.on('file-progress', e => this._onFileProgress(e.detail)); Events.on('paste', e => this._onPaste(e)); + Events.on('peer-modify-name', e => this._onPeerModifyName(e.detail)); + Events.on('close-progress',e => this._closeProgress(e.detail)); + Events.on('clear-cancel',e => this._clearCancel(e.detail)) } - _onPeerJoined(peer) { + _onPeerJoined(peer, currentPeerInfo) { + if(JSON.stringify(peer.name) == JSON.stringify(currentPeerInfo)){ + Events.fire('notify-user', jQuery.i18n.prop('same_notice')); + } if ($(peer.id)) return; // peer already exists const peerUI = new PeerUI(peer); $$('x-peers').appendChild(peerUI.$el); setTimeout(e => window.animateBackground(false), 1750); // Stop animation } - _onPeers(peers) { + _onPeerModifyName(peer) { + let el = $(peer.id) + el.querySelector('.name').textContent = peer.name.displayName; + } + + _onPeers(msg) { + const currentPeerInfo = msg.currentPeerInfo + const peers = msg.peers this._clearPeers(); - peers.forEach(peer => this._onPeerJoined(peer)); + peers.forEach(peer => this._onPeerJoined(peer,currentPeerInfo)); } _onPeerLeft(peerId) { @@ -48,6 +70,20 @@ class PeersUI { $peer.ui.setProgress(progress.progress); } + _closeProgress(message){ + const peerId = message.sender || message.recipient; + const $peer = $(peerId); + if (!$peer) return; + $peer.ui.closeProgress(); + } + + _clearCancel(message) { + const peerId = message.sender || message.recipient; + const $peer = $(peerId); + if (!$peer) return; + $peer.ui.clearCancel(); + } + _clearPeers() { const $peers = $$('x-peers').innerHTML = ''; } @@ -64,7 +100,8 @@ class PeersUI { if (files.length > 0 && peers.length === 1) { Events.fire('files-selected', { files: files, - to: $$('x-peer').id + to: $$('x-peer').id, + sender: $('displayName').innerText }); } } @@ -86,6 +123,7 @@ class PeerUI {
+ ` } @@ -93,6 +131,7 @@ class PeerUI { this._peer = peer; this._initDom(); this._bindListeners(this.$el); + this._hasCancel = false; } _initDom() { @@ -116,6 +155,7 @@ class PeerUI { el.addEventListener('contextmenu', e => this._onRightClick(e)); el.addEventListener('touchstart', e => this._onTouchStart(e)); el.addEventListener('touchend', e => this._onTouchEnd(e)); + el.querySelector('.cancel-transfer').addEventListener('click',e => this._cancelTransfer(e)) // prevent browser's default file drop behavior Events.on('dragover', e => e.preventDefault()); Events.on('drop', e => e.preventDefault()); @@ -141,18 +181,23 @@ class PeerUI { } _onFilesSelected(e) { + this._hasCancel = false; const $input = e.target; const files = $input.files; + this.$el.querySelector('.cancel-transfer').style.display = "block" Events.fire('files-selected', { files: files, - to: this._peer.id + to: this._peer.id, + sender: $('displayName').innerText }); $input.value = null; // reset input } setProgress(progress) { + if(this._hasCancel) return if (progress > 0) { this.$el.setAttribute('transfer', '1'); + this.$el.querySelector('.cancel-transfer').style.display = "block"; } if (progress > 0.5) { this.$progress.classList.add('over50'); @@ -164,15 +209,26 @@ class PeerUI { if (progress >= 1) { this.setProgress(0); this.$el.removeAttribute('transfer'); + this.$el.querySelector('.cancel-transfer').style.display = "none"; } } + clearCancel() { + this._hasCancel = false + } + closeProgress() { + this.setProgress(0); + this.$el.removeAttribute('transfer'); + this.$el.querySelector('.cancel-transfer').style.display = "none" + this._hasCancel = true + } _onDrop(e) { e.preventDefault(); const files = e.dataTransfer.files; Events.fire('files-selected', { files: files, - to: this._peer.id + to: this._peer.id, + sender: $('displayName').innerText }); this._onDragEnd(); } @@ -203,6 +259,12 @@ class PeerUI { Events.fire('text-recipient', this._peer.id); } } + + _cancelTransfer(e) { + Events.fire('cancel-send', { + to: this._peer.id + }); + } } @@ -230,21 +292,22 @@ class ReceiveDialog extends Dialog { constructor() { super('receiveDialog'); Events.on('file-received', e => { - this._nextFile(e.detail); + this._nextFile(e.detail.file, e.detail.sender); window.blop.play(); }); this._filesQueue = []; + this.$previewBox = this.$el.querySelector('.preview') } - _nextFile(nextFile) { + _nextFile(nextFile, sender) { if (nextFile) this._filesQueue.push(nextFile); if (this._busy) return; this._busy = true; const file = this._filesQueue.shift(); - this._displayFile(file); + this._displayFile(file, sender); } - _dequeueFile() { + _dequeueFile(file,sender) { if (!this._filesQueue.length) { // nothing to do this._busy = false; return; @@ -252,11 +315,11 @@ class ReceiveDialog extends Dialog { // dequeue next file setTimeout(_ => { this._busy = false; - this._nextFile(); + this._nextFile(undefined,sender); }, 300); } - _displayFile(file) { + _displayFile(file, sender) { const $a = this.$el.querySelector('#download'); const url = URL.createObjectURL(file.blob); $a.href = url; @@ -266,14 +329,27 @@ class ReceiveDialog extends Dialog { $a.click() return } - if(file.mime.split('/')[0] === 'image'){ - console.log('the file is image'); - this.$el.querySelector('.preview').style.visibility = 'inherit'; - this.$el.querySelector("#img-preview").src = url; + let mime = file.mime.split('/')[0], + previewElement = { + image: 'img', + audio: 'audio', + video: 'video' + } + if(Object.keys(previewElement).indexOf(mime) !== -1){ + console.log('the file is previewable'); + let element = document.createElement(previewElement[mime]); + element.src = url; + element.controls = true; + element.classList = 'element-preview' + + this.$previewBox.style.visibility = 'inherit'; + this.$previewBox.appendChild(element) } + this.$el.querySelector('#fileName').textContent = file.name; this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size); + $('fileSender').innerHTML = sender; this.show(); if (window.isDownloadSupported) return; @@ -297,8 +373,8 @@ class ReceiveDialog extends Dialog { } hide() { - this.$el.querySelector('.preview').style.visibility = 'hidden'; - this.$el.querySelector("#img-preview").src = ""; + this.$previewBox.style.visibility = 'hidden'; + this.$previewBox.innerHTML = ''; super.hide(); this._dequeueFile(); } @@ -309,6 +385,32 @@ class ReceiveDialog extends Dialog { } } +class EditNameDialog extends Dialog { + constructor() { + super('editNameDialog'); + this.$nameText = this.$el.querySelector('#nameTextInput') + Events.on('edit-name', e => this._onOpenEditName(e.detail)) + const button = this.$el.querySelector('form') + button.addEventListener('submit', e => { + this._sure(e) + }); + + } + _onOpenEditName(){ + this.$nameText.innerHTML = "" + this.show() + } + _sure(e) { + e.preventDefault(); + if(this.$nameText.innerText == '') { + return + } + localStorage.setItem('displayname',this.$nameText.innerText) + Events.fire('edit-name-commit', { + text: this.$nameText.innerText + }); + } +} class SendTextDialog extends Dialog { constructor() { @@ -317,11 +419,21 @@ class SendTextDialog extends Dialog { this.$text = this.$el.querySelector('#textInput'); const button = this.$el.querySelector('form'); button.addEventListener('submit', e => this._send(e)); + this.$text.addEventListener('paste', e => this._onInputPaste(e)) + } + _onInputPaste(e) { + const files = e.clipboardData.files || e.clipboardData.items + .filter(i => i.type.indexOf('image') > -1) + .map(i => i.getAsFile()); + if(!files.length) { + return + } + this.hide() } - _onRecipient(recipient) { this._recipient = recipient; this._handleShareTargetText(); + this.$text.innerHTML = '' this.show(); const range = document.createRange(); @@ -341,9 +453,11 @@ class SendTextDialog extends Dialog { _send(e) { e.preventDefault(); + let displayName = $('displayName').innerText Events.fire('send-text', { to: this._recipient, - text: this.$text.innerText + text: this.$text.innerText, + from: displayName }); } } @@ -358,6 +472,7 @@ class ReceiveTextDialog extends Dialog { } _onText(e) { + $('sender').innerHTML = e.sender this.$text.innerHTML = ''; const text = e.text; if (isURL(text)) { @@ -406,7 +521,7 @@ class Notifications { this.$button.addEventListener('click', e => this._requestPermission()); } Events.on('text-received', e => this._messageNotification(e.detail.text)); - Events.on('file-received', e => this._downloadNotification(e.detail.name)); + Events.on('file-received', e => this._downloadNotification(e.detail.file.name)); } _requestPermission() { @@ -539,6 +654,7 @@ class Snapdrop { Events.on('load', e => { const receiveDialog = new ReceiveDialog(); const sendTextDialog = new SendTextDialog(); + const editNameDialog = new EditNameDialog(); const receiveTextDialog = new ReceiveTextDialog(); const toast = new Toast(); const notifications = new Notifications(); @@ -553,7 +669,7 @@ const snapdrop = new Snapdrop(); if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/service-worker.js') + navigator.serviceWorker.register('service-worker.js') .then(serviceWorker => { console.log('Service Worker registered'); window.serviceWorker = serviceWorker @@ -646,4 +762,4 @@ document.body.onclick = e => { // safari hack to fix audio document.body.onclick = null; if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; blop.play(); -} +} \ No newline at end of file diff --git a/client/styles.css b/client/styles.css index 0a9aca98..8f5c0325 100644 --- a/client/styles.css +++ b/client/styles.css @@ -310,6 +310,12 @@ footer .logo { --icon-size: 80px; margin-bottom: 8px; color: var(--primary-color); + position: relative; +} + +footer .logo .logo-icon { + width: 100%; + height: 100% } footer .font-body2 { @@ -717,8 +723,9 @@ x-dialog x-paper { color: var(--text-color); background-color: var(--bg-color-secondary); } -/* Image Preview */ -#img-preview{ +/* Media Preview */ +.element-preview { + max-width: 100%; max-height: 50vh; margin: auto; display: block; diff --git a/package.json b/package.json index 2434dac5..f5369f18 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "author": "", "license": "ISC", "dependencies": { - "ua-parser-js": "^1.0.32", + "ua-parser-js": "^1.0.37", "unique-names-generator": "^4.7.1", "express": "^4.18.2", - "ws": "^8.9.0" + "ws": "^8.16.0" } }