diff --git a/assets/js/api.js b/assets/js/api.js --- a/assets/js/api.js +++ b/assets/js/api.js @@ -2,8 +2,10 @@ import {Socket, LongPoll, Presence} from "phoenix" import UserListWidget from "./widgets/userlist" import UserStatusWidget from "./widgets/userstatus" +import ChannelListWidget from "./widgets/channellist" import ChatInputWidget from "./widgets/chatinput" import ChatRoomWidget from "./widgets/chatroom" +import ChatDetailsWidget from "./widgets/chatdetails" class KolabChat { @@ -16,6 +18,8 @@ * - userStatusElement: Id of HTML element where to put users status widget * - chatInputElement: Id of HTML element which is a text chat input * - chatRoomElement: Id of HTML element where to put text conversation + * - chatDetailsElement: Id of HTML element where to put chat room details widget + * - channelListElement: Id of HTML element where to put channellist widget */ constructor(config) { @@ -64,6 +68,7 @@ if (this.config.userListElement && $('#' + this.config.userListElement).length) { config = { username: this.username, + title: true, openChat: (e, user) => { this.openChat(e, user) } } this.userListWidget = new UserListWidget(this.config.userListElement, config) @@ -77,6 +82,23 @@ this.userStatusWidget = new UserStatusWidget(this.config.userStatusElement, config) } + if (this.config.channelListElement && $('#' + this.config.channelListElement).length) { + config = { + username: this.username, + title: true, + openChat: (e, roomId) => { this.openExistingChat(roomId) }, + createChat: (e) => { this.openChat(e); } + } + this.channelListWidget = new ChannelListWidget(this.config.channelListElement, config) + } + + if (this.config.chatDetailsElement && $('#' + this.config.chatDetailsElement).length) { + config = { + submit: (e, data) => { this.updateChatDetails(data); } + } + this.chatDetailsWidget = new ChatDetailsWidget(this.config.chatDetailsElement, config) + } + if (this.config.chatRoomElement && $('#' + this.config.chatRoomElement).length) { this.chatRoomWidget = new ChatRoomWidget(this.config.chatRoomElement) } @@ -139,6 +161,12 @@ }) } + if (this.chatDetailsWidget) { + this.chat.on("info", message => { + this.chatDetailsWidget.setRoom(message) + }) + } + let join = this.chat.join() if (invitees) { @@ -231,7 +259,11 @@ openChat(event, user) { let windowName = 'KolabChat' + new Date().getTime() - let url = "/chat/?token=" + encodeURIComponent(this.config.token) + "&invite=" + encodeURIComponent(user) + let url = "/chat/?token=" + encodeURIComponent(this.config.token) + + if (user) { + url += "&invite=" + encodeURIComponent(user) + } var extwin = window.open(url, windowName); } @@ -248,6 +280,14 @@ var extwin = window.open(url, windowName); } + /** + * Update chat room details (e.g. name) + */ + updateChatDetails(data) + { + // TODO + } + static extend(obj, src) { Object.keys(src).forEach(function(key) { obj[key] = src[key] }) diff --git a/assets/js/app.js b/assets/js/app.js --- a/assets/js/app.js +++ b/assets/js/app.js @@ -24,7 +24,9 @@ let chat = new KolabChat({ userListElement: "userlist", userStatusElement: "userstatus", + channelListElement: "channellist", chatRoomElement: "chat_txt", + chatDetailsElement: "chat_details", chatInputElement: "chat_txt_input" }) diff --git a/assets/js/widgets/channellist.js b/assets/js/widgets/channellist.js new file mode 100644 --- /dev/null +++ b/assets/js/widgets/channellist.js @@ -0,0 +1,87 @@ + +class ChannelListWidget +{ + /** + * Configuration: + * - username: Current user name + * - openChat: Callback for "Open" button + * - createChat: Callback for "Create" button + * - title: Enable list header with Create button + */ + constructor(id, config) + { + this.config = config || {} + this.id = id + this.render([]) + } + + setUser(username, userId) + { + this.config.username = username + this.config.userId = userId + } + + + /** + * Render channels list + */ + render(channels) + { + let title = '' + let btn = '' + let list = $('#' + this.id) + let config = this.config + let html = channels.map(channel => { + // TODO: List only public channels and channels the user has been invited to + let buttons = this.buttons(channel) + return ` +
  • + #${channel.name} + ${buttons} +
  • ` + }) + .join("") + + if (config.title === true) { + if (config.createChat) { + btn = ` + + ` + } + + title = `
    Channels ${btn}
    ` // TODO: Localization + } + + list.html(title + '') + + $('button', list).on('click', function(e) { + let action = $(this).data('action') + if (action && config[action]) { + config[action](e, $(this).parents('li').data('channel')) + } + }) + } + + /** + * Render channel list record buttons + */ + buttons(channel) + { + let buttons = '' + + if (this.config.openChat) { + let btn_name = ' Open' // TODO: localization + buttons += `` + } + + if (buttons) { + buttons = '
    ' + buttons + '
    ' + } + + return buttons + } +} + +export default ChannelListWidget diff --git a/assets/js/widgets/chatdetails.js b/assets/js/widgets/chatdetails.js new file mode 100644 --- /dev/null +++ b/assets/js/widgets/chatdetails.js @@ -0,0 +1,51 @@ + +class ChatDetailsWidget +{ + /** + * Configuration: + * - submit: Callback for chat details update (only name for now) + */ + constructor(id, config) + { + this.config = config || {} + this.id = id + this.render() + } + + /** + * Set chat room details and re-render the widget + */ + setRoom(room) + { + this.render(room) + } + + /** + * Renders text chat input widget + */ + render(room) + { + if (!room || !room.id) { + return $('#' + this.id).html('') + } + + // TODO: Localization + + let room_name = room.name || ''; + let html = ` +
    Room id: ${room.id}
    +
    Room name:
    + ` + + $('#' + this.id) + .html(html) + .on('keypress', 'input', e => { + let txt + if (e.keyCode == 13 && this.config.submit && (txt = $(e.target).val())) { + this.config.submit(e, txt) + } + }) + } +} + +export default ChatDetailsWidget diff --git a/assets/js/widgets/userlist.js b/assets/js/widgets/userlist.js --- a/assets/js/widgets/userlist.js +++ b/assets/js/widgets/userlist.js @@ -4,12 +4,14 @@ /** * Configuration: * - username: Current user name - * - openChat: callback for "Open chat" button + * - openChat: Callback for "Open chat" button + * - title: Enables list header */ constructor(id, config) { this.config = config || {} this.id = id + this.render([]) } setUser(username, userId) @@ -24,6 +26,7 @@ */ render(presences) { + let title = '' let list = $('#' + this.id) let config = this.config let html = presences.map(presence => { @@ -38,7 +41,11 @@ }) .join("") - list.html(html) + if (config.title === true) { + title = '
    Users
    ' // TODO: Localization + } + + list.html(title + '') $('button', list).on('click', function(e) { let action = $(this).data('action') @@ -56,7 +63,7 @@ let buttons = '' if (this.config.openChat) { - let btn_name = ' Open chat' + let btn_name = ' Open chat' // TODO: Localization buttons += `` } diff --git a/assets/js/widgets/userstatus.js b/assets/js/widgets/userstatus.js --- a/assets/js/widgets/userstatus.js +++ b/assets/js/widgets/userstatus.js @@ -22,6 +22,8 @@ "invisible", "offline" ]; + + // TODO: render() with defalt state here } /** diff --git a/lib/kolab_chat/web/channels/room_channel.ex b/lib/kolab_chat/web/channels/room_channel.ex --- a/lib/kolab_chat/web/channels/room_channel.ex +++ b/lib/kolab_chat/web/channels/room_channel.ex @@ -5,6 +5,8 @@ @spec join(topic :: binary(), args :: map(), socket :: pid()) :: {:ok, socket :: pid()} def join("room:" <> room_name, _, socket) do + socket = assign(socket, :room, %{id: room_name}) + send self(), :after_join {:ok, socket} end @@ -27,4 +29,11 @@ def handle_in("ctl:invite", params, socket) do {:noreply, socket} end + + @spec handle_info(:after_join, socket :: pid()) :: {:noreply, socket :: pid()} + def handle_info(:after_join, socket) do + push socket, "info", socket.assigns.room + {:noreply, socket} + end + end diff --git a/lib/kolab_chat/web/templates/chat/index.html.eex b/lib/kolab_chat/web/templates/chat/index.html.eex --- a/lib/kolab_chat/web/templates/chat/index.html.eex +++ b/lib/kolab_chat/web/templates/chat/index.html.eex @@ -1,3 +1,3 @@ -Room id is: <%= @room %> +
    diff --git a/lib/kolab_chat/web/templates/page/index.html.eex b/lib/kolab_chat/web/templates/page/index.html.eex --- a/lib/kolab_chat/web/templates/page/index.html.eex +++ b/lib/kolab_chat/web/templates/page/index.html.eex @@ -1,7 +1,7 @@
    <%= if @conn.assigns[:user] do %> - Users List: - +
    +
    <% else %>

    <%= gettext "Welcome to %{name}", name: "Kolab Chat!" %>

    <%= gettext "Real-time communication for the Kolab groupware system." %>

    diff --git a/mix.lock b/mix.lock --- a/mix.lock +++ b/mix.lock @@ -1,18 +1,18 @@ -%{"amnesia": {:hex, :amnesia, "0.2.7", "ffc2221bf72da4cfafbbb497adf9cf7e52138f1333cec5836187a53f94ae0665", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, repo: "hexpm", optional: false]}], "hexpm"}, - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, - "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, - "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [], [], "hexpm"}, - "credo": {:hex, :credo, "0.7.4", "0c33bcce4d574ce6df163cbc7d1ecb22de65713184355bd3be81cc4ab0ecaafa", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, - "exquisite": {:hex, :exquisite, "0.1.8", "ee8f56aae477287ce5e7dfcbc163a420cccbb73e680a6d80a09203e9ef514fa4", [], [], "hexpm"}, - "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], [], "hexpm"}, - "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"}, - "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.3.0-rc.2", "53104ada25ba85fe160268c0dc826fe038bc074293730b4522fb9aca28d8aa13", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [], [], "hexpm"}, - "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, - "remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], [], "hexpm"}, - "uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [:mix], [], "hexpm"}} +%{"amnesia": {:hex, :amnesia, "0.2.7", "ffc2221bf72da4cfafbbb497adf9cf7e52138f1333cec5836187a53f94ae0665", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, optional: false]}]}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []}, + "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, optional: false]}]}, + "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, + "credo": {:hex, :credo, "0.7.4", "0c33bcce4d574ce6df163cbc7d1ecb22de65713184355bd3be81cc4ab0ecaafa", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]}, + "exquisite": {:hex, :exquisite, "0.1.8", "ee8f56aae477287ce5e7dfcbc163a420cccbb73e680a6d80a09203e9ef514fa4", [:mix], []}, + "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, + "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []}, + "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []}, + "phoenix": {:hex, :phoenix, "1.3.0-rc.2", "53104ada25ba85fe160268c0dc826fe038bc074293730b4522fb9aca28d8aa13", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: false]}]}, + "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []}, + "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, + "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []}, + "remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], []}, + "uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [:mix], []}}