diff --git a/app/assets/javascripts/lib/cloudcarousel.js b/app/assets/javascripts/lib/cloudcarousel.js index 9f26592a..52a26539 100644 --- a/app/assets/javascripts/lib/cloudcarousel.js +++ b/app/assets/javascripts/lib/cloudcarousel.js @@ -164,16 +164,17 @@ jQuery.browser = browser; // START METAMAPS CODE // Add code that makes tab and shift+tab scroll through metacodes $('.new_topic').bind('keydown',this,function(event){ - if (event.keyCode == 9) { - if (event.shiftKey) { - event.data.rotate(-1) - } else { - event.data.rotate(1) - } - event.preventDefault(); - event.stopPropagation(); - Metamaps.Create.newTopic.metacode = $(items[event.data.frontIndex].image).attr('data-id'); - } + if (event.keyCode == 9 && event.shiftKey) { + $(container).show() + event.data.rotate(-1); + event.preventDefault(); + event.stopPropagation(); + } else if (event.keyCode == 9) { + $(container).show() + event.data.rotate(1); + event.preventDefault(); + event.stopPropagation(); + } }); // END METAMAPS CODE @@ -181,14 +182,14 @@ jQuery.browser = browser; if (options.mouseWheel) { // START METAMAPS CODE - $('body').bind('mousewheel',this,function(event, delta) { - if (Metamaps.Create.newTopic.beingCreated && - !Metamaps.Create.isSwitchingSet && - !Metamaps.Create.newTopic.pinned) { - event.data.rotate(delta); - return false; - } - }); + /*$('body').bind('mousewheel',this,function(event, delta) { + if (Metamaps.Create.newTopic.beingCreated && + !Metamaps.Create.isSwitchingSet && + !Metamaps.Create.newTopic.pinned) { + event.data.rotate(delta); + return false; + } + });*/ // END METAMAPS CODE // ORIGINAL CODE // $(container).bind('mousewheel',this,function(event, delta) { @@ -255,8 +256,11 @@ jQuery.browser = browser; this.showFrontText = function() { if ( items[this.frontIndex] === undefined ) { return; } // Images might not have loaded yet. - $(options.titleBox).html( $(items[this.frontIndex].image).attr('title')); - $(options.altBox).html( $(items[this.frontIndex].image).attr('alt')); + // METAMAPS CODE + Metamaps.Create.newTopic.setMetacode($(items[this.frontIndex].image).attr('data-id')) + // NOT METAMAPS CODE + //$(options.titleBox).html( $(items[this.frontIndex].image).attr('title')); + //$(options.altBox).html( $(items[this.frontIndex].image).attr('alt')); }; this.go = function() @@ -269,7 +273,10 @@ jQuery.browser = browser; this.stop = function() { clearTimeout(this.controlTimer); - this.controlTimer = 0; + this.controlTimer = 0; + // METAMAPS CODE + $(container).hide() + // END METAMAPS CODE }; @@ -390,7 +397,7 @@ jQuery.browser = browser; } // If all images have valid widths and heights, we can stop checking. clearInterval(this.tt); - this.showFrontText(); + // METAMAPS COMMENT this.showFrontText(); this.autoRotate(); this.updateAll(); diff --git a/app/assets/stylesheets/application.scss.erb b/app/assets/stylesheets/application.scss.erb index 988b8241..330091d2 100644 --- a/app/assets/stylesheets/application.scss.erb +++ b/app/assets/stylesheets/application.scss.erb @@ -531,18 +531,48 @@ button.button.btn-no:hover { left: -1000px; display: block; position: absolute; - width: 340px; - margin: -40px 0 0 -35px; z-index: 1; } +.selectedMetacode { + float: left; + background: #FFF; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + padding: 5px 10px 5px 6px; + vertical-align: top; + border-right: 1px solid #DDD; + cursor: pointer; +} +.selectedMetacode:hover, .selectedMetacode.isBeingSelected { + background: #EDEDED; +} +.selectedMetacode img { + width: 24px; + height: 24px; + display: inline-block; + vertical-align: middle; +} +.selectedMetacode span { + vertical-align: middle; +} +.selectedMetacode .downArrow { + display: inline-block; + vertical-align: middle; + width: 0; + height: 0; + border-style: solid; + border-width: 8px 6px 0 6px; + border-color: #777 transparent transparent transparent; + margin-left: 2px; + margin-top: 4px; +} + #new_topic .twitter-typeahead { - position: absolute !important; - top: 45px; - left: 41px; z-index: 9999; width: 256px; height: 34px; + float: left; } .new_topic #topic_name, .new_topic .tt-hint { @@ -552,7 +582,8 @@ button.button.btn-no:hover { margin: 0; padding: 10px 6px; border: none; - border-radius: 2px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; outline: none; font-size: 14px; line-height: 14px; @@ -563,7 +594,7 @@ button.button.btn-no:hover { color: #BDBDBD; } .openMetacodeSwitcher { - display: block; + display: none; height: 16px; width: 16px; background-image: url(<%= asset_data_uri('metacodesettings_sprite.png') %>); @@ -583,8 +614,8 @@ button.button.btn-no:hover { background-image: url(<%= asset_data_uri('pincarousel_sprite.png') %>); position: absolute; z-index: 2; - top: 20px; - right: 16px; + top: -20px; + right: -16px; } .pinCarousel:hover { background-position: 0 -16px; @@ -597,8 +628,14 @@ button.button.btn-no:hover { } #metacodeImg { height: 120px; + width: 380px; + display: none; + position: absolute !important; + top: -30px; + z-index: -1; } #metacodeImgTitle { + display: none; float: left; width: 120px; text-align: center; diff --git a/app/assets/stylesheets/metacode-select.scss.erb b/app/assets/stylesheets/metacode-select.scss.erb new file mode 100644 index 00000000..3e30303a --- /dev/null +++ b/app/assets/stylesheets/metacode-select.scss.erb @@ -0,0 +1,72 @@ +#metacodeSelector { + display: none; +} + +.metacodeSelect { + border-top: 1px solid #DDD; + padding: 0; + + .tabList { + float: left; + background: #FFF; + + div { + border-right: 1px solid #DDD; + border-bottom: 1px solid #DDD; + } + + div:last-child { + border-bottom: none; + } + + div.active { + border-right: none; + background: #EEE; + + .metacodeFilterInput { + background: #EEE; + } + } + + .metacodeFilterInput { + width: 100px; + outline: none; + border: 0; + padding: 8px; + font-size: 14px; + line-height: 14px; + color: #424242; + font-family: 'din-medium', helvetica, sans-serif; + } + + span { + padding: 8px; + display: block; + } + } + + .metacodeList { + float:left; + list-style: none; + background: #FFF; + min-height: 107px; + min-width: 100px; + + li { + padding: 8px; + cursor: pointer; + + &:hover, &.keySelect { + background: #EEE; + } + } + + img { + width: 24px; + height: 24px; + display: inline-block; + vertical-align: middle; + padding-right: 6px; + } + } +} \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1defb323..9cd6172e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true class UsersController < ApplicationController - before_action :require_user, only: [:edit, :update, :updatemetacodes] + before_action :require_user, only: [:edit, :update, :updatemetacodes, :update_metacode_focus] respond_to :html, :json @@ -91,6 +91,16 @@ class UsersController < ApplicationController format.json { render json: @user } end end + + # PUT /user/update_metacode_focus + def update_metacode_focus + @user = current_user + @user.settings.metacode_focus = params[:value] + @user.save + respond_to do |format| + format.json { render json: { success: "success" }} + end + end private diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 96b5a2b2..4bfaad60 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -25,6 +25,10 @@ module ApplicationHelper end @metacodes.sort! { |m1, m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1) end + + def user_metacode + current_user.settings.metacode_focus ? Metacode.find(current_user.settings.metacode_focus.to_i) || user_metacodes()[0] : user_metacodes()[0] + end def user_most_used_metacodes @metacodes = current_user.most_used_metacodes.map { |id| Metacode.find(id) } diff --git a/app/models/user.rb b/app/models/user.rb index f6fcb60e..5675c829 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -66,6 +66,28 @@ class User < ApplicationRecord json['rtype'] = 'mapper' json end + + def recentMetacodes + array = [] + self.topics.sort{|a,b| b.created_at <=> a.created_at }.each do |t| + if array.length < 5 and array.index(t.metacode_id) == nil + array.push(t.metacode_id) + end + end + array + end + + def mostUsedMetacodes + self.topics.to_a.reduce({}) { |memo, topic| + if memo[topic.metacode_id] == nil + memo[topic.metacode_id] = 1 + else + memo[topic.metacode_id] = memo[topic.metacode_id] + 1 + end + + memo + }.to_a.sort{ |a, b| b[1] <=> a[1] }.map{|i| i[0]}.slice(0, 5) + end def all_accessible_maps maps + shared_maps diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index 29ca3948..c2d3696a 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true class UserPreference - attr_accessor :metacodes + attr_accessor :metacodes, :metacode_focus def initialize array = [] @@ -15,5 +15,6 @@ class UserPreference end end @metacodes = array + @metacode_focus = array[0] end end diff --git a/app/views/maps/_newtopic.html.erb b/app/views/maps/_newtopic.html.erb index 8e10c7a7..cc65d6ac 100644 --- a/app/views/maps/_newtopic.html.erb +++ b/app/views/maps/_newtopic.html.erb @@ -11,24 +11,36 @@
+ <% @metacodes = [user_metacode()].concat(user_metacodes()).uniq %> + <% set = metacodeset() %> <% @metacodes.each do |metacode| %> <%= metacode.name %> <% end %> -
- + +
+ + <%= user_metacode().name %> +
+
<%= form.text_field :name, :maxlength => 140, :placeholder => "title..." %> - -
- - +
+
+ <% end %> diff --git a/config/routes.rb b/config/routes.rb index 4dc44c91..8fe56fd6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -104,6 +104,7 @@ Metamaps::Application.routes.draw do end end post 'user/updatemetacodes', to: 'users#updatemetacodes', as: :updatemetacodes + post 'user/update_metacode_focus', to: 'users#update_metacode_focus' namespace :api, path: '/api', default: { format: :json } do namespace :v2, path: '/v2' do diff --git a/frontend/src/Metamaps/Create.js b/frontend/src/Metamaps/Create.js index 20545181..06db01c0 100644 --- a/frontend/src/Metamaps/Create.js +++ b/frontend/src/Metamaps/Create.js @@ -1,6 +1,10 @@ -/* global $, Hogan, Bloodhound */ +/* global Metamaps, $, Hogan, Bloodhound */ + +import React from 'react' +import ReactDOM from 'react-dom' import DataModel from './DataModel' +import MetacodeSelect from '../components/MetacodeSelect' import Mouse from './Mouse' import Selected from './Selected' import Synapse from './Synapse' @@ -17,7 +21,9 @@ const Create = { newSelectedMetacodeNames: [], selectedMetacodes: [], newSelectedMetacodes: [], - init: function() { + recentMetacodes: [], + mostUsedMetacodes: [], + init: function () { var self = Create self.newTopic.init() self.newSynapse.init() @@ -76,7 +82,6 @@ const Create = { } metacodeModels.sort() - $('#metacodeImg, #metacodeImgTitle').empty() $('#metacodeImg').removeData('cloudcarousel') var newMetacodes = '' metacodeModels.each(function(metacode) { @@ -84,13 +89,11 @@ const Create = { }) $('#metacodeImg').empty().append(newMetacodes).CloudCarousel({ - titleBox: $('#metacodeImgTitle'), yRadius: 40, xRadius: 190, xPos: 170, yPos: 40, speed: 0.3, - mouseWheel: true, bringToFront: true }) @@ -140,16 +143,30 @@ const Create = { $('#topic_name').focus() }, newTopic: { - init: function() { - $('#topic_name').keyup(function(e) { - const ESC = 27 + init: function () { + const DOWN_ARROW = 40 + const ESC = 27 + + $('#topic_name').keyup(function (e) { + + Create.newTopic.name = $(this).val() + if (e.which == DOWN_ARROW && !Create.newTopic.name.length) { + Create.newTopic.openSelector() + } if (e.keyCode === ESC) { Create.newTopic.hide() } // if - - Create.newTopic.name = $(this).val() }) + + $('.selectedMetacode').click(function() { + if (Create.newTopic.metacodeSelectorOpen) { + Create.newTopic.hideSelector() + $('#topic_name').focus() + } else Create.newTopic.openSelector() + }) + + Create.newTopic.initSelector() $('.pinCarousel').click(function() { if (Create.newTopic.pinned) { @@ -201,16 +218,15 @@ const Create = { }) } }) + $('#topic_name').click(function() { Create.newTopic.hideSelector() }) // initialize metacode spinner and then hide it $('#metacodeImg').CloudCarousel({ - titleBox: $('#metacodeImgTitle'), yRadius: 40, xRadius: 190, xPos: 170, yPos: 40, speed: 0.3, - mouseWheel: true, bringToFront: true }) $('.new_topic').hide() @@ -219,13 +235,62 @@ const Create = { name: null, newId: 1, beingCreated: false, + metacodeSelectorOpen: false, metacode: null, x: null, y: null, addSynapse: false, pinned: false, - open: function() { - $('#new_topic').fadeIn('fast', function() { + initSelector: function () { + ReactDOM.render( + React.createElement(MetacodeSelect, { + onClick: function (id) { + Create.newTopic.setMetacode(id) + Create.newTopic.hideSelector() + $('#topic_name').focus() + }, + close: function () { + Create.newTopic.hideSelector() + $('#topic_name').focus() + }, + metacodes: DataModel.Metacodes.models, + recent: Create.recentMetacodes, + mostUsed: Create.mostUsedMetacodes + }), + document.getElementById('metacodeSelector') + ) + }, + openSelector: function () { + Create.newTopic.initSelector() + $('#metacodeSelector').show() + Create.newTopic.metacodeSelectorOpen = true + $('.metacodeFilterInput').focus() + $('.selectedMetacode').addClass('isBeingSelected') + }, + hideSelector: function () { + ReactDOM.unmountComponentAtNode(document.getElementById('metacodeSelector')) + $('#metacodeSelector').hide() + Create.newTopic.metacodeSelectorOpen = false + $('.selectedMetacode').removeClass('isBeingSelected') + }, + setMetacode: function (id) { + Create.newTopic.metacode = id + var metacode = DataModel.Metacodes.get(id) + $('.selectedMetacode img').attr('src', metacode.get('icon')) + $('.selectedMetacode span').html(metacode.get('name')) + $.ajax({ + type: 'POST', + dataType: 'json', + url: '/user/update_metacode_focus', + data: { value: id }, + success: function (data) {}, + error: function () { + console.log('failed to save metacode focus') + } + }) + }, + open: function () { + $('#new_topic').fadeIn('fast', function () { $('#topic_name').focus() }) Create.newTopic.beingCreated = true @@ -247,6 +312,7 @@ const Create = { }, reset: function() { $('#topic_name').typeahead('val', '') + Create.newTopic.hideSelector() } }, newSynapse: { diff --git a/frontend/src/components/MetacodeSelect.js b/frontend/src/components/MetacodeSelect.js new file mode 100644 index 00000000..e71a3614 --- /dev/null +++ b/frontend/src/components/MetacodeSelect.js @@ -0,0 +1,168 @@ +/* global $ */ + +import React, { Component, PropTypes } from 'react' + +const ENTER_KEY = 13 +const LEFT_ARROW = 37 +const UP_ARROW = 38 +const RIGHT_ARROW = 39 +const DOWN_ARROW = 40 + +const Metacode = (props) => { + const { m, onClick, underCursor } = props + + return ( +
  • onClick(m.id) } className={ underCursor ? 'keySelect' : '' }> + + { m.get('name') } +
  • + ) +} + +class MetacodeSelect extends Component { + + constructor (props) { + super(props) + this.state = { + filterText: '', + activeTab: 0, + selectingSection: true, + underCursor: 0 + } + } + + componentDidMount() { + const self = this + setTimeout(function() { + $(document.body).on('keyup.metacodeSelect', self.handleKeyUp.bind(self)) + }, 10) + } + + componentWillUnmount() { + $(document.body).off('.metacodeSelect') + } + + changeFilterText (e) { + this.setState({ filterText: e.target.value, underCursor: 0 }) + } + + changeDisplay (activeTab) { + this.setState({ activeTab, underCursor: 0 }) + } + + getSelectMetacodes () { + const { metacodes, recent, mostUsed } = this.props + const { filterText, activeTab } = this.state + + let selectMetacodes = [] + if (activeTab == 0) { // search + selectMetacodes = filterText.length > 1 ? metacodes.filter(m => { + return m.get('name').toLowerCase().search(filterText.toLowerCase()) > -1 + }) : [] + } else if (activeTab == 1) { // recent + selectMetacodes = recent.map(id => { + return metacodes.find(m => m.id == id) + }).filter(m => m) + } else if (activeTab == 2) { // mostUsed + selectMetacodes = mostUsed.map(id => { + return metacodes.find(m => m.id == id) + }).filter(m => m) + } + return selectMetacodes + } + + handleKeyUp (e) { + const { close } = this.props + const { activeTab, underCursor, selectingSection } = this.state + const selectMetacodes = this.getSelectMetacodes() + let nextIndex + + switch (e.which) { + case ENTER_KEY: + if (selectMetacodes.length && !selectingSection) this.resetAndClick(selectMetacodes[underCursor].id) + break + case UP_ARROW: + if (selectingSection && activeTab == 0) { + close() + break + } + else if (selectingSection) { + nextIndex = activeTab - 1 + this.changeDisplay(nextIndex) + break + } + nextIndex = underCursor == 0 ? selectMetacodes.length - 1 : underCursor - 1 + this.setState({ underCursor: nextIndex }) + break + case DOWN_ARROW: + if (selectingSection) { + nextIndex = activeTab == 2 ? 0 : activeTab + 1 + this.changeDisplay(nextIndex) + break + } + nextIndex = underCursor == selectMetacodes.length - 1 ? 0 : underCursor + 1 + this.setState({ underCursor: nextIndex }) + break + case RIGHT_ARROW: + if (selectingSection) this.setState({ selectingSection: false }) + break + case LEFT_ARROW: + if (!selectingSection) this.setState({ selectingSection: true }) + break + } + } + + resetAndClick (id) { + const { onClick } = this.props + this.setState({ filterText: '', underCursor: 0 }) + this.changeDisplay(0) + onClick(id) + } + + render () { + const { onClick, close, recent, mostUsed } = this.props + const { filterText, activeTab, underCursor, selectingSection } = this.state + const selectMetacodes = this.getSelectMetacodes() + return
    +
    +
    { this.changeDisplay(0) }}> + +
    +
    { this.changeDisplay(1) }}> + Recent +
    +
    { this.changeDisplay(2) }}> + Most Used +
    +
    + +
    +
    + } +} + +MetacodeSelect.propTypes = { + onClick: PropTypes.func.isRequired, + close: PropTypes.func.isRequired, + metacodes: PropTypes.array.isRequired, + recent: PropTypes.array.isRequired, + mostUsed: PropTypes.array.isRequired +} + +export default MetacodeSelect +