You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

390 lines
8.6 KiB

  1. define([
  2. 'js/system/events',
  3. 'html!ui/templates/messages/template',
  4. 'html!ui/templates/messages/tplTab',
  5. 'css!ui/templates/messages/styles',
  6. 'ui/templates/messages/mobile',
  7. 'ui/templates/messages/channelPicker',
  8. 'js/input',
  9. 'js/system/client',
  10. 'js/config'
  11. ], function (
  12. events,
  13. template,
  14. tplTab,
  15. styles,
  16. messagesMobile,
  17. channelPicker,
  18. input,
  19. client,
  20. config
  21. ) {
  22. return {
  23. tpl: template,
  24. maxChatLength: 255,
  25. hoverItem: null,
  26. hoverFilter: false,
  27. currentChannel: 'global',
  28. currentSubChannel: null,
  29. privateChannels: [],
  30. lastPrivateChannel: null,
  31. customChannels: [],
  32. lastCustomChannel: null,
  33. postRender: function () {
  34. [
  35. 'onGetMessages',
  36. 'onDoWhisper',
  37. 'onJoinChannel',
  38. 'onLeaveChannel',
  39. 'onClickFilter',
  40. 'onGetCustomChatChannels',
  41. 'onKeyDown',
  42. 'onKeyUp'
  43. ].forEach(e => this.onEvent(e, this[e].bind(this)));
  44. this.find('.filter:not(.channel)').on('click', this.onClickFilter.bind(this));
  45. channelPicker.init.call(this);
  46. if (isMobile)
  47. messagesMobile.init.call(this);
  48. else {
  49. this.find('input')
  50. .on('keydown', this.sendChat.bind(this))
  51. .on('input', this.enforceMaxMsgLength.bind(this))
  52. .on('blur', this.toggle.bind(this, false, true));
  53. }
  54. },
  55. update: function () {
  56. if (isMobile)
  57. return;
  58. if (this.el.hasClass('typing'))
  59. return;
  60. const time = new Date();
  61. const hours = time.getUTCHours().toString().padStart(2, 0);
  62. const minutes = time.getUTCMinutes().toString().padStart(2, 0);
  63. let elTime = this.find('.time');
  64. const timeString = `[ ${hours}:${minutes} ]`;
  65. if (elTime.html() !== timeString)
  66. elTime.html(timeString);
  67. },
  68. enforceMaxMsgLength: function () {
  69. let textbox = this.find('input');
  70. let val = textbox.val();
  71. if (val.length <= this.maxChatLength)
  72. return;
  73. val = val.substr(0, this.maxChatLength);
  74. textbox.val(val);
  75. },
  76. onGetCustomChatChannels: function (channels) {
  77. channels.forEach(c => this.onJoinChannel(c));
  78. },
  79. onJoinChannel: function (channel) {
  80. const container = this.find('.filters');
  81. const channelName = channel.trim();
  82. this.customChannels.spliceWhere(c => c === channel);
  83. this.find(`[filter="${channelName}"]`).remove();
  84. this.customChannels.push(channel);
  85. $(tplTab)
  86. .appendTo(container)
  87. .addClass('channel')
  88. .attr('filter', channelName)
  89. .html(channelName)
  90. .on('mouseover', this.onFilterHover.bind(this, true))
  91. .on('mouseleave', this.onFilterHover.bind(this, false))
  92. .on('click', this.onClickFilter.bind(this));
  93. },
  94. onLeaveChannel: function (channel) {
  95. this.customChannels.spliceWhere(c => c === channel);
  96. this.find(`.filters div[filter="${channel}"]`).remove();
  97. },
  98. onFilterHover: function (hover) {
  99. this.hoverFilter = hover;
  100. },
  101. onClickFilter: function (e) {
  102. let el = $(e.target);
  103. el.toggleClass('active');
  104. let filter = el.attr('filter');
  105. let method = (el.hasClass('active') ? 'show' : 'hide');
  106. if (method === 'show')
  107. this.find('.list').addClass(filter);
  108. else
  109. this.find('.list').removeClass(filter);
  110. if (el.hasClass('channel'))
  111. this.find('.list .' + filter)[method]();
  112. },
  113. onKeyDown: function (key) {
  114. if (key === 'enter')
  115. this.toggle(true);
  116. else if (key === 'shift')
  117. this.showItemTooltip();
  118. else if (key === 'esc' && this.el.hasClass('typing'))
  119. this.toggle(false);
  120. },
  121. onKeyUp: function (key) {
  122. if (key === 'shift')
  123. this.showItemTooltip();
  124. },
  125. onDoWhisper: function (charName) {
  126. this.toggle(true);
  127. this.currentChannel = 'direct';
  128. this.currentSubChannel = charName;
  129. this.find('.channelPicker').html(charName);
  130. const elInput = this.find('input')
  131. .val('message')
  132. .focus();
  133. elInput[0].setSelectionRange(0, 7);
  134. },
  135. //Remember private and custom channels used
  136. trackHistory: function (msg) {
  137. const { subType, source, target, channel } = msg;
  138. if (subType === 'privateIn' || subType === 'privateOut') {
  139. const list = this.privateChannels;
  140. list.spliceWhere(l => l === source || l === target);
  141. //Newest sources are always at the end
  142. list.push(source || target);
  143. if (list.length > 5)
  144. list.splice(0, list.length - 5);
  145. if (subType === 'privateOut' && config.rememberChatChannel)
  146. this.lastPrivateChannel = target;
  147. } else if (subType === 'custom' && config.rememberChatChannel)
  148. this.lastCustomChannel = channel;
  149. },
  150. onGetMessages: function (e) {
  151. if (!window.player)
  152. return;
  153. let messages = e.messages;
  154. if (!messages.length)
  155. messages = [messages];
  156. let container = this.find('.list');
  157. const [ { scrollHeight, clientHeight, scrollTop } ] = container;
  158. const isAtMaxScroll = scrollTop >= (scrollHeight - clientHeight);
  159. messages.forEach(m => {
  160. this.trackHistory(m);
  161. let message = m.message;
  162. if (m.source) {
  163. if (window.player.social.isPlayerBlocked(m.source))
  164. return;
  165. }
  166. if (m.item) {
  167. let source = message.split(':')[0];
  168. message = source + ': <span class="q' + (m.item.quality || 0) + '">' + message.replace(source + ': ', '') + '</span>';
  169. }
  170. let el = $('<div class="list-message ' + m.class + '">' + message + '</div>')
  171. .appendTo(container);
  172. if (m.has('type'))
  173. el.addClass(m.type);
  174. else
  175. el.addClass('info');
  176. if (m.has('channel'))
  177. el.addClass(m.channel);
  178. if (m.item) {
  179. let clickHander = () => {};
  180. let moveHandler = this.showItemTooltip.bind(this, el, m.item);
  181. if (isMobile)
  182. [clickHander, moveHandler] = [moveHandler, clickHander];
  183. el.find('span')
  184. .on('mousemove', moveHandler)
  185. .on('mousedown', clickHander)
  186. .on('mouseleave', this.hideItemTooltip.bind(this));
  187. }
  188. if (m.type) {
  189. const isChannel = m?.subType === 'custom';
  190. if (isChannel) {
  191. if (this.find('.filter[filter="' + m.channel + '"]').hasClass('active'))
  192. el.show();
  193. else
  194. el.hide();
  195. }
  196. if (isMobile && ['loot', 'info'].indexOf(m.type) !== -1) {
  197. events.emit('onGetAnnouncement', {
  198. msg: m.message
  199. });
  200. }
  201. }
  202. });
  203. if (!this.el.hasClass('typing') || isAtMaxScroll)
  204. container.scrollTop(9999999);
  205. },
  206. hideItemTooltip: function () {
  207. if (this.dragEl) {
  208. this.hoverCell = null;
  209. return;
  210. }
  211. events.emit('onHideItemTooltip', this.hoverItem);
  212. this.hoverItem = null;
  213. },
  214. showItemTooltip: function (el, item, e) {
  215. if (item)
  216. this.hoverItem = item;
  217. else
  218. item = this.hoverItem;
  219. if (!item)
  220. return;
  221. let ttPos = null;
  222. if (el) {
  223. ttPos = {
  224. x: ~~(e.clientX + 32),
  225. y: ~~(e.clientY)
  226. };
  227. }
  228. let bottomAlign = !isMobile;
  229. events.emit('onShowItemTooltip', item, ttPos, true, bottomAlign);
  230. },
  231. toggle: function (show, isFake, e) {
  232. if (isFake && this.hoverFilter)
  233. return;
  234. input.resetKeys();
  235. this.el.removeClass('typing');
  236. this.el.find('.main').removeClass('hasBorderShadow');
  237. let textbox = this.find('input');
  238. if (show) {
  239. this.el.addClass('typing');
  240. this.el.find('.main').addClass('hasBorderShadow');
  241. if (!config.rememberChatChannel) {
  242. this.currentChannel = 'global';
  243. this.currentSubChannel = null;
  244. }
  245. this.find('.channelPicker').html(this.currentSubChannel || this.currentChannel);
  246. textbox.focus();
  247. this.find('.list').scrollTop(9999999);
  248. } else {
  249. this.find('.channelOptions').removeClass('active');
  250. textbox.val('');
  251. this.el.removeClass('picking');
  252. if (['direct', 'custom'].includes(this.currentChannel) && (!this.currentSubChannel || ['join new', 'leave'].includes(this.currentSubChannel))) {
  253. this.currentSubChannel = null;
  254. this.currentChannel = 'global';
  255. }
  256. }
  257. if (e)
  258. e.stopPropagation();
  259. },
  260. sendChat: function (e) {
  261. let textbox = this.find('input');
  262. let msgConfig = {
  263. success: true,
  264. message: textbox.val(),
  265. event: e,
  266. cancel: false
  267. };
  268. this.processChat(msgConfig);
  269. if (msgConfig.cancel || this.el.hasClass('picking'))
  270. return false;
  271. const { which: charCode } = e;
  272. if ([9, 27].includes(charCode) || charCode !== 13) {
  273. if (charCode === 9) {
  274. e.preventDefault();
  275. textbox.val(`${textbox.val()} `);
  276. } else if (charCode === 27)
  277. this.toggle(false);
  278. return;
  279. }
  280. events.emit('onBeforeChat', msgConfig);
  281. let val = msgConfig.message
  282. .split('<').join('&lt;')
  283. .split('>').join('&gt;');
  284. if (!msgConfig.success) {
  285. this.toggle(false);
  286. return;
  287. }
  288. if (val.trim() === '') {
  289. this.toggle(false);
  290. return;
  291. }
  292. client.request({
  293. cpn: 'social',
  294. method: 'chat',
  295. data: {
  296. message: val,
  297. type: this.currentChannel,
  298. subType: this.currentSubChannel
  299. }
  300. });
  301. this.toggle();
  302. }
  303. };
  304. });