diff --git a/README.md b/README.md index 06b102f..b60f621 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,9 @@ Usage ----- Go to `RsNewWebUI/webui/` and open the `index.html` file in your browser. +### References: +* Mithril: https://mithril.js.org/hyperscript.html + Contributing ------------ ### Bug Reports & Feature requests diff --git a/webui-src/app/chat/chat.css b/webui-src/app/chat/chat.css index e15e1bf..1fdac40 100644 --- a/webui-src/app/chat/chat.css +++ b/webui-src/app/chat/chat.css @@ -2,18 +2,61 @@ margin: 10px; border: 1px solid #aaa; border-radius: 20px; -}.lobby h5 { +} + +.lobby .mainname { margin: 20px; font-weight: 100; font-size: 1.2em; -}.lobby > p { - font-size: 0.95em; - color: #666; - margin-left: 25px; } -.lobby.subscribed:hover { + +.topic { + color: #666; +} + +.lobby > .topic { + font-size: 0.95em; + margin-left: 25px; + margin-bottom: 5px; +} + +.lefttitle { + margin-top: 15px; + margin-bottom: 0; + font-weight: 100; + font-size: 1.2em; +} + +.leftname { + margin-top: 5px; + margin-bottom: 5px; + padding:5px; + font-weight: 100; + font-size: 1em; +} + +.leftlobby > .topic { + font-size: 0.75em; + margin-left: 15px; + margin-bottom: 5px; +} + +.subscribed { cursor: pointer; } + +.leftlobby { + border: 1px solid #aaa; + border-radius: 10px; + margin-top: 5px; + background-color: white; +} + +.leftlobby.selected-lobby { + color: white; + background-color: #3ba4d7; +} + .rightbar { position: absolute; width: 185px; @@ -32,25 +75,20 @@ padding: 15px; } -.chatMessage { +.lobbies { position: absolute; - background-color: white; - height: 85px; + width: 185px; + left:15px ; bottom: 15px; - right: 215px; - left: 15px; -} - -textarea.chatMsg { - height: 100%; - width: 100%; + top: 130px; + overflow:auto; } .messages { position: absolute; background-color: white; top: 130px; - left: 15px; + left: 215px; right: 215px; bottom: 115px; overflow: auto; @@ -67,4 +105,18 @@ textarea.chatMsg { .username { color: darkgreen; font-weight: bolder; +} + +.chatMessage { + position: absolute; + background-color: white; + height: 85px; + bottom: 15px; + right: 215px; + left: 215px; +} + +textarea.chatMsg { + height: 100%; + width: 100%; } \ No newline at end of file diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js index 763c6df..02ca238 100644 --- a/webui-src/app/chat/chat.js +++ b/webui-src/app/chat/chat.js @@ -21,6 +21,7 @@ function loadLobbyDetails(id, apply) { let ChatRoomsModel = { allRooms: [], + knownSubscrIds:[], // to exclude subscribed from public rooms (subscribedRooms filled to late) subscribedRooms: {}, loadPublicRooms() { // TODO: this doesn't preserve id of rooms, @@ -32,23 +33,28 @@ let ChatRoomsModel = { ); }, loadSubscribedRooms() { - ChatRoomsModel.subscribedRooms = {}; + // ChatRoomsModel.subscribedRooms = {}; rs.rsJsonApiRequest('/rsMsgs/getChatLobbyList', {}, - data => data.map(id => loadLobbyDetails(id, info => ChatRoomsModel.subscribedRooms[id] = info)), + data => { + ChatRoomsModel.knownSubscrIds = data; + let rooms = {}; + data.map(id => loadLobbyDetails(id, info => rooms[id] = info)); + ChatRoomsModel.subscribedRooms = rooms; + }, true, {}, // Custom deserializer NOTE: // JS uses double precision numbers of 64 bit. It is equivalent // to 53 bits of precision. All large precision ints will // get truncated to an approximation. // This API uses Cpp-style 64 bits for `id`. - // Instead of parsing using JSON.parse, this funciton manually + // Instead of parsing using JSON.parse, this function manually // extracts all numbers and stores them as strings // Note the g flag. The match will return an array of strings (response) => response.match(/\d+/g), ) }, subscribed(info) { - return this.subscribedRooms.hasOwnProperty(info.lobby_id.xstr64); + return this.knownSubscrIds.includes(info.lobby_id.xstr64); }, }; @@ -69,10 +75,10 @@ const ChatLobbyModel = { chatId(action) { return {type:3,lobby_id:{xstr64:m.route.param('lobby')}}; }, - loadLobby () { - loadLobbyDetails(m.route.param('lobby'), detail => { + loadLobby (currentlobbyid) { + loadLobbyDetails(currentlobbyid, detail => { this.currentLobby = detail; - let lobbyid = m.route.param('lobby'); + let lobbyid = currentlobbyid; // apply existing messages to current lobby view rs.events[15].chatMessages(this.chatId(),rs.events[15], l => (this.messages = l.map(printMessage))); // register for chatEvents for future messages @@ -108,37 +114,50 @@ const ChatLobbyModel = { () => '{"id":{"type": 3,"lobby_id":' + m.route.param('lobby') + '}, "msg":' + JSON.stringify(msg) + '}' ); }, + selected(info, selName, defaultName) { + let currid = (ChatLobbyModel.currentLobby.lobby_id || {xstr64: m.route.param('lobby')}).xstr64 + return ((info.lobby_id.xstr64 === currid) ? selName : '') + defaultName; + }, + switchToEvent(info) { + return () => { + ChatLobbyModel.currentLobby = info; + m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 }); + ChatLobbyModel.loadLobby(info.lobby_id.xstr64); // update + }; + } } const Lobby = () => { let info = {}; + let tagname = ''; + let onclick = e => {}; + let lobbytagname = ''; return { - oninit: (v) => info = v.attrs.info, - view: (v) => m( '.lobby.' + (ChatRoomsModel.subscribed(v.attrs.info) ? 'subscribed':'public'), { - key: v.attrs.info.lobby_id.xstr64, - onclick: e => { - if (ChatRoomsModel.subscribed(v.attrs.info)) { - m.route.set('/chat/:lobby', { - lobby: v.attrs.info.lobby_id.xstr64 - }); - } - }, + oninit: (v) => { + info = v.attrs.info; + tagname = v.attrs.tagname; + onclick = v.attrs.onclick || (e => {}); + lobbytagname= v.attrs.lobbytagname || 'h5.mainname'; + }, + view: v => { + return m(ChatLobbyModel.selected(info, '.selected-lobby', tagname), { + key: info.lobby_id.xstr64, + onclick: onclick, }, [ - m('h5', info.lobby_name === '' ? '' : info.lobby_name), - m('p', info.lobby_topic), - ]), + m(lobbytagname,info.lobby_name === '' ? '' : info.lobby_name), + m('.topic', info.lobby_topic), + ]); + }, }; }; const SubscribedLobbies = () => { - lobbies = []; return { - oninit: (v) => ChatRoomsModel.loadSubscribedRooms(), view: () => m('.widget', [ m('h3', 'Subscribed chat rooms'), m('hr'), Object.values(ChatRoomsModel.subscribedRooms).map(info => m(Lobby, { - info + info, tagname:'.lobby.subscribed', onclick: e => m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 }) })), ]), }; @@ -146,12 +165,11 @@ const SubscribedLobbies = () => { const PublicLobbies = () => { return { - oninit: () => ChatRoomsModel.loadPublicRooms(), view: () => m('.widget', [ m('h3', 'Public chat rooms'), m('hr'), ChatRoomsModel.allRooms.filter(info => !ChatRoomsModel.subscribed(info)).map(info => m(Lobby, { - info, + info, tagname:'.lobby.public', })), ]) }; @@ -159,6 +177,10 @@ const PublicLobbies = () => { const Layout = () => { return { + oninit: () => { + ChatRoomsModel.loadSubscribedRooms(); + ChatRoomsModel.loadPublicRooms(); + }, view: vnode => m('.tab-page', [ m(SubscribedLobbies), m(PublicLobbies), @@ -166,11 +188,51 @@ const Layout = () => { }; }; +const LobbyList = () => { + let rooms= []; + let tagname= ''; + let lobbytagname= ''; + let onclick = (info => (() => {})); + return { + oninit: vnode => { + rooms = vnode.attrs.rooms; + tagname = vnode.attrs.tagname; + lobbytagname= vnode.attrs.lobbytagname; + onclick=vnode.attrs.onclick||(() => (() => {})); + }, + view: vnode => rooms.map(info => m(Lobby, { + info, + tagname: tagname, + lobbytagname, + onclick: onclick(info), + })), + }; +} + const LayoutSingle = () => { return { - oninit: ChatLobbyModel.loadLobby(), + oninit: () => { + ChatRoomsModel.loadSubscribedRooms(); + ChatRoomsModel.loadPublicRooms(); + ChatLobbyModel.loadLobby(m.route.param('lobby')); + }, view: vnode => m('.tab-page', [ m('h3.lobbyName', ChatLobbyModel.currentLobby.lobby_name), + m('.lobbies', + m('h5.lefttitle', 'subscribed:'), + m('hr'), + m(LobbyList, { + rooms: Object.values(ChatRoomsModel.subscribedRooms), + tagname: '.leftlobby.subscribed', + lobbytagname:'h5.leftname', + onclick: ChatLobbyModel.switchToEvent, + }), + m('h5.lefttitle', 'public:'), + m('hr'), + Object.values(ChatRoomsModel.allRooms).filter(info => !ChatRoomsModel.subscribed(info)).map(info => m(Lobby, { + info, tagname: '.leftlobby.public', lobbytagname:'h5.leftname', + })), + ), m('.messages', ChatLobbyModel.messages), m('.rightbar', ChatLobbyModel.users), m('.chatMessage', {}, m("textarea.chatMsg", { diff --git a/webui-src/app/rswebui.js b/webui-src/app/rswebui.js index 386e353..fd3c62f 100644 --- a/webui-src/app/rswebui.js +++ b/webui-src/app/rswebui.js @@ -100,30 +100,40 @@ function computeIfMissing(map, key, missing = () => ({})) { function deeperIfExist(map, key, action) { if (map.hasOwnProperty(key)) { action(map[key]); + return true; + } else { + return false; } } const eventQueue = { events: { 15: { // Chat-Messages - types: { - 3: chat_id => chat_id.lobby_id.xstr64, // lobby_id - }, - messages: {}, - chatMessages: (chat_id, owner, action) => deeperIfExist( - owner.types, - chat_id.type, - keyfn => action(computeIfMissing(computeIfMissing(owner.messages,chat_id.type), keyfn(chat_id),()=>[])) - ), - handler: (event,owner) => owner.chatMessages(event.mChatMessage.chat_id, owner, r => { - console.info(['adding chat' ,r , event.mChatMessage]) - r.push(event.mChatMessage); - owner.notify(event.mChatMessage); - }), - notify: () => {}, + types: { + 3: chat_id => chat_id.lobby_id.xstr64, // lobby_id + }, + messages: {}, + chatMessages: (chat_id, owner, action) => deeperIfExist( + owner.types, + chat_id.type, + keyfn => action(computeIfMissing(computeIfMissing(owner.messages,chat_id.type), keyfn(chat_id),()=>[])) + ), + handler: (event,owner) => owner.chatMessages(event.mChatMessage.chat_id, owner, r => { + console.info(['adding chat' ,r , event.mChatMessage]); + r.push(event.mChatMessage); + owner.notify(event.mChatMessage); + }), + notify: () => {}, }, + 8: { // Circles (ignore in the meantime) + handler: (event,owner) => {}, + } + }, + handler: event => { + if (!deeperIfExist(eventQueue.events, event.mType, owner => owner.handler(event, owner))) { + console.info(event); + }; }, - handler: event => deeperIfExist(eventQueue.events, event.mType, owner => owner.handler(event, owner)), } const userList = { @@ -189,11 +199,10 @@ function startEventQueue(info, loginHeader = {}, displayAuthError = () => {}, di } } else if(data.hasOwnProperty('event')) { data.event.queueSize = currIndex; - console.info(data.event); eventQueue.handler(data.event); } } - if (currIndex > 1e6) { // max 1 MB eventQueue + if (currIndex > 1e5) { // max 100 kB eventQueue startEventQueue('restart queue'); xhr.abort(); }