diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dddeed14..21d9cbfb 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,12 @@ +please link to related trello cards, if they exist, from the following two boards respectively +https://trello.com/b/8HlCikOX/metamaps-design + +https://trello.com/b/uFOA6a2x/metamaps-feedback-feature-ideas-requests + +[the issue as framed for design]() + +[the issue as framed from the users perspective]() ============ diff --git a/Gemfile b/Gemfile index 0d8e8d7a..56ee0bd2 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'rack-cors' gem 'redis' gem 'slack-notifier' gem 'snorlax' +gem 'puma' # asset stuff gem 'jquery-rails' diff --git a/Gemfile.lock b/Gemfile.lock index d104cb51..2397c647 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,6 +167,7 @@ GEM pry (~> 0.10) pry-rails (0.3.4) pry (>= 0.9.10) + puma (3.6.2) pundit (1.1.0) activesupport (>= 3.0.0) pundit_extra (0.3.0) @@ -298,6 +299,7 @@ DEPENDENCIES pg pry-byebug pry-rails + puma pundit pundit_extra rack-attack diff --git a/Procfile b/Procfile index e00c3019..b6bae37c 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ -web: bundle exec rails server -p $PORT +web: bundle exec puma -p $PORT worker: bundle exec rake jobs:work diff --git a/README.md b/README.md index b3fdb8ab..d2a3a424 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Metamaps ======= [![Build Status](https://travis-ci.org/metamaps/metamaps.svg?branch=develop)](https://travis-ci.org/metamaps/metamaps) +[![Code Climate](https://codeclimate.com/github/metamaps/metamaps/badges/gpa.svg)](https://codeclimate.com/github/metamaps/metamaps) ## What is Metamaps? @@ -16,8 +17,12 @@ Metamaps is developed and maintained by a distributed, nomadic community compris - Contact: [team@metamaps.cc](mailto:team@metamaps.cc) or [@metamapps](https://twitter.com/metamapps) on Twitter - User Documentation: [docs.metamaps.cc](https://docs.metamaps.cc) - User Community: [hylo.com/c/metamaps](https://www.hylo.com/c/metamaps) -- Development Roadmap: [github.com/metamaps/metamaps/milestones](https://github.com/metamaps/metamaps/milestones) -- To send us a personal message or request an invite to the open beta, get in touch with us via email, Twitter, or Hylo +- To see what we're developing, or to weigh in on what you'd like to see developed, see our [Metamaps Feedback and Features](https://trello.com/b/uFOA6a2x/metamaps-feedback-feature-ideas-requests) board on trello +- To follow along with, or contribute,to our design process, see our [Metamaps Design](https://trello.com/b/8HlCikOX/metamaps-design) board on trello +- To follow along with, or contribute to, our development process, see our [Github Issues and Pull Requests](https://github.com/metamaps/metamaps/issues) +- Request an invite to the open beta [here](https://metamaps.cc/request) + +- To send us a personal message get in touch with us via email, Twitter, or Hylo - If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing]. - If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up. @@ -63,7 +68,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY The license can be read [here][license]. -Copyright (c) 2016 Connor Turland +Copyright (c) 2017 Connor Turland [site-beta]: http://metamaps.cc [license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 11633bea..6af52fbd 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,6 +14,7 @@ //= require jquery //= require jquery-ui //= require jquery_ujs +//= require action_cable //= require_directory ./lib //= require ./webpacked/metamaps.bundle //= require ./Metamaps.ServerData diff --git a/app/assets/stylesheets/emoji-mart-0.3.5.css b/app/assets/stylesheets/emoji-mart-0.3.5.css new file mode 100644 index 00000000..3672320f --- /dev/null +++ b/app/assets/stylesheets/emoji-mart-0.3.5.css @@ -0,0 +1,263 @@ +.emoji-mart, +.emoji-mart * { + box-sizing: border-box; + line-height: 1.15; +} + +.emoji-mart { + font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; + font-size: 16px; + display: inline-block; + color: #222427; + border: 1px solid #d9d9d9; + border-radius: 5px; + background: #fff; +} + +.emoji-mart .emoji-mart-emoji { + padding: 6px; +} + +.emoji-mart-bar:first-child { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} +.emoji-mart-bar:last-child { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} + +.emoji-mart-anchors { + display: flex; + justify-content: space-between; + padding: 0 6px; + color: #858585; + line-height: 0; +} + +.emoji-mart-anchor { + position: relative; + flex: 1; + text-align: center; + padding: 12px 4px; + overflow: hidden; + transition: color .1s ease-out; +} +.emoji-mart-anchor:hover, +.emoji-mart-anchor-selected { + color: #464646; +} + +.emoji-mart-anchor-selected .emoji-mart-anchor-bar { + bottom: 0; +} + +.emoji-mart-anchor-bar { + position: absolute; + bottom: -3px; left: 0; + width: 100%; height: 3px; + background-color: #464646; +} + +.emoji-mart-anchors i { + display: inline-block; + width: 100%; + max-width: 22px; +} + +.emoji-mart-anchors svg { + fill: currentColor; +} + +.emoji-mart-scroll { + overflow-y: scroll; + height: 270px; + padding: 0 6px 6px 6px; + border: solid #d9d9d9; + border-width: 1px 0; +} + +.emoji-mart-search { + font-size: 16px; + display: block; + width: 100%; + padding: .2em .6em; + margin-top: 6px; + border-radius: 25px; + border: 1px solid #d9d9d9; + outline: 0; +} + +.emoji-mart-category .emoji-mart-emoji span { + z-index: 1; + position: relative; +} + +.emoji-mart-category .emoji-mart-emoji:hover:before { + z-index: 0; + content: ""; + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + background-color: #f4f4f4; + border-radius: 100%; +} + +.emoji-mart-category-label { + z-index: 2; + position: relative; + position: -webkit-sticky; + top: 0; +} + +.emoji-mart-category-label span { + display: block; + width: 100%; + font-weight: 500; + padding: 5px 6px; + background-color: #fff; + background-color: rgba(255, 255, 255, .95); +} + +.emoji-mart-emoji { + position: relative; + display: inline-block; + font-size: 0; +} + +.emoji-mart-no-results { + font-size: 14px; + text-align: center; + padding-top: 70px; + color: #858585; +} +.emoji-mart-no-results span { + display: inline-block; + vertical-align: middle; +} + +.emoji-mart-preview { + position: relative; + height: 70px; +} + +.emoji-mart-preview-emoji, +.emoji-mart-preview-data, +.emoji-mart-preview-skins { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +.emoji-mart-preview-emoji { + left: 12px; +} + +.emoji-mart-preview-data { + left: 68px; right: 12px; + word-break: break-word; +} + +.emoji-mart-preview-skins { + right: 30px; + text-align: right; +} + +.emoji-mart-preview-name { + font-size: 14px; +} + +.emoji-mart-preview-shortname { + font-size: 12px; + color: #888; +} +.emoji-mart-preview-shortname + .emoji-mart-preview-shortname, +.emoji-mart-preview-shortname + .emoji-mart-preview-emoticon, +.emoji-mart-preview-emoticon + .emoji-mart-preview-emoticon { + margin-left: .5em; +} + +.emoji-mart-preview-emoticon { + font-size: 11px; + color: #bbb; +} + +.emoji-mart-title span { + display: inline-block; + vertical-align: middle; +} + +.emoji-mart-title .emoji-mart-emoji { + padding: 0; +} + +.emoji-mart-title-label { + color: #999A9C; + font-size: 26px; + font-weight: 300; +} + +.emoji-mart-skin-swatches { + font-size: 0; + padding: 2px 0; + border: 1px solid #d9d9d9; + border-radius: 12px; + background-color: #fff; +} + +.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch { + width: 16px; + padding: 0 2px; +} + +.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch-selected:after { + opacity: .75; +} + +.emoji-mart-skin-swatch { + display: inline-block; + width: 0; + vertical-align: middle; + transition-property: width, padding; + transition-duration: .125s; + transition-timing-function: ease-out; +} + +.emoji-mart-skin-swatch:nth-child(1) { transition-delay: 0 } +.emoji-mart-skin-swatch:nth-child(2) { transition-delay: .03s } +.emoji-mart-skin-swatch:nth-child(3) { transition-delay: .06s } +.emoji-mart-skin-swatch:nth-child(4) { transition-delay: .09s } +.emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s } +.emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s } + +.emoji-mart-skin-swatch-selected { + position: relative; + width: 16px; + padding: 0 2px; +} +.emoji-mart-skin-swatch-selected:after { + content: ""; + position: absolute; + top: 50%; left: 50%; + width: 4px; height: 4px; + margin: -2px 0 0 -2px; + background-color: #fff; + border-radius: 100%; + pointer-events: none; + opacity: 0; + transition: opacity .2s ease-out; +} + +.emoji-mart-skin { + display: inline-block; + width: 100%; padding-top: 100%; + max-width: 12px; + border-radius: 100%; +} + +.emoji-mart-skin-tone-1 { background-color: #ffc93a } +.emoji-mart-skin-tone-2 { background-color: #fadcbc } +.emoji-mart-skin-tone-3 { background-color: #e0bb95 } +.emoji-mart-skin-tone-4 { background-color: #bf8f68 } +.emoji-mart-skin-tone-5 { background-color: #9b643d } +.emoji-mart-skin-tone-6 { background-color: #594539 } diff --git a/app/assets/stylesheets/junto.css.erb b/app/assets/stylesheets/junto.css.erb deleted file mode 100644 index 91b610fc..00000000 --- a/app/assets/stylesheets/junto.css.erb +++ /dev/null @@ -1,348 +0,0 @@ -.collaborator-video { - z-index: 1; - position: absolute; - width: 150px; - height: 150px; - cursor: default; - color: #FFF; -} -.collaborator-video .video-receive { - position: absolute; - width: 160px; - padding: 20px 20px 20px 170px; - background: #424242; - height: 110px; - border-top-left-radius: 75px; - border-bottom-left-radius: 75px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} -.collaborator-video .video-receive .video-statement { - margin-bottom: 10px; -} -.collaborator-video .video-receive .btn-group .btn-yes { - margin-right: 10px; -} -.collaborator-video .video-receive .btn-group .btn-no { - background-color: #c04f4f; -} -.collaborator-video .video-receive .btn-group .btn-no:hover { - background-color: #A54242; -} -.collaborator-video .video-cutoff { - width: 150px; - height: 150px; - overflow: hidden; - border-radius: 75px; - z-index: 0; - position: relative; - -webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); - -moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); - box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); -} -.collaborator-video .video-cutoff video { - height: 150px; - margin-left: -25px; -} -.collaborator-video .video-cutoff .collaborator-video-avatar { - position: absolute; - top: 0; - left: 0; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; - -webkit-user-drag: none; - display: none; -} -.collaborator-video .video-audio { - position: absolute; - width: 24px; - height: 24px; - top: 85%; - right: 0px; - cursor: pointer; - background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat; -} -.collaborator-video .video-audio:hover { - background-position-x: -24px; -} -.collaborator-video .video-audio.active { - background-position-y: -24px; -} -.collaborator-video .video-video { - position: absolute; - width: 24px; - height: 24px; - top: 85%; - left: 0px; - cursor: pointer; - background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat; -} -.collaborator-video .video-video:hover { - background-position-x: -24px; -} -.collaborator-video .video-video.active { - background-position-y: -24px; -} -.collaborator-video.my-video { - left: 30px; - top: 72px; -} -.chat-box { - position: relative; - display: flex; - flex-direction: column; - z-index: 1; - width: 300px; - float: right; - height: 100%; - background: #424242; - box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23); -} -.chat-box .chat-button { - position: absolute; - top: 50%; - left: -36px; - width: 36px; - height: 49px; - background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat; - cursor: pointer; -} -.chat-box .chat-button.active { - background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important; -} -.chat-box .chat-button .chat-unread { - display: none; - background: #DAB539; - position: absolute; - top: -3px; - left: -11px; - width: 20px; - height: 20px; - border-radius: 11px; - border: 2px solid #424242; - color: #424242; - text-align: center; - font-size: 12px; - font-weight: bold; - line-height: 20px; -} -.chat-box .junto-header { - width: 276px; - padding: 16px 8px 16px 16px; - font-size: 16px; - text-align: left; - font-weight: bold; - background-color: #000000; - color: #f5f5f5; - box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23); -} -.chat-box .junto-header .cursor-toggle { - width: 32px; - height: 32px; - margin-right: 8px; - margin-top: -8px; - float: right; - background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat; -} -.chat-box .junto-header .cursor-toggle:hover { - background-position-x: -32px; -} -.chat-box .junto-header .cursor-toggle.active { - background-position-y: -32px; -} -.chat-box .junto-header .video-toggle { - width: 32px; - height: 32px; - margin-right: 10px; - margin-top: -8px; - float: right; - background: url(<%= asset_path 'video_sprite.png' %>) no-repeat; -} -.chat-box .junto-header .video-toggle:hover { - background-position-x: -32px; -} -.chat-box .junto-header .video-toggle.active { - background-position-y: -32px; -} -.chat-box .participants { - width: 100%; - min-height: 150px; - padding: 16px 0px 16px 0px; - text-align: left; - color: #f5f5f5; - overflow-y: auto; -} -.chat-box .participants .conversation-live { - display: none; - padding: 5px 10px 5px 10px; - background: #c04f4f; - margin: 5px 10px; - border-radius: 2px; -} -.chat-box .participants .conversation-live .call-action { - float: right; - cursor: pointer; - color: #EBFF00; -} -.chat-box .participants .conversation-live .leave { - display: none; -} -.chat-box .participants.is-participating .conversation-live .leave { - display: block; -} -.chat-box .participants.is-participating .conversation-live .join { - display: none; -} -.chat-box .participants .participant { - width: 89%; - padding: 8px 8px 2px 8px; - color: #f5f5f5; - font-family: arial, sans-serif; - font-size: 13px; - line-height: 14px; -} -.chat-box .participants .participant .chat-participant-image { - width: 15%; - float: left; - overflow: hidden; - color: #BBB; - padding-top: 2px; -} -.chat-box .participants .participant .chat-participant-image img { - width: 32px; - height: 32px; - border-radius: 18px; -} -.chat-box .participants .participant .chat-participant-name { - width: 53%; - float: left; - font-size: 13px; - font-weight: bold; - margin-top: 12px; - padding: 2px 8px 0; - text-align: left; -} -.chat-box .participants .participant.is-self .chat-participant-invite-call, -.chat-box .participants .participant.is-self .chat-participant-invite-join { - display: none !important; -} -.chat-box .participants.is-live .participant .chat-participant-invite-call { - display: none; -} -.chat-box .participants .participant .chat-participant-invite-join { - display: none; -} -.chat-box .participants.is-live.is-participating .participant:not(.active) .chat-participant-invite-join { - display: block; -} -.chat-box .participants .participant .chat-participant-invite-call, -.chat-box .participants .participant .chat-participant-invite-join - { - float: right; - background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center; -} -.chat-box .participants .participant.pending .chat-participant-invite-call, -.chat-box .participants .participant.pending .chat-participant-invite-join { - background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center; -} -.chat-box .participants .participant .chat-participant-participating { - float: right; - display: none; - margin-top: 14px; -} -.chat-box .participants .participant .chat-participant-participating .green-dot { - background: #4fc059; - width: 12px; - height: 12px; - border-radius: 6px; -} -.chat-box .participants .participant.active .chat-participant-participating { - display: block; -} -.chat-box .chat-header { - width: 276px; - padding: 16px 8px 16px 16px; - font-size: 16px; - text-align: left; - font-weight: bold; - background-color: #000000; - color: #f5f5f5; - box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23); -} -.chat-box .chat-header .sound-toggle { - width: 24px; - height: 24px; - margin-right: 10px; - margin-top: -2px; - float: right; - background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat; -} -.chat-box .chat-header .sound-toggle:hover { - background-position-x: -24px; -} -.chat-box .chat-header .sound-toggle.active { - background-position-y: -24px; -} -.chat-box .chat-input { - min-height: 80px; - width: 94%; - padding: 8px 3% 8px 3%; - font-size: 13px; - outline: none; - resize: none; -} -.chat-box .chat-messages { - width: 100%; - padding: 16px 0px 0px 0px; - overflow-y: auto; - flex-grow: 1; -} -.chat-box .chat-messages .chat-message { - width: 89%; - padding: 8px 8px 2px 8px; - color: #f5f5f5; - font-family: arial, sans-serif; - font-size: 13px; - line-height: 14px; -} -.chat-box .chat-messages .chat-message a:link { - color: #4fb5c0; - text-decoration: underline; -} -.chat-box .chat-messages .chat-message a:visited { - color: #aea9fd; - text-decoration: underline; -} -.chat-box .chat-messages .chat-message a:hover { - color: #dab539; - text-decoration: underline; -} -.chat-box .chat-messages .chat-message .chat-message-user { - width: 15%; - float: left; - overflow: hidden; - color: #BBB; - padding-top: 2px; -} -.chat-box .chat-messages .chat-message .chat-message-user img { - border: 2px solid #424242; - width: 32px; - height: 32px; - border-radius: 18px; -} -.chat-box .chat-messages .chat-message .chat-message-text { - width: 73%; - float: left; - margin-top: 12px; - padding: 2px 8px 0; - text-align: left; - word-wrap: break-word; -} -.chat-box .chat-messages .chat-message .chat-message-time { - float: right; - font-size: 10px; - color: #757575; -} diff --git a/app/assets/stylesheets/junto.scss.erb b/app/assets/stylesheets/junto.scss.erb new file mode 100644 index 00000000..2ce4656f --- /dev/null +++ b/app/assets/stylesheets/junto.scss.erb @@ -0,0 +1,375 @@ +.collaborator-video { + z-index: 1; + position: absolute; + width: 150px; + height: 150px; + cursor: default; + color: #FFF; + + .video-receive { + position: absolute; + width: 160px; + padding: 20px 20px 20px 170px; + background: #424242; + height: 110px; + border-top-left-radius: 75px; + border-bottom-left-radius: 75px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + + .video-statement { + margin-bottom: 10px; + } + .btn-group { + .btn-yes { + margin-right: 10px; + } + .btn-no { + background-color: #c04f4f; + &:hover { + background-color: #A54242; + } + } + } + } + + .video-cutoff { + width: 150px; + height: 150px; + overflow: hidden; + border-radius: 75px; + z-index: 0; + position: relative; + -webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); + -moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); + box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); + + video { + height: 150px; + margin-left: -25px; + } + .collaborator-video-avatar { + position: absolute; + top: 0; + left: 0; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + -webkit-user-drag: none; + display: none; + } + } + .video-audio { + position: absolute; + width: 24px; + height: 24px; + top: 85%; + right: 0px; + cursor: pointer; + background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat; + } + .video-audio:hover { + background-position-x: -24px; + } + .video-audio.active { + background-position-y: -24px; + } + .video-video { + position: absolute; + width: 24px; + height: 24px; + top: 85%; + left: 0px; + cursor: pointer; + background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat; + } + .video-video:hover { + background-position-x: -24px; + } + .video-video.active { + background-position-y: -24px; + } +} + +.collaborator-video.my-video { + left: 30px; + top: 72px; +} + +#chat-box-wrapper { + height: 100%; + float: right; +} + +.chat-box { + position: relative; + display: flex; + flex-direction: column; + z-index: 1; + width: 300px; + height: 100%; + background: #424242; + box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23); + + .chat-button { + position: absolute; + top: 50%; + left: -36px; + width: 36px; + height: 49px; + background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat; + cursor: pointer; + + &.active { + background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important; + } + .chat-unread { + background: #DAB539; + position: absolute; + top: -3px; + left: -11px; + width: 20px; + height: 20px; + border-radius: 11px; + border: 2px solid #424242; + color: #424242; + text-align: center; + font-size: 12px; + font-weight: bold; + line-height: 20px; + } + } + + .junto-header { + width: 276px; + padding: 16px 8px 16px 16px; + font-size: 16px; + text-align: left; + font-weight: bold; + background-color: #000000; + color: #f5f5f5; + box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23); + + .cursor-toggle { + width: 32px; + height: 32px; + margin-right: 8px; + margin-top: -8px; + float: right; + background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat; + } + .cursor-toggle:hover { + background-position-x: -32px; + } + .cursor-toggle.active { + background-position-y: -32px; + } + .video-toggle { + width: 32px; + height: 32px; + margin-right: 10px; + margin-top: -8px; + float: right; + background: url(<%= asset_path 'video_sprite.png' %>) no-repeat; + } + .video-toggle:hover { + background-position-x: -32px; + } + .video-toggle.active { + background-position-y: -32px; + } + } + + .participants { + width: 100%; + min-height: 150px; + padding: 16px 0px 16px 0px; + text-align: left; + color: #f5f5f5; + overflow-y: auto; + + .conversation-live { + padding: 5px 10px 5px 10px; + background: #c04f4f; + margin: 5px 10px; + border-radius: 2px; + } + .conversation-live .call-action { + float: right; + cursor: pointer; + color: #EBFF00; + } + .participant { + width: 89%; + padding: 8px 8px 2px 8px; + color: #f5f5f5; + font-family: arial, sans-serif; + font-size: 13px; + line-height: 14px; + + .chat-participant-image { + width: 15%; + float: left; + overflow: hidden; + color: #BBB; + padding-top: 2px; + } + .chat-participant-image img { + width: 32px; + height: 32px; + border-radius: 18px; + } + .chat-participant-name { + width: 53%; + float: left; + font-size: 13px; + font-weight: bold; + margin-top: 12px; + padding: 2px 8px 0; + text-align: left; + } + .chat-participant-invite-call, + .chat-participant-invite-join + { + float: right; + background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center; + } + .chat-participant-invite-call.pending, + .chat-participant-invite-join.pending { + background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center; + } + .chat-participant-participating { + float: right; + margin-top: 14px; + } + .chat-participant-participating .green-dot { + background: #4fc059; + width: 12px; + height: 12px; + border-radius: 6px; + } + } + } + + .chat-header { + width: 276px; + padding: 16px 8px 16px 16px; + font-size: 16px; + text-align: left; + font-weight: bold; + background-color: #000000; + color: #f5f5f5; + box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23); + + .sound-toggle { + width: 24px; + height: 24px; + margin-right: 10px; + margin-top: -2px; + float: right; + background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat; + } + .sound-toggle:hover { + background-position-x: -24px; + } + .sound-toggle.active { + background-position-y: -24px; + } + } + + $chat_font_size: 16px; + + .chat-input { + min-height: 80px; + width: 88%; + padding: 8px 9% 8px 3%; + font-size: $chat_font_size; + outline: none; + resize: none; + } + + .chat-messages { + width: 100%; + padding: 16px 0px; + overflow-y: auto; + flex-grow: 1; + + .chat-message { + width: 89%; + padding: 8px 8px 2px 8px; + color: #f5f5f5; + font-family: arial, sans-serif; + font-size: $chat_font_size; + line-height: $chat_font_size + 1px; + + a:link { + color: #4fb5c0; + text-decoration: underline; + } + a:visited { + color: #aea9fd; + text-decoration: underline; + } + a:hover { + color: #dab539; + text-decoration: underline; + } + .chat-message-user { + width: 12%; + float: left; + overflow: hidden; + color: #BBB; + padding-top: 2px; + } + .chat-message-user img { + border: 2px solid #424242; + width: 28px; + height: 28px; + border-radius: 16px; + } + .chat-message-meta { + padding: 0 8px; + float: left; + } + .chat-message-username { + color: #4fc059; + } + .chat-message-text { + width: 80%; + float: left; + padding: 2px 8px 0; + text-align: left; + word-wrap: break-word; + } + .chat-message-time { + font-size: 12px; + color: #757575; + } + } + } + + .new-message-area { + position: relative; + + .emoji-mart { + position: absolute; + bottom: 98px; + } + + .extra-message-options { + height: 20px; + position: absolute; + right: 2px; + bottom: 74px; + + .emoji-picker-button { + font-size: 16px; + line-height: 20px; + cursor: pointer; + padding: 4px; + } + } + } +} diff --git a/app/assets/stylesheets/mapcard.scss.erb b/app/assets/stylesheets/mapcard.scss.erb index 8f30b00d..e13c3c6c 100644 --- a/app/assets/stylesheets/mapcard.scss.erb +++ b/app/assets/stylesheets/mapcard.scss.erb @@ -211,6 +211,16 @@ span.creatorName { margin-left: 8px; + max-width: 162px; + display: inline-block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: middle; + } + + .creatorAndPerm.cardHasViewOnly span.creatorName { + max-width: 95px; } .cardViewOnly { diff --git a/app/assets/stylesheets/search.scss.erb b/app/assets/stylesheets/search.scss.erb index 8958fd05..4f43ecfa 100644 --- a/app/assets/stylesheets/search.scss.erb +++ b/app/assets/stylesheets/search.scss.erb @@ -93,7 +93,7 @@ .sidebarSearchField { float: left; - width: 380px; + width: 379px; padding: 7px 10px 3px 10px; height: 20px; border-top: 1px solid #BDBDBD; diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..5eb79fae --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + logger.add_tags 'ActionCable', current_user.name + end + + protected + + def find_verified_user + verified_user = User.find_by(id: cookies.signed['user.id']) + if verified_user && cookies.signed['user.expires_at'] > Time.now.getlocal + verified_user + else + reject_unauthorized_connection + end + end + end +end diff --git a/app/channels/map_channel.rb b/app/channels/map_channel.rb new file mode 100644 index 00000000..e7c10b45 --- /dev/null +++ b/app/channels/map_channel.rb @@ -0,0 +1,8 @@ +class MapChannel < ApplicationCable::Channel + # Called when the consumer has successfully + # become a subscriber of this channel. + def subscribed + return unless Pundit.policy(current_user, Map.find(params[:id])).show? + stream_from "map_#{params[:id]}" + end +end diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 6e1e0d77..189ae550 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -124,7 +124,7 @@ class MapsController < ApplicationController end def create_map_params - params.permit(:name, :desc, :permission) + params.permit(:name, :desc, :permission, :source_id) end def update_map_params diff --git a/app/models/map.rb b/app/models/map.rb index 899992c6..4f86d6f8 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Map < ApplicationRecord belongs_to :user + belongs_to :source, class_name: :Map has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping, dependent: :destroy has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy @@ -32,6 +33,7 @@ class Map < ApplicationRecord # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :screenshot, content_type: /\Aimage\/.*\Z/ + after_update :after_updated after_save :update_deferring_topics_and_synapses, if: :permission_changed? delegate :count, to: :topics, prefix: :topic # same as `def topic_count; topics.count; end` @@ -118,6 +120,13 @@ class Map < ApplicationRecord end removed.compact end + + def after_updated + attrs = ['name', 'desc', 'permission'] + if attrs.any? {|k| changed_attributes.key?(k)} + ActionCable.server.broadcast 'map_' + id.to_s, type: 'mapUpdated' + end + end def update_deferring_topics_and_synapses Topic.where(defer_to_map_id: id).update_all(permission: permission) diff --git a/app/models/mapping.rb b/app/models/mapping.rb index 99d23db0..7a82be76 100644 --- a/app/models/mapping.rb +++ b/app/models/mapping.rb @@ -33,8 +33,16 @@ class Mapping < ApplicationRecord if mappable_type == 'Topic' meta = {'x': xloc, 'y': yloc, 'mapping_id': id} Events::TopicAddedToMap.publish!(mappable, map, user, meta) + ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicAdded', topic: mappable.filtered, mapping_id: id elsif mappable_type == 'Synapse' Events::SynapseAddedToMap.publish!(mappable, map, user, meta) + ActionCable.server.broadcast( + 'map_' + map.id.to_s, + type: 'synapseAdded', + synapse: mappable.filtered, + topic1: mappable.topic1.filtered, + topic2: mappable.topic2.filtered, + mapping_id: id) end end @@ -42,6 +50,7 @@ class Mapping < ApplicationRecord if mappable_type == 'Topic' and (xloc_changed? or yloc_changed?) meta = {'x': xloc, 'y': yloc, 'mapping_id': id} Events::TopicMovedOnMap.publish!(mappable, map, updated_by, meta) + ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicMoved', id: mappable.id, mapping_id: id, x: xloc, y: yloc end end @@ -55,8 +64,10 @@ class Mapping < ApplicationRecord meta = {'mapping_id': id} if mappable_type == 'Topic' Events::TopicRemovedFromMap.publish!(mappable, map, updated_by, meta) + ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicRemoved', id: mappable.id, mapping_id: id elsif mappable_type == 'Synapse' Events::SynapseRemovedFromMap.publish!(mappable, map, updated_by, meta) + ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'synapseRemoved', id: mappable.id, mapping_id: id end end end diff --git a/app/models/message.rb b/app/models/message.rb index 682b7e51..de7fd5d1 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -4,6 +4,8 @@ class Message < ApplicationRecord belongs_to :resource, polymorphic: true delegate :name, to: :user, prefix: true + + after_create :after_created def user_image user.image.url @@ -13,4 +15,8 @@ class Message < ApplicationRecord json = super(methods: [:user_name, :user_image]) json end + + def after_created + ActionCable.server.broadcast 'map_' + resource.id.to_s, type: 'messageCreated', message: self.as_json + end end diff --git a/app/models/synapse.rb b/app/models/synapse.rb index d14a18f4..be57dde1 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -38,6 +38,15 @@ class Synapse < ApplicationRecord end end + def filtered + { + id: id, + permission: permission, + user_id: user_id, + collaborator_ids: collaborator_ids + } + end + def as_json(_options = {}) super(methods: [:user_name, :user_image, :collaborator_ids]) end @@ -50,6 +59,9 @@ class Synapse < ApplicationRecord meta = new.merge(old) # we are prioritizing the old values, keeping them meta['changed'] = changed_attributes.keys.select {|k| attrs.include?(k) } Events::SynapseUpdated.publish!(self, user, meta) + maps.each {|map| + ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'synapseUpdated', id: id + } end end end diff --git a/app/models/topic.rb b/app/models/topic.rb index e5ea90ee..90443862 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -90,6 +90,15 @@ class Topic < ApplicationRecord end end + def filtered + { + id: id, + permission: permission, + user_id: user_id, + collaborator_ids: collaborator_ids + } + end + # TODO: move to a decorator? def synapses_csv(output_format = 'array') output = [] @@ -145,6 +154,9 @@ class Topic < ApplicationRecord meta = new.merge(old) # we are prioritizing the old values, keeping them meta['changed'] = changed_attributes.keys.select {|k| attrs.include?(k) } Events::TopicUpdated.publish!(self, user, meta) + maps.each {|map| + ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicUpdated', id: id + } end end end diff --git a/app/serializers/api/v2/map_serializer.rb b/app/serializers/api/v2/map_serializer.rb index ff641c69..7e090d33 100644 --- a/app/serializers/api/v2/map_serializer.rb +++ b/app/serializers/api/v2/map_serializer.rb @@ -18,6 +18,7 @@ module Api def self.embeddable { user: {}, + source: {}, topics: {}, synapses: {}, mappings: {}, diff --git a/app/views/layouts/_foot.html.erb b/app/views/layouts/_foot.html.erb index e309c65f..ea4ab671 100644 --- a/app/views/layouts/_foot.html.erb +++ b/app/views/layouts/_foot.html.erb @@ -10,6 +10,6 @@ Metamaps.Loading.setup() -<%= render :partial => 'layouts/googleanalytics' if Rails.env.production? %> +<%= render :partial => 'layouts/googleanalytics' if ENV["GA_TRACKING_CODE"].present? %> diff --git a/app/views/layouts/_googleanalytics.html.erb b/app/views/layouts/_googleanalytics.html.erb index 11d41495..2acb9501 100644 --- a/app/views/layouts/_googleanalytics.html.erb +++ b/app/views/layouts/_googleanalytics.html.erb @@ -3,16 +3,13 @@ # Google analytics, rendered on every page #%> - diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 23a24c4a..a2f92380 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -15,6 +15,19 @@ <%= yield(:title) %> <%= csrf_meta_tags %> + + <% if controller.class.name == 'MapsController' && @map %> + + + + + + + + + + + <% end %> <%= stylesheet_link_tag "application", :media => "all" %> <%= javascript_include_tag "application" %> diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb index 91dfcf7d..efb3ed68 100644 --- a/app/views/layouts/_templates.html.erb +++ b/app/views/layouts/_templates.html.erb @@ -5,7 +5,7 @@