Merge pull request #482 from metamaps/develop
merge develop into master (version 2.8!)
3
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
|||
#assety stuff
|
||||
realtime/node_modules
|
||||
public/assets
|
||||
public/metamaps_mobile
|
||||
vendor/
|
||||
|
||||
#secrets and config
|
||||
|
@ -19,6 +20,8 @@ vendor/
|
|||
log/*.log
|
||||
tmp
|
||||
|
||||
coverage
|
||||
|
||||
.DS_Store
|
||||
*/.DS_Store
|
||||
.DS_Store?
|
||||
|
|
3
.simplecov
Normal file
|
@ -0,0 +1,3 @@
|
|||
if ENV['COVERAGE'] == 'on'
|
||||
SimpleCov.start 'rails'
|
||||
end
|
9
.travis.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
sudo: false
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.1.3
|
||||
before_script:
|
||||
- export RAILS_ENV=test
|
||||
- cp .example-env .env
|
||||
- bundle exec rake db:create
|
||||
- bundle exec rake db:schema:load
|
11
Gemfile
|
@ -6,7 +6,9 @@ gem 'rails', '4.2.4'
|
|||
gem 'devise'
|
||||
gem 'redis'
|
||||
gem 'pg'
|
||||
gem 'cancancan'
|
||||
gem 'pundit'
|
||||
gem 'pundit_extra'
|
||||
gem 'doorkeeper'
|
||||
gem 'formula'
|
||||
gem 'formtastic'
|
||||
gem 'json'
|
||||
|
@ -15,6 +17,11 @@ gem 'best_in_place' #in-place editing
|
|||
gem 'kaminari' # pagination
|
||||
gem 'uservoice-ruby'
|
||||
gem 'dotenv'
|
||||
gem 'snorlax'
|
||||
gem 'httparty'
|
||||
gem 'active_model_serializers', '~> 0.8.1'
|
||||
gem 'delayed_job', '~> 4.0.2'
|
||||
gem 'delayed_job_active_record', '~> 4.0.1'
|
||||
|
||||
gem 'paperclip'
|
||||
gem 'aws-sdk', '< 2.0'
|
||||
|
@ -42,6 +49,8 @@ group :test do
|
|||
gem 'rspec-rails'
|
||||
gem 'factory_girl_rails'
|
||||
gem 'shoulda-matchers'
|
||||
gem 'simplecov', require: false
|
||||
gem 'json-schema'
|
||||
end
|
||||
|
||||
group :production do #this is used on heroku
|
||||
|
|
157
Gemfile.lock
|
@ -20,6 +20,8 @@ GEM
|
|||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
active_model_serializers (0.8.3)
|
||||
activemodel (>= 3.0)
|
||||
activejob (4.2.4)
|
||||
activesupport (= 4.2.4)
|
||||
globalid (>= 0.3.0)
|
||||
|
@ -36,14 +38,15 @@ GEM
|
|||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
arel (6.0.3)
|
||||
aws-sdk (1.66.0)
|
||||
aws-sdk-v1 (= 1.66.0)
|
||||
aws-sdk-v1 (1.66.0)
|
||||
json (~> 1.4)
|
||||
nokogiri (>= 1.4.4)
|
||||
bcrypt (3.1.10)
|
||||
best_in_place (3.0.3)
|
||||
bcrypt (3.1.11)
|
||||
best_in_place (3.1.0)
|
||||
actionpack (>= 3.2)
|
||||
railties (>= 3.2)
|
||||
better_errors (2.1.1)
|
||||
|
@ -53,24 +56,27 @@ GEM
|
|||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
builder (3.2.2)
|
||||
byebug (5.0.0)
|
||||
columnize (= 0.9.0)
|
||||
cancancan (1.13.1)
|
||||
byebug (8.2.2)
|
||||
climate_control (0.0.3)
|
||||
activesupport (>= 3.0)
|
||||
cocaine (0.5.7)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.0)
|
||||
coffee-rails (4.1.0)
|
||||
coderay (1.1.1)
|
||||
coffee-rails (4.1.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
railties (>= 4.0.0, < 5.1.x)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.9.1.1)
|
||||
columnize (0.9.0)
|
||||
coffee-script-source (1.10.0)
|
||||
concurrent-ruby (1.0.1)
|
||||
debug_inspector (0.0.2)
|
||||
devise (3.5.2)
|
||||
delayed_job (4.0.6)
|
||||
activesupport (>= 3.0, < 5.0)
|
||||
delayed_job_active_record (4.0.3)
|
||||
activerecord (>= 3.0, < 5.0)
|
||||
delayed_job (>= 3.0, < 4.1)
|
||||
devise (3.5.6)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 3.2.6, < 5)
|
||||
|
@ -78,13 +84,16 @@ GEM
|
|||
thread_safe (~> 0.1)
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.2.5)
|
||||
dotenv (2.0.2)
|
||||
docile (1.1.5)
|
||||
doorkeeper (3.1.0)
|
||||
railties (>= 3.2)
|
||||
dotenv (2.1.0)
|
||||
erubis (2.7.0)
|
||||
execjs (2.6.0)
|
||||
ezcrypto (0.7.2)
|
||||
factory_girl (4.5.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.5.0)
|
||||
factory_girl_rails (4.6.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
railties (>= 3.0.0)
|
||||
formtastic (3.1.3)
|
||||
|
@ -93,50 +102,61 @@ GEM
|
|||
rails (> 3.0.0)
|
||||
globalid (0.3.6)
|
||||
activesupport (>= 4.1.0)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.7.0)
|
||||
jbuilder (2.3.2)
|
||||
activesupport (>= 3.0.0, < 5)
|
||||
jbuilder (2.4.1)
|
||||
activesupport (>= 3.0.0, < 5.1)
|
||||
multi_json (~> 1.2)
|
||||
jquery-rails (4.0.5)
|
||||
rails-dom-testing (~> 1.0)
|
||||
jquery-rails (4.1.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-ui-rails (5.0.5)
|
||||
railties (>= 3.2.16)
|
||||
json (1.8.3)
|
||||
json-schema (2.6.1)
|
||||
addressable (~> 2.3.8)
|
||||
kaminari (0.16.3)
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
mail (2.6.4)
|
||||
mime-types (>= 1.16, < 4)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.6.2)
|
||||
mime-types (3.0)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0221)
|
||||
mimemagic (0.3.0)
|
||||
mini_portile (0.6.2)
|
||||
minitest (5.8.2)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.8.4)
|
||||
multi_json (1.11.2)
|
||||
nokogiri (1.6.6.2)
|
||||
mini_portile (~> 0.6.0)
|
||||
oauth (0.4.7)
|
||||
multi_xml (0.5.5)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
oauth (0.5.1)
|
||||
orm_adapter (0.5.0)
|
||||
paperclip (4.3.1)
|
||||
paperclip (4.3.5)
|
||||
activemodel (>= 3.2.0)
|
||||
activesupport (>= 3.2.0)
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (= 0.3.0)
|
||||
pg (0.18.3)
|
||||
pg (0.18.4)
|
||||
pry (0.10.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-byebug (3.2.0)
|
||||
byebug (~> 5.0)
|
||||
pry-byebug (3.3.0)
|
||||
byebug (~> 8.0)
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
pundit (1.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
pundit_extra (0.1.1)
|
||||
quiet_assets (1.1.0)
|
||||
railties (>= 3.1, < 5.0)
|
||||
rack (1.6.4)
|
||||
|
@ -159,61 +179,69 @@ GEM
|
|||
activesupport (>= 4.2.0.beta, < 5.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.2)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails3-jquery-autocomplete (1.0.15)
|
||||
rails (>= 3.2)
|
||||
rails_12factor (0.0.3)
|
||||
rails_serve_static_assets
|
||||
rails_stdout_logging
|
||||
rails_serve_static_assets (0.0.4)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.4)
|
||||
railties (4.2.4)
|
||||
actionpack (= 4.2.4)
|
||||
activesupport (= 4.2.4)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rake (10.4.2)
|
||||
redis (3.2.1)
|
||||
responders (2.1.0)
|
||||
railties (>= 4.2.0, < 5)
|
||||
rspec-core (3.3.2)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-expectations (3.3.1)
|
||||
rake (11.1.1)
|
||||
redis (3.2.2)
|
||||
responders (2.1.1)
|
||||
railties (>= 4.2.0, < 5.1)
|
||||
rspec-core (3.4.4)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-mocks (3.3.2)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-mocks (3.4.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-rails (3.3.3)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-rails (3.4.2)
|
||||
actionpack (>= 3.0, < 4.3)
|
||||
activesupport (>= 3.0, < 4.3)
|
||||
railties (>= 3.0, < 4.3)
|
||||
rspec-core (~> 3.3.0)
|
||||
rspec-expectations (~> 3.3.0)
|
||||
rspec-mocks (~> 3.3.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-support (3.3.0)
|
||||
sass (3.4.19)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
sass (3.4.21)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
shoulda-matchers (3.0.1)
|
||||
shoulda-matchers (3.1.1)
|
||||
activesupport (>= 4.0.0)
|
||||
simplecov (0.11.2)
|
||||
docile (~> 1.1.0)
|
||||
json (~> 1.8)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
slop (3.6.0)
|
||||
sprockets (3.4.0)
|
||||
snorlax (0.1.5)
|
||||
rails (> 4.1)
|
||||
sprockets (3.5.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (2.3.3)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (3.0.4)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.1)
|
||||
tunemygc (1.0.61)
|
||||
tilt (2.0.2)
|
||||
tunemygc (1.0.65)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (2.7.2)
|
||||
|
@ -223,33 +251,40 @@ GEM
|
|||
ezcrypto (>= 0.7.2)
|
||||
json (>= 1.7.5)
|
||||
oauth (>= 0.4.7)
|
||||
warden (1.2.3)
|
||||
warden (1.2.6)
|
||||
rack (>= 1.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.8.1)
|
||||
aws-sdk (< 2.0)
|
||||
best_in_place
|
||||
better_errors
|
||||
binding_of_caller
|
||||
cancancan
|
||||
coffee-rails
|
||||
delayed_job (~> 4.0.2)
|
||||
delayed_job_active_record (~> 4.0.1)
|
||||
devise
|
||||
doorkeeper
|
||||
dotenv
|
||||
factory_girl_rails
|
||||
formtastic
|
||||
formula
|
||||
httparty
|
||||
jbuilder
|
||||
jquery-rails
|
||||
jquery-ui-rails
|
||||
json
|
||||
json-schema
|
||||
kaminari
|
||||
paperclip
|
||||
pg
|
||||
pry-byebug
|
||||
pry-rails
|
||||
pundit
|
||||
pundit_extra
|
||||
quiet_assets
|
||||
rails (= 4.2.4)
|
||||
rails3-jquery-autocomplete
|
||||
|
@ -258,9 +293,11 @@ DEPENDENCIES
|
|||
rspec-rails
|
||||
sass-rails
|
||||
shoulda-matchers
|
||||
simplecov
|
||||
snorlax
|
||||
tunemygc
|
||||
uglifier
|
||||
uservoice-ruby
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
||||
1.11.2
|
||||
|
|
2
Procfile
|
@ -1 +1,3 @@
|
|||
web: bundle exec rails server -p $PORT
|
||||
worker: bundle exec rake jobs:work
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ Metamaps
|
|||
=======
|
||||
|
||||
[](https://gitter.im/metamaps/metamaps_gen002?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
[](https://travis-ci.org/metamaps/metamaps_gen002)
|
||||
|
||||
Welcome to the Metamaps GitHub repo.
|
||||
|
||||
|
|
1
Vagrantfile
vendored
|
@ -16,7 +16,6 @@ sudo apt-get install nodejs -y
|
|||
sudo apt-get install npm -y
|
||||
sudo apt-get install postgresql -y
|
||||
sudo apt-get install libpq-dev -y
|
||||
sudo apt-get install redis-server -y
|
||||
|
||||
# get imagemagick
|
||||
sudo apt-get install imagemagick --fix-missing
|
||||
|
|
BIN
app/assets/images/audio_sprite.png
Normal file
After Width: | Height: | Size: 854 B |
BIN
app/assets/images/camera_sprite.png
Normal file
After Width: | Height: | Size: 780 B |
BIN
app/assets/images/chat32.png
Normal file
After Width: | Height: | Size: 466 B |
BIN
app/assets/images/cursor_sprite.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
app/assets/images/default_profile.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
app/assets/images/ellipsis.gif
Normal file
After Width: | Height: | Size: 220 B |
BIN
app/assets/images/invitepeer16.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
app/assets/images/junto.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/assets/images/junto_spinner_dark.gif
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
app/assets/images/sound_sprite.png
Normal file
After Width: | Height: | Size: 717 B |
BIN
app/assets/images/sounds/sounds.mp3
Normal file
BIN
app/assets/images/sounds/sounds.ogg
Normal file
BIN
app/assets/images/tray_tab.png
Normal file
After Width: | Height: | Size: 331 B |
BIN
app/assets/images/video_sprite.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -20,8 +20,12 @@
|
|||
//= require ./src/Metamaps.Router
|
||||
//= require ./src/Metamaps.Backbone
|
||||
//= require ./src/Metamaps.Views
|
||||
//= require ./src/views/chatView
|
||||
//= require ./src/views/videoView
|
||||
//= require ./src/views/room
|
||||
//= require ./src/JIT
|
||||
//= require ./src/Metamaps
|
||||
//= require ./src/Metamaps.Import
|
||||
//= require ./src/Metamaps.JIT
|
||||
//= require_directory ./shims
|
||||
//= require_directory ./require
|
||||
|
|
2756
app/assets/javascripts/lib/Autolinker.js
Normal file
39
app/assets/javascripts/lib/attachMediaStream.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
var attachMediaStream = function (stream, el, options) {
|
||||
var URL = window.URL;
|
||||
var opts = {
|
||||
autoplay: true,
|
||||
mirror: false,
|
||||
muted: false
|
||||
};
|
||||
var element = el || document.createElement('video');
|
||||
var item;
|
||||
|
||||
if (options) {
|
||||
for (item in options) {
|
||||
opts[item] = options[item];
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.autoplay) element.autoplay = 'autoplay';
|
||||
if (opts.muted) element.muted = true;
|
||||
if (opts.mirror) {
|
||||
['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) {
|
||||
var styleName = prefix ? prefix + 'Transform' : 'transform';
|
||||
element.style[styleName] = 'scaleX(-1)';
|
||||
});
|
||||
}
|
||||
|
||||
// this first one should work most everywhere now
|
||||
// but we have a few fallbacks just in case.
|
||||
if (URL && URL.createObjectURL) {
|
||||
element.src = URL.createObjectURL(stream);
|
||||
} else if (element.srcObject) {
|
||||
element.srcObject = stream;
|
||||
} else if (element.mozSrcObject) {
|
||||
element.mozSrcObject = stream;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
1353
app/assets/javascripts/lib/howler.js
Normal file
9808
app/assets/javascripts/lib/simplewebrtc.bundle.js
Normal file
23
app/assets/javascripts/lib/socketIoConnection.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
function SocketIoConnection(config) {
|
||||
this.connection = io.connect(config.url, config.socketio);
|
||||
}
|
||||
|
||||
SocketIoConnection.prototype.on = function (ev, fn) {
|
||||
this.connection.on(ev, fn);
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.emit = function () {
|
||||
this.connection.emit.apply(this.connection, arguments);
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.removeAllListeners = function () {
|
||||
this.connection.removeAllListeners();
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.getSessionid = function () {
|
||||
return this.connection.socket.sessionid;
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.disconnect = function () {
|
||||
return this.connection.disconnect();
|
||||
};
|
|
@ -206,6 +206,26 @@ Metamaps.Backbone.MapsCollection = Backbone.Collection.extend({
|
|||
}
|
||||
});
|
||||
|
||||
Metamaps.Backbone.Message = Backbone.Model.extend({
|
||||
urlRoot: '/messages',
|
||||
blacklist: ['created_at', 'updated_at'],
|
||||
toJSON: function (options) {
|
||||
return _.omit(this.attributes, this.blacklist);
|
||||
},
|
||||
prepareLiForFilter: function () {
|
||||
/*var li = '';
|
||||
li += '<li data-id="' + this.id.toString() + '">';
|
||||
li += '<img src="' + this.get("image") + '" data-id="' + this.id.toString() + '"';
|
||||
li += ' alt="' + this.get('name') + '" />';
|
||||
li += '<p>' + this.get('name') + '</p></li>';
|
||||
return li;*/
|
||||
}
|
||||
});
|
||||
Metamaps.Backbone.MessageCollection = Backbone.Collection.extend({
|
||||
model: Metamaps.Backbone.Message,
|
||||
url: '/messages'
|
||||
});
|
||||
|
||||
Metamaps.Backbone.Mapper = Backbone.Model.extend({
|
||||
urlRoot: '/users',
|
||||
blacklist: ['created_at', 'updated_at'],
|
||||
|
|
|
@ -43,22 +43,25 @@ Metamaps.Active = {
|
|||
Metamaps.Maps = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
function init() {
|
||||
for (var prop in Metamaps) {
|
||||
|
||||
// this runs the init function within each sub-object on the Metamaps one
|
||||
if (Metamaps.hasOwnProperty(prop) &&
|
||||
Metamaps[prop] != null &&
|
||||
Metamaps[prop].hasOwnProperty('init') &&
|
||||
typeof (Metamaps[prop].init) == 'function'
|
||||
) {
|
||||
Metamaps[prop].init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the famous ui
|
||||
var callFamous = function(){
|
||||
if (Metamaps.Famous) {
|
||||
Metamaps.Famous.build();
|
||||
init();
|
||||
}
|
||||
else {
|
||||
setTimeout(callFamous, 100);
|
||||
|
@ -159,6 +162,7 @@ Metamaps.GlobalUI = {
|
|||
notifyUser: function (message, leaveOpen) {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
||||
function famousReady() {
|
||||
Metamaps.Famous.toast.surf.setContent(message);
|
||||
Metamaps.Famous.toast.show();
|
||||
clearTimeout(self.notifyTimeOut);
|
||||
|
@ -167,6 +171,18 @@ Metamaps.GlobalUI = {
|
|||
Metamaps.Famous.toast.hide();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the famous ui
|
||||
var callFamous = function(){
|
||||
if (Metamaps.Famous && Metamaps.Famous.toast) {
|
||||
famousReady();
|
||||
}
|
||||
else {
|
||||
setTimeout(callFamous, 100);
|
||||
}
|
||||
}
|
||||
callFamous();
|
||||
},
|
||||
clearNotify: function() {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
@ -333,7 +349,6 @@ Metamaps.GlobalUI.Account = {
|
|||
open: function () {
|
||||
var self = Metamaps.GlobalUI.Account;
|
||||
|
||||
Metamaps.Realtime.close();
|
||||
Metamaps.Filter.close();
|
||||
$('.sidebarAccountIcon .tooltipsUnder').addClass('hide');
|
||||
|
||||
|
|
327
app/assets/javascripts/src/Metamaps.Import.js.erb
Normal file
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* Example tab-separated input:
|
||||
* Some fields will be ignored
|
||||
*
|
||||
* Topics
|
||||
* Id Name Metacode X Y Description Link User Permission
|
||||
* 8 topic8 Action -231 131 admin commons
|
||||
* 5 topic Action -229 -131 admin commons
|
||||
* 7 topic7.1 Action -470 -55 hey admin commons
|
||||
* 2 topic2 Event -57 -63 admin commons
|
||||
* 1 topic1 Catalyst -51 50 admin commons
|
||||
* 6 topic6 Action -425 63 admin commons
|
||||
*
|
||||
* Synapses
|
||||
* Topic1 Topic2 Category Description User Permission
|
||||
* 6 2 from-to admin commons
|
||||
* 6 1 from-to admin commons
|
||||
* 6 5 from-to admin commons
|
||||
* 2 7 from-to admin commons
|
||||
* 8 6 from-to admin commons
|
||||
* 8 1 from-to admin commons
|
||||
*
|
||||
*/
|
||||
|
||||
Metamaps.Import = {
|
||||
// note that user is not imported
|
||||
topicWhitelist: [
|
||||
'id', 'name', 'metacode', 'x', 'y', 'description', 'link', 'permission'
|
||||
],
|
||||
synapseWhitelist: [
|
||||
'topic1', 'topic2', 'category', 'desc', 'description', 'permission'
|
||||
],
|
||||
cidMappings: {}, //to be filled by import_id => cid mappings
|
||||
|
||||
init: function() {
|
||||
var self = Metamaps.Import;
|
||||
|
||||
$('body').bind('paste', function(e) {
|
||||
if (e.target.tagName === "INPUT") return;
|
||||
|
||||
var text = e.originalEvent.clipboardData.getData('text/plain');
|
||||
|
||||
var results;
|
||||
if (text[0] === '{') {
|
||||
try {
|
||||
results = JSON.parse(text);
|
||||
} catch (e) {
|
||||
results = false;
|
||||
}
|
||||
} else {
|
||||
results = self.parseTabbedString(text);
|
||||
}
|
||||
if (results === false) return;
|
||||
|
||||
var topics = results.topics;
|
||||
var synapses = results.synapses;
|
||||
|
||||
if (topics.length > 0 || synapses.length > 0) {
|
||||
if (confirm("Are you sure you want to create " + topics.length +
|
||||
" new topics and " + synapses.length + " new synapses?")) {
|
||||
self.importTopics(topics);
|
||||
self.importSynapses(synapses);
|
||||
}//if
|
||||
}//if
|
||||
});
|
||||
},
|
||||
|
||||
abort: function(message) {
|
||||
console.error(message);
|
||||
},
|
||||
|
||||
simplify: function(string) {
|
||||
return string
|
||||
.replace(/(^\s*|\s*$)/g, '')
|
||||
.toLowerCase();
|
||||
},
|
||||
|
||||
parseTabbedString: function(text) {
|
||||
var self = Metamaps.Import;
|
||||
|
||||
// determine line ending and split lines
|
||||
var delim = "\n";
|
||||
if (text.indexOf("\r\n") !== -1) {
|
||||
delim = "\r\n";
|
||||
} else if (text.indexOf("\r") !== -1) {
|
||||
delim = "\r";
|
||||
}//if
|
||||
|
||||
var STATES = {
|
||||
ABORT: -1,
|
||||
UNKNOWN: 0,
|
||||
TOPICS_NEED_HEADERS: 1,
|
||||
SYNAPSES_NEED_HEADERS: 2,
|
||||
TOPICS: 3,
|
||||
SYNAPSES: 4,
|
||||
};
|
||||
|
||||
// state & lines determine parser behaviour
|
||||
var state = STATES.UNKNOWN;
|
||||
var lines = text.split(delim);
|
||||
var results = { topics: [], synapses: [] }
|
||||
var topicHeaders = [];
|
||||
var synapseHeaders = [];
|
||||
|
||||
lines.forEach(function(line_raw, index) {
|
||||
var line = line_raw.split("\t");
|
||||
var noblanks = line.filter(function(elt) {
|
||||
return elt !== "";
|
||||
});
|
||||
switch(state) {
|
||||
case STATES.UNKNOWN:
|
||||
if (noblanks.length === 0) {
|
||||
state = STATES.UNKNOWN;
|
||||
break;
|
||||
} else if (noblanks.length === 1 && self.simplify(line[0]) === 'topics') {
|
||||
state = STATES.TOPICS_NEED_HEADERS;
|
||||
break;
|
||||
} else if (noblanks.length === 1 && self.simplify(line[0]) === 'synapses') {
|
||||
state = STATES.SYNAPSES_NEED_HEADERS;
|
||||
break;
|
||||
}
|
||||
state = STATES.TOPICS_NEED_HEADERS;
|
||||
// FALL THROUGH - if we're not sure what to do, pretend
|
||||
// we're on the TOPICS_NEED_HEADERS state and parse some headers
|
||||
|
||||
case STATES.TOPICS_NEED_HEADERS:
|
||||
if (noblanks.length < 2) {
|
||||
self.abort("Not enough topic headers on line " + index);
|
||||
state = STATES.ABORT;
|
||||
}
|
||||
topicHeaders = line.map(function(header, index) {
|
||||
return header.toLowerCase().replace('description', 'desc');
|
||||
});
|
||||
state = STATES.TOPICS;
|
||||
break;
|
||||
|
||||
case STATES.SYNAPSES_NEED_HEADERS:
|
||||
if (noblanks.length < 2) {
|
||||
self.abort("Not enough synapse headers on line " + index);
|
||||
state = STATES.ABORT;
|
||||
}
|
||||
synapseHeaders = line.map(function(header, index) {
|
||||
return header.toLowerCase().replace('description', 'desc');
|
||||
});
|
||||
state = STATES.SYNAPSES;
|
||||
break;
|
||||
|
||||
case STATES.TOPICS:
|
||||
if (noblanks.length === 0) {
|
||||
state = STATES.UNKNOWN;
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'topics') {
|
||||
state = STATES.TOPICS_NEED_HEADERS;
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'synapses') {
|
||||
state = STATES.SYNAPSES_NEED_HEADERS;
|
||||
} else {
|
||||
var topic = {};
|
||||
line.forEach(function(field, index) {
|
||||
var header = topicHeaders[index];
|
||||
if (self.topicWhitelist.indexOf(header) === -1) return;
|
||||
topic[header] = field;
|
||||
if (['id', 'x', 'y'].indexOf(header) !== -1) {
|
||||
topic[header] = parseInt(topic[header]);
|
||||
}//if
|
||||
});
|
||||
results.topics.push(topic);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATES.SYNAPSES:
|
||||
if (noblanks.length === 0) {
|
||||
state = STATES.UNKNOWN;
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'topics') {
|
||||
state = STATES.TOPICS_NEED_HEADERS;
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'synapses') {
|
||||
state = STATES.SYNAPSES_NEED_HEADERS;
|
||||
} else {
|
||||
var synapse = {};
|
||||
line.forEach(function(field, index) {
|
||||
var header = synapseHeaders[index];
|
||||
if (self.synapseWhitelist.indexOf(header) === -1) return;
|
||||
synapse[header] = field;
|
||||
if (['id', 'topic1', 'topic2'].indexOf(header) !== -1) {
|
||||
synapse[header] = parseInt(synapse[header]);
|
||||
}//if
|
||||
});
|
||||
results.synapses.push(synapse);
|
||||
}
|
||||
break;
|
||||
case STATES.ABORT:
|
||||
;
|
||||
default:
|
||||
self.abort("Invalid state while parsing import data. Check code.");
|
||||
state = STATES.ABORT;
|
||||
}
|
||||
});
|
||||
|
||||
if (state === STATES.ABORT) {
|
||||
return false;
|
||||
} else {
|
||||
return results;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
importTopics: function(parsedTopics) {
|
||||
var self = Metamaps.Import;
|
||||
|
||||
// up to 25 topics: scale 100
|
||||
// up to 81 topics: scale 200
|
||||
// up to 169 topics: scale 300
|
||||
var scale = Math.floor((Math.sqrt(parsedTopics.length) - 1) / 4) * 100;
|
||||
if (scale < 100) scale = 100;
|
||||
var autoX = -scale;
|
||||
var autoY = -scale;
|
||||
|
||||
parsedTopics.forEach(function(topic) {
|
||||
var x, y;
|
||||
if (topic.x && topic.y) {
|
||||
x = topic.x;
|
||||
y = topic.y;
|
||||
} else {
|
||||
x = autoX;
|
||||
y = autoY;
|
||||
autoX += 50;
|
||||
if (autoX > scale) {
|
||||
autoY += 50;
|
||||
autoX = -scale;
|
||||
}
|
||||
}
|
||||
|
||||
self.createTopicWithParameters(
|
||||
topic.name, topic.metacode, topic.permission,
|
||||
topic.desc, topic.link, x, y, topic.id
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
importSynapses: function(parsedSynapses) {
|
||||
var self = Metamaps.Import;
|
||||
|
||||
parsedSynapses.forEach(function(synapse) {
|
||||
//only createSynapseWithParameters once both topics are persisted
|
||||
var topic1 = Metamaps.Topics.get(self.cidMappings[node1_id]);
|
||||
var topic2 = Metamaps.Topics.get(self.cidMappings[node2_id]);
|
||||
var synapse_created = false
|
||||
topic1.once('sync', function() {
|
||||
if (topic1.id && topic2.id && !synapse_created) {
|
||||
synaprse_created = true
|
||||
self.createSynapseWithParameters(
|
||||
synapse.desc, synapse.category, synapse.permission,
|
||||
topic1, topic2
|
||||
);
|
||||
}//if
|
||||
});
|
||||
topic2.once('sync', function() {
|
||||
if (topic1.id && topic2.id && !synapse_created) {
|
||||
synaprse_created = true
|
||||
self.createSynapseWithParameters(
|
||||
synapse.desc, synapse.category, synapse.permission,
|
||||
topic1, topic2
|
||||
);
|
||||
}//if
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
createTopicWithParameters: function(name, metacode_name, permission, desc,
|
||||
link, xloc, yloc, import_id) {
|
||||
var self = Metamaps.Import;
|
||||
$(document).trigger(Metamaps.Map.events.editedByActiveMapper);
|
||||
var metacode = Metamaps.Metacodes.where({name: metacode_name})[0] || null;
|
||||
if (metacode === null) return console.error("metacode not found");
|
||||
|
||||
var topic = new Metamaps.Backbone.Topic({
|
||||
name: name,
|
||||
metacode_id: metacode.id,
|
||||
permission: permission || Metamaps.Active.Map.get('permission'),
|
||||
desc: desc,
|
||||
link: link,
|
||||
});
|
||||
Metamaps.Topics.add(topic);
|
||||
self.cidMappings[import_id] = topic.cid;
|
||||
|
||||
var mapping = new Metamaps.Backbone.Mapping({
|
||||
xloc: xloc,
|
||||
yloc: yloc,
|
||||
mappable_id: topic.cid,
|
||||
mappable_type: "Topic",
|
||||
});
|
||||
Metamaps.Mappings.add(mapping);
|
||||
|
||||
// this function also includes the creation of the topic in the database
|
||||
Metamaps.Topic.renderTopic(mapping, topic, true, true);
|
||||
|
||||
|
||||
Metamaps.Famous.viz.hideInstructions();
|
||||
},
|
||||
|
||||
createSynapseWithParameters: function(description, category, permission,
|
||||
topic1, topic2) {
|
||||
var self = Metamaps.Import;
|
||||
var node1 = topic1.get('node');
|
||||
var node2 = topic2.get('node');
|
||||
|
||||
if (!topic1.id || !topic2.id) {
|
||||
console.error("missing topic id when creating synapse")
|
||||
return;
|
||||
}//if
|
||||
|
||||
var synapse = new Metamaps.Backbone.Synapse({
|
||||
desc: description,
|
||||
category: category,
|
||||
permission: permission,
|
||||
node1_id: topic1.id,
|
||||
node2_id: topic2.id
|
||||
});
|
||||
Metamaps.Synapses.add(synapse);
|
||||
|
||||
var mapping = new Metamaps.Backbone.Mapping({
|
||||
mappable_type: "Synapse",
|
||||
mappable_id: synapse.cid,
|
||||
});
|
||||
Metamaps.Mappings.add(mapping);
|
||||
|
||||
Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, true);
|
||||
},
|
||||
};
|
|
@ -108,7 +108,7 @@ Metamaps.JIT = {
|
|||
_.each(results[1], function (synapse) {
|
||||
mapping = synapse.getMapping();
|
||||
Metamaps.Synapses.remove(synapse);
|
||||
Metamaps.Mappings.remove(mapping);
|
||||
if (Metamaps.Mappings) Metamaps.Mappings.remove(mapping);
|
||||
});
|
||||
|
||||
if (self.vizData.length == 0) {
|
||||
|
@ -869,9 +869,9 @@ Metamaps.JIT = {
|
|||
}
|
||||
// if it's a right click or holding down alt, start synapse creation ->third option is for firefox
|
||||
else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) {
|
||||
if (tempInit == false) {
|
||||
tempNode = node;
|
||||
tempInit = true;
|
||||
if (Metamaps.tempInit == false) {
|
||||
Metamaps.tempNode = node;
|
||||
Metamaps.tempInit = true;
|
||||
|
||||
Metamaps.Create.newTopic.hide();
|
||||
Metamaps.Create.newSynapse.hide();
|
||||
|
@ -887,8 +887,8 @@ Metamaps.JIT = {
|
|||
}
|
||||
} else {
|
||||
Metamaps.Mouse.synapseStartCoordinates = [{
|
||||
x: tempNode.pos.getc().x,
|
||||
y: tempNode.pos.getc().y
|
||||
x: Metamaps.tempNode.pos.getc().x,
|
||||
y: Metamaps.tempNode.pos.getc().y
|
||||
}];
|
||||
}
|
||||
Metamaps.Mouse.synapseEndCoordinates = {
|
||||
|
@ -899,11 +899,11 @@ Metamaps.JIT = {
|
|||
//
|
||||
temp = eventInfo.getNode();
|
||||
if (temp != false && temp.id != node.id && Metamaps.Selected.Nodes.indexOf(temp) == -1) { // this means a Node has been returned
|
||||
tempNode2 = temp;
|
||||
Metamaps.tempNode2 = temp;
|
||||
|
||||
Metamaps.Mouse.synapseEndCoordinates = {
|
||||
x: tempNode2.pos.getc().x,
|
||||
y: tempNode2.pos.getc().y
|
||||
x: Metamaps.tempNode2.pos.getc().x,
|
||||
y: Metamaps.tempNode2.pos.getc().y
|
||||
};
|
||||
|
||||
// before making the highlighted one bigger, make sure all the others are regular size
|
||||
|
@ -913,7 +913,7 @@ Metamaps.JIT = {
|
|||
temp.setData('dim', 35, 'current');
|
||||
Metamaps.Visualize.mGraph.plot();
|
||||
} else if (!temp) {
|
||||
tempNode2 = null;
|
||||
Metamaps.tempNode2 = null;
|
||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
||||
n.setData('dim', 25, 'current');
|
||||
});
|
||||
|
@ -941,10 +941,10 @@ Metamaps.JIT = {
|
|||
}
|
||||
}, // onDragMoveTopicHandler
|
||||
onDragCancelHandler: function (node, eventInfo, e) {
|
||||
tempNode = null;
|
||||
if (tempNode2) tempNode2.setData('dim', 25, 'current');
|
||||
tempNode2 = null;
|
||||
tempInit = false;
|
||||
Metamaps.tempNode = null;
|
||||
if (Metamaps.tempNode2) Metamaps.tempNode2.setData('dim', 25, 'current');
|
||||
Metamaps.tempNode2 = null;
|
||||
Metamaps.tempInit = false;
|
||||
// reset the draw synapse positions to false
|
||||
Metamaps.Mouse.synapseStartCoordinates = [];
|
||||
Metamaps.Mouse.synapseEndCoordinates = null;
|
||||
|
@ -953,27 +953,27 @@ Metamaps.JIT = {
|
|||
onDragEndTopicHandler: function (node, eventInfo, e) {
|
||||
var midpoint = {}, pixelPos, mapping;
|
||||
|
||||
if (tempInit && tempNode2 == null) {
|
||||
if (Metamaps.tempInit && Metamaps.tempNode2 == null) {
|
||||
// this means you want to add a new topic, and then a synapse
|
||||
Metamaps.Create.newTopic.addSynapse = true;
|
||||
Metamaps.Create.newTopic.open();
|
||||
} else if (tempInit && tempNode2 != null) {
|
||||
} else if (Metamaps.tempInit && Metamaps.tempNode2 != null) {
|
||||
// this means you want to create a synapse between two existing topics
|
||||
Metamaps.Create.newTopic.addSynapse = false;
|
||||
Metamaps.Create.newSynapse.topic1id = tempNode.getData('topic').id;
|
||||
Metamaps.Create.newSynapse.topic2id = tempNode2.getData('topic').id;
|
||||
tempNode2.setData('dim', 25, 'current');
|
||||
Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id;
|
||||
Metamaps.Create.newSynapse.topic2id = Metamaps.tempNode2.getData('topic').id;
|
||||
Metamaps.tempNode2.setData('dim', 25, 'current');
|
||||
Metamaps.Visualize.mGraph.plot();
|
||||
midpoint.x = tempNode.pos.getc().x + (tempNode2.pos.getc().x - tempNode.pos.getc().x) / 2;
|
||||
midpoint.y = tempNode.pos.getc().y + (tempNode2.pos.getc().y - tempNode.pos.getc().y) / 2;
|
||||
midpoint.x = Metamaps.tempNode.pos.getc().x + (Metamaps.tempNode2.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2;
|
||||
midpoint.y = Metamaps.tempNode.pos.getc().y + (Metamaps.tempNode2.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2;
|
||||
pixelPos = Metamaps.Util.coordsToPixels(midpoint);
|
||||
$('#new_synapse').css('left', pixelPos.x + "px");
|
||||
$('#new_synapse').css('top', pixelPos.y + "px");
|
||||
Metamaps.Create.newSynapse.open();
|
||||
tempNode = null;
|
||||
tempNode2 = null;
|
||||
tempInit = false;
|
||||
} else if (!tempInit && node && !node.nodeFrom) {
|
||||
Metamaps.tempNode = null;
|
||||
Metamaps.tempNode2 = null;
|
||||
Metamaps.tempInit = false;
|
||||
} else if (!Metamaps.tempInit && node && !node.nodeFrom) {
|
||||
// this means you dragged an existing node, autosave that to the database
|
||||
|
||||
// check whether to save mappings
|
||||
|
@ -1047,14 +1047,13 @@ Metamaps.JIT = {
|
|||
Metamaps.TopicCard.hideCard();
|
||||
Metamaps.SynapseCard.hideCard();
|
||||
Metamaps.Create.newTopic.hide();
|
||||
Metamaps.Create.newSynapse.hide();
|
||||
$('.rightclickmenu').remove();
|
||||
// reset the draw synapse positions to false
|
||||
Metamaps.Mouse.synapseStartCoordinates = [];
|
||||
Metamaps.Mouse.synapseEndCoordinates = null;
|
||||
tempInit = false;
|
||||
tempNode = null;
|
||||
tempNode2 = null;
|
||||
Metamaps.tempInit = false;
|
||||
Metamaps.tempNode = null;
|
||||
Metamaps.tempNode2 = null;
|
||||
if (!e.ctrlKey && !e.shiftKey) {
|
||||
Metamaps.Control.deselectAllEdges();
|
||||
Metamaps.Control.deselectAllNodes();
|
||||
|
@ -1693,8 +1692,8 @@ Metamaps.JIT = {
|
|||
easing = 1; // frictional value
|
||||
|
||||
easing = 1;
|
||||
window.clearInterval(panningInt)
|
||||
panningInt = setInterval(function () {
|
||||
window.clearInterval(Metamaps.panningInt)
|
||||
Metamaps.panningInt = setInterval(function () {
|
||||
myTimer()
|
||||
}, 1);
|
||||
|
||||
|
@ -1703,7 +1702,7 @@ Metamaps.JIT = {
|
|||
$(document).trigger(Metamaps.JIT.events.pan);
|
||||
easing = easing * 0.75;
|
||||
|
||||
if (easing < 0.1) window.clearInterval(panningInt);
|
||||
if (easing < 0.1) window.clearInterval(Metamaps.panningInt);
|
||||
}
|
||||
}, // SmoothPanning
|
||||
renderMidArrow: function (from, to, dim, swap, canvas, placement, newSynapse) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// TODO document this user agent function
|
||||
|
||||
var labelType, useGradients, nativeTextSupport, animate;
|
||||
|
||||
(function () {
|
||||
|
@ -14,11 +16,11 @@ var labelType, useGradients, nativeTextSupport, animate;
|
|||
animate = !(iStuff || !nativeCanvasSupport);
|
||||
})();
|
||||
|
||||
// TODO eliminate these 4 global variables
|
||||
var panningInt; // this variable is used to store a 'setInterval' for the Metamaps.JIT.SmoothPanning() function, so that it can be cleared with window.clearInterval
|
||||
var tempNode = null,
|
||||
tempInit = false,
|
||||
tempNode2 = null;
|
||||
// TODO eliminate these 4 top-level variables
|
||||
Metamaps.panningInt = null;
|
||||
Metamaps.tempNode = null;
|
||||
Metamaps.tempInit = false;
|
||||
Metamaps.tempNode2 = null;
|
||||
|
||||
Metamaps.Settings = {
|
||||
embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages
|
||||
|
@ -375,7 +377,7 @@ Metamaps.Backbone.init = function () {
|
|||
mappable_id: this.isNew() ? this.cid : this.id
|
||||
});
|
||||
},
|
||||
createEdge: function () {
|
||||
createEdge: function (providedMapping) {
|
||||
var mapping, mappingID;
|
||||
var synapseID = this.isNew() ? this.cid : this.id;
|
||||
|
||||
|
@ -389,7 +391,7 @@ Metamaps.Backbone.init = function () {
|
|||
};
|
||||
|
||||
if (Metamaps.Active.Map) {
|
||||
mapping = this.getMapping();
|
||||
mapping = providedMapping || this.getMapping();
|
||||
mappingID = mapping.isNew() ? mapping.cid : mapping.id;
|
||||
edge.data.$mappings = [];
|
||||
edge.data.$mappingIDs = [mappingID];
|
||||
|
@ -736,10 +738,6 @@ Metamaps.Create = {
|
|||
init: function () {
|
||||
var self = Metamaps.Create.newSynapse;
|
||||
|
||||
$('#synapse_desc').keyup(function () {
|
||||
Metamaps.Create.newSynapse.description = $(this).val();
|
||||
});
|
||||
|
||||
var synapseBloodhound = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
|
@ -795,6 +793,22 @@ Metamaps.Create = {
|
|||
}]
|
||||
);
|
||||
|
||||
$('#synapse_desc').keyup(function (e) {
|
||||
var ESC = 27, BACKSPACE = 8, DELETE = 46;
|
||||
if (e.keyCode === BACKSPACE && $(this).val() === "" ||
|
||||
e.keyCode === DELETE && $(this).val() === "" ||
|
||||
e.keyCode === ESC) {
|
||||
Metamaps.Create.newSynapse.hide();
|
||||
}//if
|
||||
Metamaps.Create.newSynapse.description = $(this).val();
|
||||
});
|
||||
|
||||
$('#synapse_desc').focusout(function() {
|
||||
if (Metamaps.Create.newSynapse.beingCreated) {
|
||||
Metamaps.Synapse.createSynapseLocally();
|
||||
}
|
||||
});
|
||||
|
||||
$('#synapse_desc').bind('typeahead:select', function (event, datum, dataset) {
|
||||
if (datum.id) { // if they clicked on an existing synapse get it
|
||||
Metamaps.Synapse.getSynapseFromAutocomplete(datum.id);
|
||||
|
@ -811,7 +825,7 @@ Metamaps.Create = {
|
|||
topic2id: null,
|
||||
newSynapseId: null,
|
||||
open: function () {
|
||||
$('#new_synapse').fadeIn('fast', function () {
|
||||
$('#new_synapse').fadeIn(100, function () {
|
||||
$('#synapse_desc').focus();
|
||||
});
|
||||
Metamaps.Create.newSynapse.beingCreated = true;
|
||||
|
@ -1910,11 +1924,17 @@ Metamaps.Util = {
|
|||
*
|
||||
*/
|
||||
Metamaps.Realtime = {
|
||||
videoId: 'video-wrapper',
|
||||
socket: null,
|
||||
isOpen: false,
|
||||
changing: false,
|
||||
webrtc: null,
|
||||
readyToCall: false,
|
||||
mappersOnMap: {},
|
||||
status: true, // stores whether realtime is True/On or False/Off
|
||||
disconnected: false,
|
||||
chatOpen: false,
|
||||
status: true, // stores whether realtime is True/On or False/Off,
|
||||
broadcastingStatus: false,
|
||||
inConversation: false,
|
||||
localVideo: null,
|
||||
init: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
|
@ -1927,50 +1947,150 @@ Metamaps.Realtime = {
|
|||
$(".rtOn").click(reenableRealtime);
|
||||
$(".rtOff").click(turnOff);
|
||||
|
||||
$('.sidebarCollaborateIcon').click(self.toggleBox);
|
||||
$('.sidebarCollaborateBox').click(function(event){
|
||||
event.stopPropagation();
|
||||
});
|
||||
$('body').click(self.close);
|
||||
self.addJuntoListeners();
|
||||
|
||||
self.socket = io.connect('<%= ENV['REALTIME_SERVER'] %>');
|
||||
self.socket = new SocketIoConnection({ url: '<%= ENV['REALTIME_SERVER'] %>' });
|
||||
self.socket.on('connect', function () {
|
||||
console.log('connected');
|
||||
if (!self.disconnected) {
|
||||
self.startActiveMap();
|
||||
} else self.disconnected = false;
|
||||
});
|
||||
self.socket.on('disconnect', function () {
|
||||
self.disconnected = true;
|
||||
});
|
||||
|
||||
if (Metamaps.Active.Mapper) {
|
||||
|
||||
self.webrtc = new SimpleWebRTC({
|
||||
connection: self.socket,
|
||||
localVideoEl: self.videoId,
|
||||
remoteVideosEl: '',
|
||||
detectSpeakingEvents: true,
|
||||
autoAdjustMic: false, //true,
|
||||
autoRequestMedia: false,
|
||||
localVideo: {
|
||||
autoplay: true,
|
||||
mirror: true,
|
||||
muted: true
|
||||
},
|
||||
media: {
|
||||
video: true,
|
||||
audio: true
|
||||
},
|
||||
nick: Metamaps.Active.Mapper.id
|
||||
});
|
||||
|
||||
var
|
||||
$video = $('<video></video>').attr('id', self.videoId);
|
||||
self.localVideo = {
|
||||
$video: $video,
|
||||
view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, {
|
||||
DOUBLE_CLICK_TOLERANCE: 200,
|
||||
avatar: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : ''
|
||||
})
|
||||
};
|
||||
|
||||
self.room = new Metamaps.Views.room({
|
||||
webrtc: self.webrtc,
|
||||
socket: self.socket,
|
||||
username: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('name') : '',
|
||||
image: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '',
|
||||
room: 'global',
|
||||
$video: self.localVideo.$video,
|
||||
myVideoView: self.localVideo.view,
|
||||
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
||||
});
|
||||
self.room.videoAdded(self.handleVideoAdded);
|
||||
|
||||
self.room.chat.$container.hide();
|
||||
$('body').prepend(self.room.chat.$container);
|
||||
} // if Metamaps.Active.Mapper
|
||||
},
|
||||
addJuntoListeners: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
$(document).on(Metamaps.Views.chatView.events.openTray, function () {
|
||||
$('.main').addClass('compressed');
|
||||
self.chatOpen = true;
|
||||
self.positionPeerIcons();
|
||||
});
|
||||
$(document).on(Metamaps.Views.chatView.events.closeTray, function () {
|
||||
$('.main').removeClass('compressed');
|
||||
self.chatOpen = false;
|
||||
self.positionPeerIcons();
|
||||
});
|
||||
$(document).on(Metamaps.Views.chatView.events.videosOn, function () {
|
||||
$('#wrapper').removeClass('hideVideos');
|
||||
});
|
||||
$(document).on(Metamaps.Views.chatView.events.videosOff, function () {
|
||||
$('#wrapper').addClass('hideVideos');
|
||||
});
|
||||
$(document).on(Metamaps.Views.chatView.events.cursorsOn, function () {
|
||||
$('#wrapper').removeClass('hideCursors');
|
||||
});
|
||||
$(document).on(Metamaps.Views.chatView.events.cursorsOff, function () {
|
||||
$('#wrapper').addClass('hideCursors');
|
||||
});
|
||||
},
|
||||
toggleBox: function (event) {
|
||||
handleVideoAdded: function (v, id) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
if (self.isOpen) self.close();
|
||||
else self.open();
|
||||
|
||||
event.stopPropagation();
|
||||
self.positionVideos();
|
||||
v.setParent($('#wrapper'));
|
||||
v.$container.find('.video-cutoff').css({
|
||||
border: '4px solid ' + self.mappersOnMap[id].color
|
||||
});
|
||||
$('#wrapper').append(v.$container);
|
||||
},
|
||||
open: function () {
|
||||
positionVideos: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
var videoIds = Object.keys(self.room.videos);
|
||||
var numOfVideos = videoIds.length;
|
||||
var numOfVideosToPosition = _.filter(videoIds, function (id) {
|
||||
return !self.room.videos[id].manuallyPositioned;
|
||||
}).length;
|
||||
|
||||
Metamaps.GlobalUI.Account.close();
|
||||
Metamaps.Filter.close();
|
||||
$('.sidebarCollaborateIcon div').addClass('hide');
|
||||
var screenHeight = $(document).height();
|
||||
var screenWidth = $(document).width();
|
||||
var topExtraPadding = 20;
|
||||
var topPadding = 30;
|
||||
var leftPadding = 30;
|
||||
var videoHeight = 150;
|
||||
var videoWidth = 180;
|
||||
var column = 0;
|
||||
var row = 0;
|
||||
var yFormula = function () {
|
||||
var y = topExtraPadding + (topPadding + videoHeight)*row + topPadding;
|
||||
if (y + videoHeight > screenHeight) {
|
||||
row = 0;
|
||||
column += 1;
|
||||
y = yFormula();
|
||||
}
|
||||
row++;
|
||||
return y;
|
||||
};
|
||||
var xFormula = function () {
|
||||
var x = (leftPadding + videoWidth)*column + leftPadding;
|
||||
return x;
|
||||
};
|
||||
|
||||
if (!self.isOpen && !self.changing) {
|
||||
self.changing = true;
|
||||
$('.sidebarCollaborateBox').fadeIn(200, function () {
|
||||
self.changing = false;
|
||||
self.isOpen = true;
|
||||
// do self first
|
||||
var myVideo = Metamaps.Realtime.localVideo.view;
|
||||
if (!myVideo.manuallyPositioned) {
|
||||
myVideo.$container.css({
|
||||
top: yFormula() + 'px',
|
||||
left: xFormula() + 'px'
|
||||
});
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
$(".sidebarCollaborateIcon div").removeClass('hide');
|
||||
if (!self.changing) {
|
||||
self.changing = true;
|
||||
$('.sidebarCollaborateBox').fadeOut(200, function () {
|
||||
self.changing = false;
|
||||
self.isOpen = false;
|
||||
videoIds.forEach(function (id) {
|
||||
var video = self.room.videos[id];
|
||||
if (!video.manuallyPositioned) {
|
||||
video.$container.css({
|
||||
top: yFormula() + 'px',
|
||||
left: xFormula() + 'px'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
startActiveMap: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
@ -1986,6 +2106,7 @@ Metamaps.Realtime = {
|
|||
else if (publicMap) {
|
||||
self.attachMapListener();
|
||||
}
|
||||
self.room.addMessages(new Metamaps.Backbone.MessageCollection(Metamaps.Messages), true);
|
||||
}
|
||||
},
|
||||
endActiveMap: function () {
|
||||
|
@ -1993,9 +2114,13 @@ Metamaps.Realtime = {
|
|||
|
||||
$(document).off('mousemove');
|
||||
self.socket.removeAllListeners();
|
||||
if (self.inConversation) self.leaveCall();
|
||||
self.socket.emit('endMapperNotify');
|
||||
$(".collabCompass").remove();
|
||||
self.status = false;
|
||||
self.room.leave();
|
||||
self.room.chat.$container.hide();
|
||||
self.room.chat.close();
|
||||
},
|
||||
reenableRealtime: function() {
|
||||
var confirmString = "The layout of your map has fallen out of sync with the saved copy. ";
|
||||
|
@ -2011,24 +2136,257 @@ Metamaps.Realtime = {
|
|||
var self = Metamaps.Realtime;
|
||||
|
||||
if (notify) self.sendRealtimeOn();
|
||||
$(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn');
|
||||
$('.rtOn').addClass('active');
|
||||
$('.rtOff').removeClass('active');
|
||||
//$(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn');
|
||||
//$('.rtOn').addClass('active');
|
||||
//$('.rtOff').removeClass('active');
|
||||
self.status = true;
|
||||
$(".sidebarCollaborateIcon").addClass("blue");
|
||||
//$(".sidebarCollaborateIcon").addClass("blue");
|
||||
$(".collabCompass").show();
|
||||
self.room.chat.$container.show();
|
||||
self.room.room = 'map-' + Metamaps.Active.Map.id;
|
||||
self.checkForACallToJoin();
|
||||
|
||||
self.activeMapper = {
|
||||
id: Metamaps.Active.Mapper.id,
|
||||
name: Metamaps.Active.Mapper.get('name'),
|
||||
username: Metamaps.Active.Mapper.get('name'),
|
||||
image: Metamaps.Active.Mapper.get('image'),
|
||||
color: Metamaps.Util.getPastelColor(),
|
||||
self: true
|
||||
};
|
||||
self.localVideo.view.$container.find('.video-cutoff').css({
|
||||
border: '4px solid ' + self.activeMapper.color
|
||||
});
|
||||
self.room.chat.addParticipant(self.activeMapper);
|
||||
},
|
||||
checkForACallToJoin: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
self.socket.emit('checkForCall', { room: self.room.room, mapid: Metamaps.Active.Map.id });
|
||||
},
|
||||
promptToJoin: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
var notifyText = 'There\'s a conversation happening, want to join?';
|
||||
notifyText += ' <button type="button" class="toast-button button" onclick="Metamaps.Realtime.joinCall()">Yes</button>';
|
||||
notifyText += ' <button type="button" class="toast-button button btn-no" onclick="Metamaps.GlobalUI.clearNotify()">No</button>';
|
||||
Metamaps.GlobalUI.notifyUser(notifyText, true);
|
||||
self.room.conversationInProgress();
|
||||
},
|
||||
conversationHasBegun: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
if (self.inConversation) return;
|
||||
var notifyText = 'There\'s a conversation starting, want to join?';
|
||||
notifyText += ' <button type="button" class="toast-button button" onclick="Metamaps.Realtime.joinCall()">Yes</button>';
|
||||
notifyText += ' <button type="button" class="toast-button button btn-no" onclick="Metamaps.GlobalUI.clearNotify()">No</button>';
|
||||
Metamaps.GlobalUI.notifyUser(notifyText, true);
|
||||
self.room.conversationInProgress();
|
||||
},
|
||||
countOthersInConversation: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
var count = 0;
|
||||
|
||||
for (var key in self.mappersOnMap) {
|
||||
if (self.mappersOnMap[key].inConversation) count++;
|
||||
}
|
||||
return count;
|
||||
},
|
||||
mapperJoinedCall: function (id) {
|
||||
var self = Metamaps.Realtime;
|
||||
var mapper = self.mappersOnMap[id];
|
||||
|
||||
if (mapper) {
|
||||
if (self.inConversation) {
|
||||
var username = mapper.name;
|
||||
var notifyText = username + ' joined the call';
|
||||
Metamaps.GlobalUI.notifyUser(notifyText);
|
||||
}
|
||||
|
||||
mapper.inConversation = true;
|
||||
self.room.chat.mapperJoinedCall(id);
|
||||
}
|
||||
},
|
||||
mapperLeftCall: function (id) {
|
||||
var self = Metamaps.Realtime;
|
||||
var mapper = self.mappersOnMap[id];
|
||||
|
||||
if (mapper) {
|
||||
if (self.inConversation) {
|
||||
var username = mapper.name;
|
||||
var notifyText = username + ' left the call';
|
||||
Metamaps.GlobalUI.notifyUser(notifyText);
|
||||
}
|
||||
|
||||
mapper.inConversation = false;
|
||||
self.room.chat.mapperLeftCall(id);
|
||||
|
||||
if ((self.inConversation && self.countOthersInConversation() === 0) ||
|
||||
(!self.inConversation && self.countOthersInConversation() === 1)) {
|
||||
self.callEnded();
|
||||
}
|
||||
}
|
||||
},
|
||||
callEnded: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
self.room.conversationEnding();
|
||||
self.room.leaveVideoOnly();
|
||||
self.inConversation = false;
|
||||
self.localVideo.view.$container.hide().css({
|
||||
top: '72px',
|
||||
left: '30px'
|
||||
});
|
||||
self.localVideo.view.audioOn();
|
||||
self.localVideo.view.videoOn();
|
||||
self.webrtc.webrtc.localStreams.forEach(function (stream) {
|
||||
stream.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
});
|
||||
});
|
||||
self.webrtc.webrtc.localStreams = [];
|
||||
},
|
||||
invitedToCall: function (inviter) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
var username = self.mappersOnMap[inviter].name;
|
||||
var notifyText = username + ' is suggesting a video call. What do you think?';
|
||||
notifyText += ' <button type="button" class="toast-button button" onclick="Metamaps.Realtime.acceptCall(' + inviter + ')">Yes</button>';
|
||||
notifyText += ' <button type="button" class="toast-button button btn-no" onclick="Metamaps.Realtime.denyCall(' + inviter + ')">No</button>';
|
||||
Metamaps.GlobalUI.notifyUser(notifyText, true);
|
||||
},
|
||||
invitedToJoin: function (inviter) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
var username = self.mappersOnMap[inviter].name;
|
||||
var notifyText = username + ' is inviting you to the conversation. Join?';
|
||||
notifyText += ' <button type="button" class="toast-button button" onclick="Metamaps.Realtime.joinCall()">Yes</button>';
|
||||
notifyText += ' <button type="button" class="toast-button button btn-no" onclick="Metamaps.Realtime.denyInvite(' + inviter + ')">No</button>';
|
||||
Metamaps.GlobalUI.notifyUser(notifyText, true);
|
||||
},
|
||||
acceptCall: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
self.socket.emit('callAccepted', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
invited: Metamaps.Active.Mapper.id,
|
||||
inviter: userid
|
||||
});
|
||||
self.joinCall();
|
||||
Metamaps.GlobalUI.clearNotify();
|
||||
},
|
||||
denyCall: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
self.socket.emit('callDenied', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
invited: Metamaps.Active.Mapper.id,
|
||||
inviter: userid
|
||||
});
|
||||
Metamaps.GlobalUI.clearNotify();
|
||||
},
|
||||
denyInvite: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
self.socket.emit('inviteDenied', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
invited: Metamaps.Active.Mapper.id,
|
||||
inviter: userid
|
||||
});
|
||||
Metamaps.GlobalUI.clearNotify();
|
||||
},
|
||||
inviteACall: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
self.socket.emit('inviteACall', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
inviter: Metamaps.Active.Mapper.id,
|
||||
invited: userid
|
||||
});
|
||||
self.room.chat.invitationPending(userid);
|
||||
Metamaps.GlobalUI.clearNotify();
|
||||
},
|
||||
inviteToJoin: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
self.socket.emit('inviteToJoin', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
inviter: Metamaps.Active.Mapper.id,
|
||||
invited: userid
|
||||
});
|
||||
self.room.chat.invitationPending(userid);
|
||||
},
|
||||
callAccepted: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
var username = self.mappersOnMap[userid].name;
|
||||
Metamaps.GlobalUI.notifyUser('Conversation starting...');
|
||||
self.joinCall();
|
||||
self.room.chat.invitationAnswered(userid);
|
||||
},
|
||||
callDenied: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
var username = self.mappersOnMap[userid].name;
|
||||
Metamaps.GlobalUI.notifyUser(username + ' didn\'t accept your invite.');
|
||||
self.room.chat.invitationAnswered(userid);
|
||||
},
|
||||
inviteDenied: function (userid) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
var username = self.mappersOnMap[userid].name;
|
||||
Metamaps.GlobalUI.notifyUser(username + ' didn\'t accept your invite.');
|
||||
self.room.chat.invitationAnswered(userid);
|
||||
},
|
||||
joinCall: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
self.webrtc.off('readyToCall');
|
||||
self.webrtc.once('readyToCall', function () {
|
||||
self.videoInitialized = true;
|
||||
self.readyToCall = true;
|
||||
self.localVideo.view.manuallyPositioned = false;
|
||||
self.positionVideos();
|
||||
self.localVideo.view.$container.show();
|
||||
if (self.localVideo && self.status) {
|
||||
$('#wrapper').append(self.localVideo.view.$container);
|
||||
}
|
||||
self.room.join();
|
||||
});
|
||||
self.inConversation = true;
|
||||
self.socket.emit('mapperJoinedCall', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
id: Metamaps.Active.Mapper.id
|
||||
});
|
||||
self.webrtc.startLocalVideo();
|
||||
Metamaps.GlobalUI.clearNotify();
|
||||
self.room.chat.mapperJoinedCall(Metamaps.Active.Mapper.id);
|
||||
},
|
||||
leaveCall: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
self.socket.emit('mapperLeftCall', {
|
||||
mapid: Metamaps.Active.Map.id,
|
||||
id: Metamaps.Active.Mapper.id
|
||||
});
|
||||
|
||||
self.room.chat.mapperLeftCall(Metamaps.Active.Mapper.id);
|
||||
self.room.leaveVideoOnly();
|
||||
self.inConversation = false;
|
||||
self.localVideo.view.$container.hide();
|
||||
|
||||
// if there's only two people in the room, and we're leaving
|
||||
// we should shut down the call locally
|
||||
if (self.countOthersInConversation() === 1) {
|
||||
self.callEnded();
|
||||
}
|
||||
},
|
||||
turnOff: function (silent) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
if (self.status) {
|
||||
if (!silent) self.sendRealtimeOff();
|
||||
$(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff');
|
||||
$('.rtOn').removeClass('active');
|
||||
$('.rtOff').addClass('active');
|
||||
//$(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff');
|
||||
//$('.rtOn').removeClass('active');
|
||||
//$('.rtOff').addClass('active');
|
||||
self.status = false;
|
||||
$(".sidebarCollaborateIcon").removeClass("blue");
|
||||
//$(".sidebarCollaborateIcon").removeClass("blue");
|
||||
$(".collabCompass").hide();
|
||||
$('#' + self.videoId).remove();
|
||||
}
|
||||
},
|
||||
setupSocket: function () {
|
||||
|
@ -2043,6 +2401,19 @@ Metamaps.Realtime = {
|
|||
mapid: Metamaps.Active.Map.id
|
||||
});
|
||||
|
||||
socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToCall', self.invitedToCall); // new call
|
||||
socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToJoin', self.invitedToJoin); // call already in progress
|
||||
socket.on(myId + '-' + Metamaps.Active.Map.id + '-callAccepted', self.callAccepted);
|
||||
socket.on(myId + '-' + Metamaps.Active.Map.id + '-callDenied', self.callDenied);
|
||||
socket.on(myId + '-' + Metamaps.Active.Map.id + '-inviteDenied', self.inviteDenied);
|
||||
|
||||
// receive word that there's a conversation in progress
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-callInProgress', self.promptToJoin);
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-callStarting', self.conversationHasBegun);
|
||||
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall);
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-mapperLeftCall', self.mapperLeftCall);
|
||||
|
||||
// if you're the 'new guy' update your list with who's already online
|
||||
socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList);
|
||||
|
||||
|
@ -2064,6 +2435,9 @@ Metamaps.Realtime = {
|
|||
//
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-newTopic', self.newTopic);
|
||||
|
||||
//
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-newMessage', self.newMessage);
|
||||
|
||||
//
|
||||
socket.on('maps-' + Metamaps.Active.Map.id + '-removeTopic', self.removeTopic);
|
||||
|
||||
|
@ -2145,6 +2519,11 @@ Metamaps.Realtime = {
|
|||
};
|
||||
$(document).on(Metamaps.JIT.events.removeSynapse, sendRemoveSynapse);
|
||||
|
||||
var sendNewMessage = function (event, data) {
|
||||
self.sendNewMessage(data);
|
||||
};
|
||||
$(document).on(Metamaps.Views.room.events.newMessage, sendNewMessage);
|
||||
|
||||
},
|
||||
attachMapListener: function(){
|
||||
var self = Metamaps.Realtime;
|
||||
|
@ -2186,31 +2565,22 @@ Metamaps.Realtime = {
|
|||
// data.userrealtime
|
||||
|
||||
self.mappersOnMap[data.userid] = {
|
||||
id: data.userid,
|
||||
name: data.username,
|
||||
username: data.username,
|
||||
image: data.userimage,
|
||||
color: Metamaps.Util.getPastelColor(),
|
||||
realtime: data.userrealtime,
|
||||
inConversation: data.userinconversation,
|
||||
coords: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var onOff = data.userrealtime ? "On" : "Off";
|
||||
var mapperListItem = '<li id="mapper';
|
||||
mapperListItem += data.userid;
|
||||
mapperListItem += '" class="rtMapper littleRt';
|
||||
mapperListItem += onOff;
|
||||
mapperListItem += '">';
|
||||
mapperListItem += '<img style="border: 2px solid ' + self.mappersOnMap[data.userid].color + ';"';
|
||||
mapperListItem += ' src="' + data.userimage + '" width="24" height="24" class="rtUserImage" />';
|
||||
mapperListItem += data.username;
|
||||
mapperListItem += '<div class="littleJuntoIcon"></div>';
|
||||
mapperListItem += '</li>';
|
||||
|
||||
if (data.userid !== Metamaps.Active.Mapper.id) {
|
||||
$('#mapper' + data.userid).remove();
|
||||
$('.realtimeMapperList ul').append(mapperListItem);
|
||||
self.room.chat.addParticipant(self.mappersOnMap[data.userid]);
|
||||
if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid);
|
||||
|
||||
// create a div for the collaborators compass
|
||||
self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status);
|
||||
|
@ -2224,9 +2594,12 @@ Metamaps.Realtime = {
|
|||
// data.username
|
||||
// data.userimage
|
||||
// data.coords
|
||||
var firstOtherPerson = Object.keys(self.mappersOnMap).length === 0;
|
||||
|
||||
self.mappersOnMap[data.userid] = {
|
||||
id: data.userid,
|
||||
name: data.username,
|
||||
username: data.username,
|
||||
image: data.userimage,
|
||||
color: Metamaps.Util.getPastelColor(),
|
||||
realtime: true,
|
||||
|
@ -2238,19 +2611,16 @@ Metamaps.Realtime = {
|
|||
|
||||
// create an item for them in the realtime box
|
||||
if (data.userid !== Metamaps.Active.Mapper.id && self.status) {
|
||||
var mapperListItem = '<li id="mapper' + data.userid + '" class="rtMapper littleRtOn">';
|
||||
mapperListItem += '<img style="border: 2px solid ' + self.mappersOnMap[data.userid].color + ';"';
|
||||
mapperListItem += ' src="' + data.userimage + '" width="24" height="24" class="rtUserImage" />';
|
||||
mapperListItem += data.username;
|
||||
mapperListItem += '<div class="littleJuntoIcon"></div>';
|
||||
mapperListItem += '</li>';
|
||||
$('#mapper' + data.userid).remove();
|
||||
$('.realtimeMapperList ul').append(mapperListItem);
|
||||
self.room.chat.addParticipant(self.mappersOnMap[data.userid]);
|
||||
|
||||
// create a div for the collaborators compass
|
||||
self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status);
|
||||
|
||||
Metamaps.GlobalUI.notifyUser(data.username + ' just joined the map');
|
||||
var notifyMessage = data.username + ' just joined the map';
|
||||
if (firstOtherPerson) {
|
||||
notifyMessage += ' <button type="button" class="toast-button button" onclick="Metamaps.Realtime.inviteACall(' + data.userid + ')">Suggest A Video Call</button>';
|
||||
}
|
||||
Metamaps.GlobalUI.notifyUser(notifyMessage);
|
||||
|
||||
// send this new mapper back your details, and the awareness that you've loaded the map
|
||||
var update = {
|
||||
|
@ -2259,6 +2629,7 @@ Metamaps.Realtime = {
|
|||
userimage: Metamaps.Active.Mapper.get("image"),
|
||||
userid: Metamaps.Active.Mapper.id,
|
||||
userrealtime: self.status,
|
||||
userinconversation: self.inConversation,
|
||||
mapid: Metamaps.Active.Map.id
|
||||
};
|
||||
socket.emit('updateNewMapperList', update);
|
||||
|
@ -2291,10 +2662,16 @@ Metamaps.Realtime = {
|
|||
|
||||
delete self.mappersOnMap[data.userid];
|
||||
|
||||
$('#mapper' + data.userid).remove();
|
||||
//$('#mapper' + data.userid).remove();
|
||||
$('#compass' + data.userid).remove();
|
||||
self.room.chat.removeParticipant(data.username);
|
||||
|
||||
Metamaps.GlobalUI.notifyUser(data.username + ' just left the map');
|
||||
|
||||
if ((self.inConversation && self.countOthersInConversation() === 0) ||
|
||||
(!self.inConversation && self.countOthersInConversation() === 1)) {
|
||||
self.callEnded();
|
||||
}
|
||||
},
|
||||
newCollaborator: function (data) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
@ -2305,7 +2682,7 @@ Metamaps.Realtime = {
|
|||
|
||||
self.mappersOnMap[data.userid].realtime = true;
|
||||
|
||||
$('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn');
|
||||
//$('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn');
|
||||
$('#compass' + data.userid).show();
|
||||
|
||||
Metamaps.GlobalUI.notifyUser(data.username + ' just turned on realtime');
|
||||
|
@ -2319,7 +2696,7 @@ Metamaps.Realtime = {
|
|||
|
||||
self.mappersOnMap[data.userid].realtime = false;
|
||||
|
||||
$('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff');
|
||||
//$('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff');
|
||||
$('#compass' + data.userid).hide();
|
||||
|
||||
Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime');
|
||||
|
@ -2348,9 +2725,10 @@ Metamaps.Realtime = {
|
|||
var self = Metamaps.Realtime;
|
||||
var socket = Metamaps.Realtime.socket;
|
||||
|
||||
var boundary = self.chatOpen ? '#wrapper' : document;
|
||||
var mapper = self.mappersOnMap[id];
|
||||
var xMax=$(document).width();
|
||||
var yMax=$(document).height();
|
||||
var xMax=$(boundary).width();
|
||||
var yMax=$(boundary).height();
|
||||
var compassDiameter=56;
|
||||
var compassArrowSize=24;
|
||||
|
||||
|
@ -2385,9 +2763,10 @@ Metamaps.Realtime = {
|
|||
var self = Metamaps.Realtime;
|
||||
var socket = Metamaps.Realtime.socket;
|
||||
|
||||
var boundary = self.chatOpen ? '#wrapper' : document;
|
||||
var xLimit, yLimit;
|
||||
var xMax=$(document).width();
|
||||
var yMax=$(document).height();
|
||||
var xMax=$(boundary).width();
|
||||
var yMax=$(boundary).height();
|
||||
var compassDiameter=56;
|
||||
var compassArrowSize=24;
|
||||
|
||||
|
@ -2522,6 +2901,21 @@ Metamaps.Realtime = {
|
|||
});
|
||||
}
|
||||
},
|
||||
// newMessage
|
||||
sendNewMessage: function (data) {
|
||||
var self = Metamaps.Realtime;
|
||||
var socket = self.socket;
|
||||
|
||||
var message = data.attributes;
|
||||
message.mapid = Metamaps.Active.Map.id;
|
||||
socket.emit('newMessage', message);
|
||||
},
|
||||
newMessage: function (data) {
|
||||
var self = Metamaps.Realtime;
|
||||
var socket = self.socket;
|
||||
|
||||
self.room.addMessages(new Metamaps.Backbone.MessageCollection(data));
|
||||
},
|
||||
// newTopic
|
||||
sendNewTopic: function (data) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
@ -2986,7 +3380,6 @@ Metamaps.Control = {
|
|||
if (edge.getData("synapses").length - 1 === 0) {
|
||||
Metamaps.Control.hideEdge(edge);
|
||||
}
|
||||
|
||||
var mappableid = synapse.id;
|
||||
synapse.destroy();
|
||||
|
||||
|
@ -3206,7 +3599,6 @@ Metamaps.Filter = {
|
|||
var self = Metamaps.Filter;
|
||||
|
||||
Metamaps.GlobalUI.Account.close();
|
||||
Metamaps.Realtime.close();
|
||||
$('.sidebarFilterIcon div').addClass('hide');
|
||||
|
||||
|
||||
|
@ -3698,6 +4090,7 @@ Metamaps.Listeners = {
|
|||
$(window).resize(function () {
|
||||
if (Metamaps.Visualize && Metamaps.Visualize.mGraph) Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height());
|
||||
if ((Metamaps.Active.Map || Metamaps.Active.Topic) && Metamaps.Famous && Metamaps.Famous.maps.surf) Metamaps.Famous.maps.reposition();
|
||||
if (Metamaps.Active.Map && Metamaps.Realtime.inConversation) Metamaps.Realtime.positionVideos();
|
||||
});
|
||||
}
|
||||
}; // end Metamaps.Listeners
|
||||
|
@ -4009,11 +4402,11 @@ Metamaps.Topic = {
|
|||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end");
|
||||
}
|
||||
if (Metamaps.Create.newTopic.addSynapse && permitCreateSynapseAfter) {
|
||||
Metamaps.Create.newSynapse.topic1id = tempNode.getData('topic').id;
|
||||
Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id;
|
||||
|
||||
// position the form
|
||||
midpoint.x = tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - tempNode.pos.getc().x) / 2;
|
||||
midpoint.y = tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - tempNode.pos.getc().y) / 2;
|
||||
midpoint.x = Metamaps.tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2;
|
||||
midpoint.y = Metamaps.tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2;
|
||||
pixelPos = Metamaps.Util.coordsToPixels(midpoint);
|
||||
$('#new_synapse').css('left', pixelPos.x + "px");
|
||||
$('#new_synapse').css('top', pixelPos.y + "px");
|
||||
|
@ -4023,9 +4416,9 @@ Metamaps.Topic = {
|
|||
modes: ["node-property:dim"],
|
||||
duration: 500,
|
||||
onComplete: function () {
|
||||
tempNode = null;
|
||||
tempNode2 = null;
|
||||
tempInit = false;
|
||||
Metamaps.tempNode = null;
|
||||
Metamaps.tempNode2 = null;
|
||||
Metamaps.tempInit = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -4221,7 +4614,7 @@ Metamaps.Synapse = {
|
|||
|
||||
var edgeOnViz;
|
||||
|
||||
var newedge = synapse.createEdge();
|
||||
var newedge = synapse.createEdge(mapping);
|
||||
|
||||
Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data);
|
||||
edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id);
|
||||
|
@ -4290,7 +4683,7 @@ Metamaps.Synapse = {
|
|||
node1 = synapsesToCreate[i];
|
||||
topic1 = node1.getData('topic');
|
||||
synapse = new Metamaps.Backbone.Synapse({
|
||||
desc: Metamaps.Create.newSynapse.description,// || "",
|
||||
desc: Metamaps.Create.newSynapse.description,
|
||||
node1_id: topic1.isNew() ? topic1.cid : topic1.id,
|
||||
node2_id: topic2.isNew() ? topic2.cid : topic2.id,
|
||||
});
|
||||
|
@ -4377,6 +4770,7 @@ Metamaps.Map = {
|
|||
Metamaps.Topics = new bb.TopicCollection(data.topics);
|
||||
Metamaps.Synapses = new bb.SynapseCollection(data.synapses);
|
||||
Metamaps.Mappings = new bb.MappingCollection(data.mappings);
|
||||
Metamaps.Messages = data.messages;
|
||||
Metamaps.Backbone.attachCollectionEvents();
|
||||
|
||||
var map = Metamaps.Active.Map;
|
||||
|
@ -5173,4 +5567,3 @@ Metamaps.Admin = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
339
app/assets/javascripts/src/views/chatView.js.erb
Normal file
|
@ -0,0 +1,339 @@
|
|||
Metamaps.Views = Metamaps.Views || {};
|
||||
|
||||
Metamaps.Views.chatView = (function () {
|
||||
var
|
||||
chatView,
|
||||
linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false });
|
||||
|
||||
var Private = {
|
||||
messageHTML: "<div class='chat-message'>" +
|
||||
"<div class='chat-message-user'><img src='{{ user_image }}' title='{{user_name }}'/></div>" +
|
||||
"<div class='chat-message-text'>{{ message }}</div>" +
|
||||
"<div class='chat-message-time'>{{ timestamp }}</div>" +
|
||||
"<div class='clearfloat'></div>" +
|
||||
"</div>",
|
||||
participantHTML: "<div class='participant participant-{{ id }} {{ selfClass }}'>" +
|
||||
"<div class='chat-participant-image'><img src='{{ image }}' style='border: 2px solid {{ color }};' /></div>" +
|
||||
"<div class='chat-participant-name'>{{ username }} {{ selfName }}</div>" +
|
||||
"<button type='button' class='button chat-participant-invite-call' onclick='Metamaps.Realtime.inviteACall({{ id}});'></button>" +
|
||||
"<button type='button' class='button chat-participant-invite-join' onclick='Metamaps.Realtime.inviteToJoin({{ id}});'></button>" +
|
||||
"<span class='chat-participant-participating'><div class='green-dot'></div></span>" +
|
||||
"<div class='clearfloat'></div>" +
|
||||
"</div>",
|
||||
templates: function() {
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g
|
||||
};
|
||||
this.messageTemplate = _.template(Private.messageHTML);
|
||||
|
||||
this.participantTemplate = _.template(Private.participantHTML);
|
||||
},
|
||||
createElements: function() {
|
||||
this.$unread = $('<div class="chat-unread"></div>');
|
||||
this.$button = $('<div class="chat-button"><div class="tooltips">Chat</div></div>');
|
||||
this.$messageInput = $('<textarea placeholder="Send a message..." class="chat-input"></textarea>');
|
||||
this.$juntoHeader = $('<div class="junto-header">PARTICIPANTS</div>');
|
||||
this.$videoToggle = $('<div class="video-toggle"></div>');
|
||||
this.$cursorToggle = $('<div class="cursor-toggle"></div>');
|
||||
this.$participants = $('<div class="participants"></div>');
|
||||
this.$conversationInProgress = $('<div class="conversation-live">LIVE <span class="call-action leave" onclick="Metamaps.Realtime.leaveCall();">LEAVE</span><span class="call-action join" onclick="Metamaps.Realtime.joinCall();">JOIN</span></div>');
|
||||
this.$chatHeader = $('<div class="chat-header">CHAT</div>');
|
||||
this.$soundToggle = $('<div class="sound-toggle active"></div>');
|
||||
this.$messages = $('<div class="chat-messages"></div>');
|
||||
this.$container = $('<div class="chat-box"></div>');
|
||||
},
|
||||
attachElements: function() {
|
||||
this.$button.append(this.$unread);
|
||||
|
||||
this.$juntoHeader.append(this.$videoToggle);
|
||||
this.$juntoHeader.append(this.$cursorToggle);
|
||||
|
||||
this.$chatHeader.append(this.$soundToggle);
|
||||
|
||||
this.$participants.append(this.$conversationInProgress);
|
||||
|
||||
this.$container.append(this.$juntoHeader);
|
||||
this.$container.append(this.$participants);
|
||||
this.$container.append(this.$chatHeader);
|
||||
this.$container.append(this.$button);
|
||||
this.$container.append(this.$messages);
|
||||
this.$container.append(this.$messageInput);
|
||||
},
|
||||
addEventListeners: function() {
|
||||
var self = this;
|
||||
|
||||
this.participants.on('add', function (participant) {
|
||||
Private.addParticipant.call(self, participant);
|
||||
});
|
||||
|
||||
this.participants.on('remove', function (participant) {
|
||||
Private.removeParticipant.call(self, participant);
|
||||
});
|
||||
|
||||
this.$button.on('click', function () {
|
||||
Handlers.buttonClick.call(self);
|
||||
});
|
||||
this.$videoToggle.on('click', function () {
|
||||
Handlers.videoToggleClick.call(self);
|
||||
});
|
||||
this.$cursorToggle.on('click', function () {
|
||||
Handlers.cursorToggleClick.call(self);
|
||||
});
|
||||
this.$soundToggle.on('click', function () {
|
||||
Handlers.soundToggleClick.call(self);
|
||||
});
|
||||
this.$messageInput.on('keyup', function (event) {
|
||||
Handlers.keyUp.call(self, event);
|
||||
});
|
||||
this.$messageInput.on('focus', function () {
|
||||
Handlers.inputFocus.call(self);
|
||||
});
|
||||
this.$messageInput.on('blur', function () {
|
||||
Handlers.inputBlur.call(self);
|
||||
});
|
||||
},
|
||||
initializeSounds: function() {
|
||||
this.sound = new Howl({
|
||||
urls: ["<%= asset_path 'sounds/sounds.mp3' %>", "<%= asset_path 'sounds/sounds.ogg' %>"],
|
||||
sprite: {
|
||||
laser: [3000, 700]
|
||||
}
|
||||
});
|
||||
},
|
||||
incrementUnread: function() {
|
||||
this.unreadMessages++;
|
||||
this.$unread.html(this.unreadMessages);
|
||||
this.$unread.show();
|
||||
},
|
||||
addMessage: function(message, isInitial) {
|
||||
|
||||
if (!this.isOpen && !isInitial) Private.incrementUnread.call(this);
|
||||
|
||||
function addZero(i) {
|
||||
if (i < 10) {
|
||||
i = "0" + i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
var m = _.clone(message.attributes);
|
||||
|
||||
var today = new Date();
|
||||
m.timestamp = new Date(m.created_at);
|
||||
|
||||
var date = (m.timestamp.getMonth() + 1) + '/' + m.timestamp.getDate();
|
||||
date += " " + addZero(m.timestamp.getHours()) + ":" + addZero(m.timestamp.getMinutes());
|
||||
m.timestamp = date;
|
||||
m.image = m.user_image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; // TODO: remove
|
||||
m.message = linker.link(m.message);
|
||||
var $html = $(this.messageTemplate(m));
|
||||
this.$messages.append($html);
|
||||
if (!isInitial) this.scrollMessages(200);
|
||||
|
||||
if (!isInitial && this.alertSound) this.sound.play('laser');
|
||||
},
|
||||
initialMessages: function() {
|
||||
var messages = this.messages.models;
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
Private.addMessage.call(this, messages[i], true);
|
||||
}
|
||||
},
|
||||
handleInputMessage: function() {
|
||||
var message = {
|
||||
message: this.$messageInput.val(),
|
||||
};
|
||||
this.$messageInput.val('');
|
||||
$(document).trigger(chatView.events.message + '-' + this.room, [message]);
|
||||
},
|
||||
addParticipant: function(participant) {
|
||||
var p = _.clone(participant.attributes);
|
||||
if (p.self) {
|
||||
p.selfClass = 'is-self';
|
||||
p.selfName = '(me)';
|
||||
} else {
|
||||
p.selfClass = '';
|
||||
p.selfName = '';
|
||||
}
|
||||
var html = this.participantTemplate(p);
|
||||
this.$participants.append(html);
|
||||
},
|
||||
removeParticipant: function(participant) {
|
||||
this.$container.find('.participant-' + participant.get('id')).remove();
|
||||
}
|
||||
};
|
||||
|
||||
var Handlers = {
|
||||
buttonClick: function() {
|
||||
if (this.isOpen) this.close();
|
||||
else if (!this.isOpen) this.open();
|
||||
},
|
||||
videoToggleClick: function() {
|
||||
this.$videoToggle.toggleClass('active');
|
||||
this.videosShowing = !this.videosShowing;
|
||||
$(document).trigger(this.videosShowing ? chatView.events.videosOn : chatView.events.videosOff);
|
||||
},
|
||||
cursorToggleClick: function() {
|
||||
this.$cursorToggle.toggleClass('active');
|
||||
this.cursorsShowing = !this.cursorsShowing;
|
||||
$(document).trigger(this.cursorsShowing ? chatView.events.cursorsOn : chatView.events.cursorsOff);
|
||||
},
|
||||
soundToggleClick: function() {
|
||||
this.alertSound = !this.alertSound;
|
||||
this.$soundToggle.toggleClass('active');
|
||||
},
|
||||
keyUp: function(event) {
|
||||
switch(event.which) {
|
||||
case 13: // enter
|
||||
Private.handleInputMessage.call(this);
|
||||
break;
|
||||
}
|
||||
},
|
||||
inputFocus: function() {
|
||||
$(document).trigger(chatView.events.inputFocus);
|
||||
},
|
||||
inputBlur: function() {
|
||||
$(document).trigger(chatView.events.inputBlur);
|
||||
}
|
||||
};
|
||||
|
||||
chatView = function(messages, mapper, room) {
|
||||
var self = this;
|
||||
|
||||
this.room = room;
|
||||
this.mapper = mapper;
|
||||
this.messages = messages; // backbone collection
|
||||
|
||||
this.isOpen = false;
|
||||
this.alertSound = false; // whether to play sounds on arrival of new messages or not
|
||||
this.cursorsShowing = true;
|
||||
this.videosShowing = true;
|
||||
this.unreadMessages = 0;
|
||||
this.participants = new Backbone.Collection();
|
||||
|
||||
Private.templates.call(this);
|
||||
Private.createElements.call(this);
|
||||
Private.attachElements.call(this);
|
||||
Private.addEventListeners.call(this);
|
||||
Private.initialMessages.call(this);
|
||||
Private.initializeSounds.call(this);
|
||||
this.$container.css({
|
||||
right: '-300px'
|
||||
});
|
||||
};
|
||||
|
||||
chatView.prototype.conversationInProgress = function (participating) {
|
||||
this.$conversationInProgress.show();
|
||||
this.$participants.addClass('is-live');
|
||||
if (participating) this.$participants.addClass('is-participating');
|
||||
this.$button.addClass('active');
|
||||
|
||||
// hide invite to call buttons
|
||||
}
|
||||
|
||||
chatView.prototype.conversationEnded = function () {
|
||||
this.$conversationInProgress.hide();
|
||||
this.$participants.removeClass('is-live');
|
||||
this.$participants.removeClass('is-participating');
|
||||
this.$button.removeClass('active');
|
||||
this.$participants.find('.participant').removeClass('active');
|
||||
this.$participants.find('.participant').removeClass('pending');
|
||||
}
|
||||
|
||||
chatView.prototype.leaveConversation = function () {
|
||||
this.$participants.removeClass('is-participating');
|
||||
}
|
||||
|
||||
chatView.prototype.mapperJoinedCall = function (id) {
|
||||
this.$participants.find('.participant-' + id).addClass('active');
|
||||
}
|
||||
|
||||
chatView.prototype.mapperLeftCall = function (id) {
|
||||
this.$participants.find('.participant-' + id).removeClass('active');
|
||||
}
|
||||
|
||||
chatView.prototype.invitationPending = function (id) {
|
||||
this.$participants.find('.participant-' + id).addClass('pending');
|
||||
}
|
||||
|
||||
chatView.prototype.invitationAnswered = function (id) {
|
||||
this.$participants.find('.participant-' + id).removeClass('pending');
|
||||
}
|
||||
|
||||
chatView.prototype.addParticipant = function (participant) {
|
||||
this.participants.add(participant);
|
||||
}
|
||||
|
||||
chatView.prototype.removeParticipant = function (username) {
|
||||
var p = this.participants.find(function (p) { return p.get('username') === username; });
|
||||
if (p) {
|
||||
this.participants.remove(p);
|
||||
}
|
||||
}
|
||||
|
||||
chatView.prototype.removeParticipants = function () {
|
||||
this.participants.remove(this.participants.models);
|
||||
}
|
||||
|
||||
chatView.prototype.open = function () {
|
||||
this.$container.css({
|
||||
right: '0'
|
||||
});
|
||||
this.$messageInput.focus();
|
||||
this.isOpen = true;
|
||||
this.unreadMessages = 0;
|
||||
this.$unread.hide();
|
||||
this.scrollMessages(0);
|
||||
$(document).trigger(chatView.events.openTray);
|
||||
}
|
||||
|
||||
chatView.prototype.addMessage = function(message, isInitial) {
|
||||
this.messages.add(message);
|
||||
Private.addMessage.call(this, message, isInitial);
|
||||
}
|
||||
|
||||
chatView.prototype.scrollMessages = function(duration) {
|
||||
duration = duration || 0;
|
||||
|
||||
this.$messages.animate({
|
||||
scrollTop: this.$messages[0].scrollHeight
|
||||
}, duration);
|
||||
}
|
||||
|
||||
chatView.prototype.clearMessages = function () {
|
||||
this.unreadMessages = 0;
|
||||
this.$unread.hide();
|
||||
this.$messages.empty();
|
||||
}
|
||||
|
||||
chatView.prototype.close = function () {
|
||||
this.$container.css({
|
||||
right: '-300px'
|
||||
});
|
||||
this.$messageInput.blur();
|
||||
this.isOpen = false;
|
||||
$(document).trigger(chatView.events.closeTray);
|
||||
}
|
||||
|
||||
chatView.prototype.remove = function () {
|
||||
this.$button.off();
|
||||
this.$container.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
chatView.events = {
|
||||
message: 'ChatView:message',
|
||||
openTray: 'ChatView:openTray',
|
||||
closeTray: 'ChatView:closeTray',
|
||||
inputFocus: 'ChatView:inputFocus',
|
||||
inputBlur: 'ChatView:inputBlur',
|
||||
cursorsOff: 'ChatView:cursorsOff',
|
||||
cursorsOn: 'ChatView:cursorsOn',
|
||||
videosOff: 'ChatView:videosOff',
|
||||
videosOn: 'ChatView:videosOn'
|
||||
};
|
||||
|
||||
return chatView;
|
||||
|
||||
})();
|
194
app/assets/javascripts/src/views/room.js
Normal file
|
@ -0,0 +1,194 @@
|
|||
Metamaps.Views = Metamaps.Views || {};
|
||||
|
||||
Metamaps.Views.room = (function () {
|
||||
|
||||
var ChatView = Metamaps.Views.chatView;
|
||||
var VideoView = Metamaps.Views.videoView;
|
||||
|
||||
var room = function(opts) {
|
||||
var self = this;
|
||||
|
||||
this.isActiveRoom = false;
|
||||
this.socket = opts.socket;
|
||||
this.webrtc = opts.webrtc;
|
||||
//this.roomRef = opts.firebase;
|
||||
this.room = opts.room;
|
||||
this.config = opts.config;
|
||||
this.peopleCount = 0;
|
||||
|
||||
this.$myVideo = opts.$video;
|
||||
this.myVideo = opts.myVideoView;
|
||||
|
||||
this.messages = new Backbone.Collection();
|
||||
this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image });
|
||||
this.chat = new ChatView(this.messages, this.currentMapper, this.room);
|
||||
|
||||
this.videos = {};
|
||||
|
||||
this.init();
|
||||
};
|
||||
|
||||
room.prototype.join = function(cb) {
|
||||
this.isActiveRoom = true;
|
||||
this.webrtc.joinRoom(this.room, cb);
|
||||
this.chat.conversationInProgress(true); // true indicates participation
|
||||
}
|
||||
|
||||
room.prototype.conversationInProgress = function() {
|
||||
this.chat.conversationInProgress(false); // false indicates not participating
|
||||
}
|
||||
|
||||
room.prototype.conversationEnding = function() {
|
||||
this.chat.conversationEnded();
|
||||
}
|
||||
|
||||
room.prototype.leaveVideoOnly = function() {
|
||||
this.chat.leaveConversation(); // the conversation will carry on without you
|
||||
for (var id in this.videos) {
|
||||
this.removeVideo(id);
|
||||
}
|
||||
this.isActiveRoom = false;
|
||||
this.webrtc.leaveRoom();
|
||||
}
|
||||
|
||||
room.prototype.leave = function() {
|
||||
for (var id in this.videos) {
|
||||
this.removeVideo(id);
|
||||
}
|
||||
this.isActiveRoom = false;
|
||||
this.webrtc.leaveRoom();
|
||||
this.chat.conversationEnded();
|
||||
this.chat.removeParticipants();
|
||||
this.chat.clearMessages();
|
||||
this.messages.reset();
|
||||
}
|
||||
|
||||
room.prototype.setPeopleCount = function(count) {
|
||||
this.peopleCount = count;
|
||||
}
|
||||
|
||||
room.prototype.init = function () {
|
||||
var self = this;
|
||||
|
||||
$(document).on(VideoView.events.audioControlClick, function (event, videoView) {
|
||||
if (!videoView.audioStatus) self.webrtc.mute();
|
||||
else if (videoView.audioStatus) self.webrtc.unmute();
|
||||
});
|
||||
$(document).on(VideoView.events.videoControlClick, function (event, videoView) {
|
||||
if (!videoView.videoStatus) self.webrtc.pauseVideo();
|
||||
else if (videoView.videoStatus) self.webrtc.resumeVideo();
|
||||
});
|
||||
|
||||
this.webrtc.webrtc.off('peerStreamAdded');
|
||||
this.webrtc.webrtc.off('peerStreamRemoved');
|
||||
this.webrtc.on('peerStreamAdded', function (peer) {
|
||||
var mapper = Metamaps.Realtime.mappersOnMap[peer.nick];
|
||||
peer.avatar = mapper.image;
|
||||
peer.username = mapper.name;
|
||||
if (self.isActiveRoom) {
|
||||
self.addVideo(peer);
|
||||
}
|
||||
});
|
||||
|
||||
this.webrtc.on('peerStreamRemoved', function (peer) {
|
||||
if (self.isActiveRoom) {
|
||||
self.removeVideo(peer);
|
||||
}
|
||||
});
|
||||
|
||||
this.webrtc.on('mute', function (data) {
|
||||
var v = self.videos[data.id];
|
||||
if (!v) return;
|
||||
|
||||
if (data.name === 'audio') {
|
||||
v.audioStatus = false;
|
||||
}
|
||||
else if (data.name === 'video') {
|
||||
v.videoStatus = false;
|
||||
v.$avatar.show();
|
||||
}
|
||||
if (!v.audioStatus && !v.videoStatus) v.$container.hide();
|
||||
});
|
||||
this.webrtc.on('unmute', function (data) {
|
||||
var v = self.videos[data.id];
|
||||
if (!v) return;
|
||||
|
||||
if (data.name === 'audio') {
|
||||
v.audioStatus = true;
|
||||
}
|
||||
else if (data.name === 'video') {
|
||||
v.videoStatus = true;
|
||||
v.$avatar.hide();
|
||||
}
|
||||
v.$container.show();
|
||||
});
|
||||
|
||||
var sendChatMessage = function (event, data) {
|
||||
self.sendChatMessage(data);
|
||||
};
|
||||
$(document).on(ChatView.events.message + '-' + this.room, sendChatMessage);
|
||||
}
|
||||
|
||||
room.prototype.videoAdded = function (callback) {
|
||||
this._videoAdded = callback;
|
||||
}
|
||||
|
||||
room.prototype.addVideo = function (peer) {
|
||||
var
|
||||
id = this.webrtc.getDomId(peer),
|
||||
video = attachMediaStream(peer.stream);
|
||||
|
||||
var
|
||||
v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar, username: peer.username });
|
||||
|
||||
this.videos[peer.id] = v;
|
||||
if (this._videoAdded) this._videoAdded(v, peer.nick);
|
||||
}
|
||||
|
||||
room.prototype.removeVideo = function (peer) {
|
||||
var id = typeof peer == 'string' ? peer : peer.id;
|
||||
if (this.videos[id]) {
|
||||
this.videos[id].remove();
|
||||
delete this.videos[id];
|
||||
}
|
||||
}
|
||||
|
||||
room.prototype.sendChatMessage = function (data) {
|
||||
var self = this;
|
||||
//this.roomRef.child('messages').push(data);
|
||||
var m = new Metamaps.Backbone.Message({
|
||||
message: data.message,
|
||||
resource_id: Metamaps.Active.Map.id,
|
||||
resource_type: "Map"
|
||||
});
|
||||
m.save(null, {
|
||||
success: function (model, response) {
|
||||
self.addMessages(new Metamaps.Backbone.MessageCollection(model));
|
||||
$(document).trigger(room.events.newMessage, [model]);
|
||||
},
|
||||
error: function (model, response) {
|
||||
console.log('error!', response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// they should be instantiated as backbone models before they get
|
||||
// passed to this function
|
||||
room.prototype.addMessages = function (messages, isInitial) {
|
||||
var self = this;
|
||||
|
||||
messages.models.forEach(function (message) {
|
||||
self.chat.addMessage(message, isInitial);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
room.events = {
|
||||
newMessage: "Room:newMessage"
|
||||
};
|
||||
|
||||
return room;
|
||||
})();
|
207
app/assets/javascripts/src/views/videoView.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
Metamaps.Views = Metamaps.Views || {};
|
||||
|
||||
Metamaps.Views.videoView = (function () {
|
||||
|
||||
var videoView;
|
||||
|
||||
var Private = {
|
||||
addControls: function() {
|
||||
var self = this;
|
||||
|
||||
this.$audioControl = $('<div class="video-audio"></div>');
|
||||
this.$videoControl = $('<div class="video-video"></div>');
|
||||
|
||||
this.$audioControl.on('click', function () {
|
||||
Handlers.audioControlClick.call(self);
|
||||
});
|
||||
|
||||
this.$videoControl.on('click', function () {
|
||||
Handlers.videoControlClick.call(self);
|
||||
});
|
||||
|
||||
this.$container.append(this.$audioControl);
|
||||
this.$container.append(this.$videoControl);
|
||||
},
|
||||
cancelClick: function() {
|
||||
this.mouseIsDown = false;
|
||||
|
||||
if (this.hasMoved) {
|
||||
|
||||
}
|
||||
|
||||
$(document).trigger(videoView.events.dragEnd);
|
||||
}
|
||||
};
|
||||
|
||||
var Handlers = {
|
||||
mousedown: function(event) {
|
||||
this.mouseIsDown = true;
|
||||
this.hasMoved = false;
|
||||
this.mouseMoveStart = {
|
||||
x: event.pageX,
|
||||
y: event.pageY
|
||||
};
|
||||
this.posStart = {
|
||||
x: parseInt(this.$container.css('left'), '10'),
|
||||
y: parseInt(this.$container.css('top'), '10')
|
||||
}
|
||||
|
||||
$(document).trigger(videoView.events.mousedown);
|
||||
},
|
||||
mouseup: function(event) {
|
||||
$(document).trigger(videoView.events.mouseup, [this]);
|
||||
|
||||
var storedTime = this.lastClick;
|
||||
var now = Date.now();
|
||||
this.lastClick = now;
|
||||
|
||||
if (now - storedTime < this.config.DOUBLE_CLICK_TOLERANCE) {
|
||||
$(document).trigger(videoView.events.doubleClick, [this]);
|
||||
}
|
||||
},
|
||||
mousemove: function(event) {
|
||||
var
|
||||
diffX,
|
||||
diffY,
|
||||
newX,
|
||||
newY;
|
||||
|
||||
if (this.$parent && this.mouseIsDown) {
|
||||
this.manuallyPositioned = true;
|
||||
this.hasMoved = true;
|
||||
diffX = event.pageX - this.mouseMoveStart.x;
|
||||
diffY = this.mouseMoveStart.y - event.pageY;
|
||||
newX = this.posStart.x + diffX;
|
||||
newY = this.posStart.y - diffY;
|
||||
this.$container.css({
|
||||
top: newY,
|
||||
left: newX
|
||||
});
|
||||
}
|
||||
},
|
||||
audioControlClick: function() {
|
||||
if (this.audioStatus) {
|
||||
this.audioOff();
|
||||
} else {
|
||||
this.audioOn();
|
||||
}
|
||||
$(document).trigger(videoView.events.audioControlClick, [this]);
|
||||
},
|
||||
videoControlClick: function() {
|
||||
if (this.videoStatus) {
|
||||
this.videoOff();
|
||||
} else {
|
||||
this.videoOn();
|
||||
}
|
||||
$(document).trigger(videoView.events.videoControlClick, [this]);
|
||||
},
|
||||
};
|
||||
|
||||
var videoView = function(video, $parent, id, isMyself, config) {
|
||||
var self = this;
|
||||
|
||||
this.$parent = $parent; // mapView
|
||||
|
||||
this.video = video;
|
||||
this.id = id;
|
||||
|
||||
this.config = config;
|
||||
|
||||
this.mouseIsDown = false;
|
||||
this.mouseDownOffset = { x: 0, y: 0 };
|
||||
this.lastClick = null;
|
||||
this.hasMoved = false;
|
||||
|
||||
this.audioStatus = true;
|
||||
this.videoStatus = true;
|
||||
|
||||
this.$container = $('<div></div>');
|
||||
this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : ''));
|
||||
this.$container.attr('id', 'container_' + id);
|
||||
|
||||
|
||||
var $vidContainer = $('<div></div>');
|
||||
$vidContainer.addClass('video-cutoff');
|
||||
$vidContainer.append(this.video);
|
||||
|
||||
this.avatar = config.avatar;
|
||||
this.$avatar = $('<img draggable="false" class="collaborator-video-avatar" src="' + config.avatar + '" width="150" height="150" />');
|
||||
$vidContainer.append(this.$avatar);
|
||||
|
||||
this.$container.append($vidContainer);
|
||||
|
||||
this.$container.on('mousedown', function (event) {
|
||||
Handlers.mousedown.call(self, event);
|
||||
});
|
||||
|
||||
if (isMyself) {
|
||||
Private.addControls.call(this);
|
||||
}
|
||||
|
||||
// suppress contextmenu
|
||||
this.video.oncontextmenu = function () { return false; };
|
||||
|
||||
if (this.$parent) this.setParent(this.$parent);
|
||||
};
|
||||
|
||||
videoView.prototype.setParent = function($parent) {
|
||||
var self = this;
|
||||
this.$parent = $parent;
|
||||
this.$parent.off('.video' + this.id);
|
||||
this.$parent.on('mouseup.video' + this.id, function (event) {
|
||||
Handlers.mouseup.call(self, event);
|
||||
Private.cancelClick.call(self);
|
||||
});
|
||||
this.$parent.on('mousemove.video' + this.id, function (event) {
|
||||
Handlers.mousemove.call(self, event);
|
||||
});
|
||||
}
|
||||
|
||||
videoView.prototype.setAvatar = function (src) {
|
||||
this.$avatar.attr('src', src);
|
||||
this.avatar = src;
|
||||
}
|
||||
|
||||
videoView.prototype.remove = function () {
|
||||
this.$container.off();
|
||||
if (this.$parent) this.$parent.off('.video' + this.id);
|
||||
this.$container.remove();
|
||||
}
|
||||
|
||||
videoView.prototype.videoOff = function () {
|
||||
this.$videoControl.addClass('active');
|
||||
this.$avatar.show();
|
||||
this.videoStatus = false;
|
||||
}
|
||||
|
||||
videoView.prototype.videoOn = function () {
|
||||
this.$videoControl.removeClass('active');
|
||||
this.$avatar.hide();
|
||||
this.videoStatus = true;
|
||||
}
|
||||
|
||||
videoView.prototype.audioOff = function () {
|
||||
this.$audioControl.addClass('active');
|
||||
this.audioStatus = false;
|
||||
}
|
||||
|
||||
videoView.prototype.audioOn = function () {
|
||||
this.$audioControl.removeClass('active');
|
||||
this.audioStatus = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
videoView.events = {
|
||||
mousedown: "VideoView:mousedown",
|
||||
mouseup: "VideoView:mouseup",
|
||||
doubleClick: "VideoView:doubleClick",
|
||||
dragEnd: "VideoView:dragEnd",
|
||||
audioControlClick: "VideoView:audioControlClick",
|
||||
videoControlClick: "VideoView:videoControlClick",
|
||||
};
|
||||
|
||||
return videoView;
|
||||
})();
|
|
@ -1,63 +0,0 @@
|
|||
|
||||
|
||||
.allMetacodes {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.allMetacodes span {
|
||||
margin:4px 8px;
|
||||
color:#67AF9F;
|
||||
}
|
||||
|
||||
.editMetacodes {
|
||||
z-index:12;
|
||||
width:auto;
|
||||
color: #67AF9F;
|
||||
padding:10px;
|
||||
float:left;
|
||||
}
|
||||
|
||||
.editMetacodes ul {
|
||||
display:block;
|
||||
}
|
||||
|
||||
.editMetacodes ul li {
|
||||
clear:both;
|
||||
list-style-type:none;
|
||||
display:block;
|
||||
padding:3px;
|
||||
}
|
||||
|
||||
.editMetacodes ul img {
|
||||
width:40px;
|
||||
height:40px;
|
||||
float:left;
|
||||
}
|
||||
|
||||
.editMetacodes ul p {
|
||||
float:left;
|
||||
display: block;
|
||||
margin: 0;
|
||||
background: none;
|
||||
padding: 10px 4px 2px 4px;
|
||||
}
|
||||
|
||||
.editMetacodes #filters-one {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.editMetacodes #filters-two {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.editMetacodes #filters-three {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.editMetacodes #filters-four {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.editMetacodes li.toggledOff {
|
||||
opacity: 0.4;
|
||||
}
|
188
app/assets/stylesheets/admin.scss.erb
Normal file
|
@ -0,0 +1,188 @@
|
|||
.allMetacodes {
|
||||
float:left;
|
||||
|
||||
span {
|
||||
margin:4px 8px;
|
||||
color:#67AF9F;
|
||||
}
|
||||
}
|
||||
|
||||
.editMetacodes {
|
||||
z-index:12;
|
||||
width:auto;
|
||||
color: #67AF9F;
|
||||
padding:10px;
|
||||
float:left;
|
||||
|
||||
ul {
|
||||
display:block;
|
||||
}
|
||||
|
||||
ul li {
|
||||
clear:both;
|
||||
list-style-type:none;
|
||||
display:block;
|
||||
padding:3px;
|
||||
}
|
||||
|
||||
ul img {
|
||||
width:40px;
|
||||
height:40px;
|
||||
float:left;
|
||||
}
|
||||
|
||||
ul p {
|
||||
float:left;
|
||||
display: block;
|
||||
margin: 0;
|
||||
background: none;
|
||||
padding: 10px 4px 2px 4px;
|
||||
}
|
||||
|
||||
#filters-one {
|
||||
float:left;
|
||||
}
|
||||
|
||||
#filters-two {
|
||||
float:left;
|
||||
}
|
||||
|
||||
#filters-three {
|
||||
float:left;
|
||||
}
|
||||
|
||||
#filters-four {
|
||||
float:left;
|
||||
}
|
||||
|
||||
li.toggledOff {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.blackBox {
|
||||
width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0 60px 20px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.metacodeSetsDescription {
|
||||
width: 314px;
|
||||
}
|
||||
td.metacodeSetDesc {
|
||||
width: 314px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.metacodeSetImage {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
float: left;
|
||||
}
|
||||
tr {
|
||||
display: table-row;
|
||||
}
|
||||
tr:nth-child(odd) {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 10px;
|
||||
}
|
||||
td.iconURL {
|
||||
max-width: 415px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.field {
|
||||
margin: 15px 0 5px;
|
||||
}
|
||||
label {
|
||||
float: left;
|
||||
width: 100px;
|
||||
margin-right: 15px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 336px;
|
||||
height: 32px;
|
||||
font-size: 15px;
|
||||
direction: ltr;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-top: 1px solid #c0c0c0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-border-radius: 1px;
|
||||
-moz-border-radius: 1px;
|
||||
border-radius: 1px;
|
||||
font: -webkit-small-control;
|
||||
color: initial;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
text-align: start;
|
||||
font-family: arial;
|
||||
}
|
||||
textarea:hover,
|
||||
input[type="text"]:hover {
|
||||
border: 1px solid #b9b9b9;
|
||||
border-top: 1px solid #a0a0a0;
|
||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
textarea {
|
||||
padding: 8px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-top: 1px solid #c0c0c0;
|
||||
resize: none;
|
||||
font: -webkit-small-control;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
text-align: start;
|
||||
font-family: arial;
|
||||
font-size: 15px;
|
||||
line-height: 17px;
|
||||
width: 318px;
|
||||
}
|
||||
.allMetacodes {
|
||||
padding: 5px 0;
|
||||
}
|
||||
a.button {
|
||||
margin-right: 20px;
|
||||
line-height: 40px;
|
||||
}
|
||||
a.button,
|
||||
input.add {
|
||||
float: left;
|
||||
margin-top: 5px;
|
||||
height: 40px;
|
||||
font-size: 17px;
|
||||
width: auto;
|
||||
padding: 0 30px;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
}
|
||||
a.button:hover,
|
||||
input.add:hover {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
|
@ -132,6 +132,17 @@ a.button:active,
|
|||
input[type="submit"]:active {
|
||||
background: #429B46;
|
||||
}
|
||||
|
||||
button.button.btn-no {
|
||||
background-color: #c04f4f;
|
||||
}
|
||||
button.button.btn-no:hover {
|
||||
background-color: #A54242;
|
||||
}
|
||||
.toast .toast-button {
|
||||
margin-top: -10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
/*
|
||||
* Utility
|
||||
*/
|
||||
|
@ -628,8 +639,12 @@ label {
|
|||
margin: 0 0 0 1.3em;
|
||||
}
|
||||
.main {
|
||||
position: relative;
|
||||
/*overflow:hidden; */
|
||||
}
|
||||
.main.compressed {
|
||||
width: calc(100% - 300px);
|
||||
}
|
||||
#infovis-canvas {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
@ -1077,84 +1092,6 @@ h3.filterBox {
|
|||
}
|
||||
/* end filter by metacode */
|
||||
|
||||
/* collaborate */
|
||||
|
||||
.sidebarCollaborate {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.sidebarCollaborateBox {
|
||||
display: none;
|
||||
height: auto;
|
||||
padding: 16px;
|
||||
width: 238px;
|
||||
}
|
||||
h3.realtimeBoxTitle {
|
||||
margin-bottom: 10px;
|
||||
text-align: left;
|
||||
float: left;
|
||||
font-size:18px;
|
||||
line-height:18px;
|
||||
}
|
||||
.sidebarCollaborateBox .realtimeOnOff {
|
||||
float: right;
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
margin-left: 12px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size:12px;
|
||||
}
|
||||
.sidebarCollaborateBox .realtimeOnOff:hover, .sidebarCollaborateBox .realtimeOnOff.active {
|
||||
color: #00bcd4;
|
||||
}
|
||||
.sidebarCollaborateBox .rtOff {
|
||||
|
||||
}
|
||||
.sidebarCollaborateBox .rtOn {
|
||||
|
||||
}
|
||||
.realtimeMapperList .rtMapper {
|
||||
list-style-type: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 10px 34px;
|
||||
display: block;
|
||||
height: 14px;
|
||||
font-family: 'din-regular', helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.rtMapperSelf img {
|
||||
border: 2px solid #424242;
|
||||
}
|
||||
|
||||
.rtUserImage {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.littleJuntoIcon {
|
||||
width: 24px;
|
||||
height:24px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
background-image: url(<%= asset_data_uri('junto24_sprite.png') %>);
|
||||
}
|
||||
.realtimeMapperList .littleRtOff .littleJuntoIcon {
|
||||
background-position: 0 0;
|
||||
}
|
||||
.realtimeMapperList .littleRtOn .littleJuntoIcon {
|
||||
background-position: -24px 0;
|
||||
}
|
||||
/* end collaborate */
|
||||
|
||||
.nodemargin {
|
||||
padding-top: 120px;
|
||||
}
|
||||
|
|
131
app/assets/stylesheets/apps.css.erb
Normal file
|
@ -0,0 +1,131 @@
|
|||
.centerContent {
|
||||
position: relative;
|
||||
margin: 92px auto 0 auto;
|
||||
padding: 20px 0 60px 20px;
|
||||
width: 760px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);
|
||||
background: #fff;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dcdcdc;
|
||||
margin-bottom: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.centerContent .page-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #DCDCDC;
|
||||
}
|
||||
|
||||
.centerContent .form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.centerContent .inline-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.centerContent.showApp p {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.centerContent a {
|
||||
color: #4fb5c0;
|
||||
}
|
||||
.centerContent a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.centerContent a.button {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.centerContent a.button:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.centerContent th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.centerContent td {
|
||||
padding-right: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.centerContent .link-button {
|
||||
line-height: 32px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.centerContent .button-margin {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.centerContent .button-margin-top {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.centerContent a.red-button, .centerContent button.red-button,
|
||||
.centerContent input[type="submit"].red-button {
|
||||
color: #c04f4f;
|
||||
background: transparent;
|
||||
text-transform: none;
|
||||
}
|
||||
.centerContent .red-button:hover {
|
||||
background: transparent;
|
||||
color: #9A3E3E !important;
|
||||
}
|
||||
|
||||
.centerContent input[type="text"] {
|
||||
font-family: 'din-medium', helvetica, sans-serif;
|
||||
width: 400px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
direction: ltr;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-top: 1px solid #c0c0c0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-border-radius: 1px;
|
||||
-moz-border-radius: 1px;
|
||||
border-radius: 2px;
|
||||
color: #424242;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.centerContent textarea {
|
||||
color: #424242;
|
||||
padding: 8px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-top: 1px solid #c0c0c0;
|
||||
resize: none;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
text-align: start;
|
||||
font-family: 'din-medium', helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
width: 400px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
}
|
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
#famousOverlay {
|
||||
position:fixed;
|
||||
position:absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -116,7 +116,7 @@
|
|||
|
||||
/* upperLeftUI */
|
||||
.upperLeftUI {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 24px;
|
||||
z-index:3;
|
||||
|
@ -155,7 +155,7 @@
|
|||
/* upperRightUI */
|
||||
|
||||
.upperRightUI {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 24px;
|
||||
z-index:4;
|
||||
|
@ -166,9 +166,9 @@
|
|||
}
|
||||
|
||||
.upperRightBox {
|
||||
position: fixed;
|
||||
top:52px;
|
||||
right:24px;
|
||||
position: absolute;
|
||||
top:42px;
|
||||
right:0;
|
||||
background-color: #E0E0E0;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||
|
@ -187,17 +187,12 @@
|
|||
}
|
||||
|
||||
.upperRightMapButtons {
|
||||
position: relative;
|
||||
top: -42px; /* puts it just offscreen */
|
||||
}
|
||||
.mapPage .upperRightMapButtons, .topicPage .upperRightMapButtons {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.topicPage .sidebarCollaborate {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upperRightIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
@ -205,20 +200,6 @@
|
|||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebarCollaborateIcon {
|
||||
background-position: 0 0;
|
||||
display: none;
|
||||
}
|
||||
.sidebarCollaborateIcon.blue {
|
||||
background-position: -32px 0;
|
||||
}
|
||||
.sidebarCollaborateIcon.blue:hover {
|
||||
background-position: -32px -32px;
|
||||
}
|
||||
/* only show the collaborate icon on commons */
|
||||
.commonsMap .sidebarCollaborateIcon {
|
||||
display: block;
|
||||
}
|
||||
.sidebarFilterIcon {
|
||||
background-position: -64px 0;
|
||||
}
|
||||
|
@ -384,7 +365,7 @@
|
|||
}
|
||||
|
||||
.infoAndHelp {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 3;
|
||||
|
@ -424,7 +405,7 @@
|
|||
/* mapControls */
|
||||
|
||||
.mapControls {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
right:-32px; /* puts it just offscreen */
|
||||
width:32px;
|
||||
|
@ -474,9 +455,8 @@
|
|||
background-position: -32px 0;
|
||||
}
|
||||
|
||||
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarCollaborateIcon:hover .tooltipsUnder,
|
||||
.sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove {
|
||||
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -532,10 +512,6 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
.sidebarCollaborateIcon .tooltipsUnder {
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
.sidebarFilterIcon .tooltipsUnder {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
@ -560,16 +536,20 @@
|
|||
left: -11px;
|
||||
}
|
||||
|
||||
.chat-button .tooltips {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.openCheatsheet .tooltipsAbove {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.sidebarAccountIcon .tooltipsUnder {
|
||||
margin-left: -8px;
|
||||
margin-left: -12px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.zoomExtents div::after, .zoomIn div::after, .zoomOut div::after, .takeScreenshot div:after {
|
||||
.zoomExtents div::after, .zoomIn div::after, .zoomOut div::after, .takeScreenshot div:after, .chat-button div.tooltips::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 57%;
|
||||
|
@ -582,21 +562,20 @@
|
|||
border-bottom: 5px solid transparent;
|
||||
}
|
||||
|
||||
.sidebarCollaborateIcon div:after, .sidebarFilterIcon div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
||||
left: 38%;
|
||||
}
|
||||
|
||||
.sidebarCollaborateIcon div:after, .sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
||||
.sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 129%;
|
||||
margin-top: -30px;
|
||||
right: 40%;
|
||||
margin-top: -7px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-bottom: 4px solid #000000;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
.sidebarFilterIcon div:after {
|
||||
right: 37% !important;
|
||||
}
|
||||
|
||||
.mapInfoIcon div:after, .openCheatsheet div:after {
|
||||
content: '';
|
||||
|
@ -735,7 +714,7 @@
|
|||
color: #F5F5F5;
|
||||
padding: 16px;
|
||||
border-radius: 2px;
|
||||
z-index: 1 !important; /* important necessary for firefox */
|
||||
z-index: 4 !important; /* important necessary for firefox */
|
||||
font-size: 14px;
|
||||
line-height:14px;
|
||||
}
|
||||
|
@ -764,3 +743,11 @@ box-shadow: 0px 1px 1.5px rgba(0,0,0,0.12), 0 1px 1px rgba(0,0,0,0.24);
|
|||
body a#barometer_tab:hover {
|
||||
background-position: 0 -110px;
|
||||
}
|
||||
|
||||
.hideVideos .collaborator-video {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hideCursors .collabCompass {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
348
app/assets/stylesheets/junto.css.erb
Normal file
|
@ -0,0 +1,348 @@
|
|||
.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: 0px 0px 16px 8px rgba(0, 0, 0, 0.23), -2px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.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: 100%;
|
||||
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), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.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: 32px;
|
||||
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: 100%;
|
||||
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), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle {
|
||||
display: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 32px;
|
||||
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;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-time {
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
color: #757575;
|
||||
}
|
3
app/controllers/api/mappings_controller.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Api::MappingsController < API::RestfulController
|
||||
|
||||
end
|
3
app/controllers/api/maps_controller.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Api::MapsController < API::RestfulController
|
||||
|
||||
end
|
53
app/controllers/api/restful_controller.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
class API::RestfulController < ActionController::Base
|
||||
include Pundit
|
||||
include PunditExtra
|
||||
|
||||
snorlax_used_rest!
|
||||
|
||||
load_and_authorize_resource only: [:show, :update, :destroy]
|
||||
|
||||
def create
|
||||
instantiate_resource
|
||||
resource.user = current_user
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_serializer
|
||||
"new_#{resource_name}_serializer".camelize.constantize
|
||||
end
|
||||
|
||||
def accessible_records
|
||||
if current_user
|
||||
visible_records
|
||||
else
|
||||
public_records
|
||||
end
|
||||
end
|
||||
|
||||
def current_user
|
||||
super || token_user || doorkeeper_user || nil
|
||||
end
|
||||
|
||||
def token_user
|
||||
token = params[:access_token]
|
||||
access_token = Token.find_by_token(token)
|
||||
if access_token
|
||||
@token_user ||= access_token.user
|
||||
end
|
||||
end
|
||||
|
||||
def doorkeeper_user
|
||||
return unless doorkeeper_token.present?
|
||||
doorkeeper_render_error unless valid_doorkeeper_token?
|
||||
@doorkeeper_user ||= User.find(doorkeeper_token.resource_owner_id)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
@permitted_params ||= PermittedParams.new(params)
|
||||
end
|
||||
|
||||
end
|
3
app/controllers/api/synapses_controller.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Api::SynapsesController < API::RestfulController
|
||||
|
||||
end
|
19
app/controllers/api/tokens_controller.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Api::TokensController < API::RestfulController
|
||||
|
||||
def my_tokens
|
||||
raise Pundit::NotAuthorizedError.new unless current_user
|
||||
instantiate_collection page_collection: false, timeframe_collection: false
|
||||
respond_with_collection
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_serializer
|
||||
"#{resource_name}_serializer".camelize.constantize
|
||||
end
|
||||
|
||||
def visible_records
|
||||
current_user.tokens
|
||||
end
|
||||
|
||||
end
|
3
app/controllers/api/topics_controller.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Api::TopicsController < API::RestfulController
|
||||
|
||||
end
|
|
@ -1,7 +1,15 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
include ApplicationHelper
|
||||
include Pundit
|
||||
include PunditExtra
|
||||
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
|
||||
protect_from_forgery
|
||||
|
||||
before_filter :get_invite_link
|
||||
after_action :allow_embedding
|
||||
|
||||
def default_serializer_options
|
||||
{ root: false }
|
||||
end
|
||||
|
||||
# this is for global login
|
||||
include ContentHelper
|
||||
|
@ -11,13 +19,7 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :admin?
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
unsafe_uri = request.env["REQUEST_URI"]
|
||||
if unsafe_uri.starts_with?('http') && !unsafe_uri.starts_with?('https')
|
||||
protocol = 'http'
|
||||
else
|
||||
protocol = 'https'
|
||||
end
|
||||
sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => protocol)
|
||||
sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => 'https')
|
||||
|
||||
if request.referer == sign_in_url
|
||||
super
|
||||
|
@ -28,6 +30,10 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def handle_unauthorized
|
||||
head :forbidden # TODO make this better
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_no_user
|
||||
|
@ -63,10 +69,10 @@ private
|
|||
authenticated? && current_user.admin
|
||||
end
|
||||
|
||||
def get_invite_link
|
||||
unsafe_uri = request.env["REQUEST_URI"] || 'https://metamaps.cc'
|
||||
valid_url = /^https?:\/\/([\w\.-]+)(:\d{1,5})?\/?$/
|
||||
safe_uri = (unsafe_uri.match(valid_url)) ? unsafe_uri : '//metamaps.cc/'
|
||||
@invite_link = "#{safe_uri}join" + (current_user ? "?code=#{current_user.code}" : "")
|
||||
def allow_embedding
|
||||
#allow all
|
||||
response.headers.except! 'X-Frame-Options'
|
||||
# or allow a whitelist
|
||||
# response.headers['X-Frame-Options'] = 'ALLOW-FROM http://blog.metamaps.cc'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,19 +4,19 @@ class MainController < ApplicationController
|
|||
include UsersHelper
|
||||
include SynapsesHelper
|
||||
|
||||
after_action :verify_policy_scoped
|
||||
|
||||
respond_to :html, :json
|
||||
|
||||
# home page
|
||||
def home
|
||||
@current = current_user
|
||||
|
||||
@maps = policy_scope(Map).order("updated_at DESC").page(1).per(20)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if authenticated?
|
||||
@maps = Map.where("maps.permission != ?", "private").order("updated_at DESC").page(1).per(20)
|
||||
respond_with(@maps, @current)
|
||||
if not authenticated?
|
||||
render 'main/home'
|
||||
else
|
||||
respond_with(@current)
|
||||
render 'maps/activemaps'
|
||||
end
|
||||
}
|
||||
end
|
||||
|
@ -60,68 +60,34 @@ class MainController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
search = '%' + term.downcase + '%'
|
||||
builder = policy_scope(Topic)
|
||||
|
||||
if filterByMetacode
|
||||
if term == ""
|
||||
@topics = []
|
||||
builder = builder.none
|
||||
else
|
||||
search = term.downcase + '%'
|
||||
|
||||
if user
|
||||
@topics = Set.new(Topic.where('LOWER("name") like ?', search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"'))
|
||||
@topics2 = Set.new(Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"'))
|
||||
@topics3 = Set.new(Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"'))
|
||||
@topics4 = Set.new(Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"'))
|
||||
else
|
||||
@topics = Set.new(Topic.where('LOWER("name") like ?', search).where('metacode_id = ?', filterByMetacode.id).order('"name"'))
|
||||
@topics2 = Set.new(Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"'))
|
||||
@topics3 = Set.new(Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"'))
|
||||
@topics4 = Set.new(Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"'))
|
||||
end
|
||||
|
||||
#get unique elements only through the magic of Sets
|
||||
@topics = (@topics + @topics2 + @topics3 + @topics4).to_a
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
builder = builder.where(metacode_id: filterByMetacode.id)
|
||||
end
|
||||
elsif desc
|
||||
search = '%' + term.downcase + '%'
|
||||
if !user
|
||||
@topics = Topic.where('LOWER("desc") like ?', search).order('"name"')
|
||||
elsif user
|
||||
@topics = Topic.where('LOWER("desc") like ?', search).where('user_id = ?', user).order('"name"')
|
||||
end
|
||||
builder = builder.where('LOWER("desc") like ?', search)
|
||||
elsif link
|
||||
search = '%' + term.downcase + '%'
|
||||
if !user
|
||||
@topics = Topic.where('LOWER("link") like ?', search).order('"name"')
|
||||
elsif user
|
||||
@topics = Topic.where('LOWER("link") like ?', search).where('user_id = ?', user).order('"name"')
|
||||
end
|
||||
builder = builder.where('LOWER("link") like ?', search)
|
||||
else #regular case, just search the name
|
||||
search = term.downcase + '%'
|
||||
if !user
|
||||
@topics = Topic.where('LOWER("name") like ?', search).order('"name"')
|
||||
@topics2 = Topic.where('LOWER("name") like ?', '%' + search).order('"name"')
|
||||
@topics3 = Topic.where('LOWER("desc") like ?', '%' + search).order('"name"')
|
||||
@topics4 = Topic.where('LOWER("link") like ?', '%' + search).order('"name"')
|
||||
@topics = @topics + (@topics2 - @topics)
|
||||
@topics = @topics + (@topics3 - @topics)
|
||||
@topics = @topics + (@topics4 - @topics)
|
||||
elsif user
|
||||
@topics = Topic.where('LOWER("name") like ?', search).where('user_id = ?', user).order('"name"')
|
||||
@topics2 = Topic.where('LOWER("name") like ?', '%' + search).where('user_id = ?', user).order('"name"')
|
||||
@topics3 = Topic.where('LOWER("desc") like ?', '%' + search).where('user_id = ?', user).order('"name"')
|
||||
@topics4 = Topic.where('LOWER("link") like ?', '%' + search).where('user_id = ?', user).order('"name"')
|
||||
@topics = @topics + (@topics2 - @topics)
|
||||
@topics = @topics + (@topics3 - @topics)
|
||||
@topics = @topics + (@topics4 - @topics)
|
||||
end
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
end
|
||||
|
||||
builder = builder.where(user: user) if user
|
||||
@topics = builder.order(:name)
|
||||
else
|
||||
@topics = []
|
||||
end
|
||||
|
||||
#read this next line as 'delete a topic if its private and you're either 1. logged out or 2. logged in but not the topic creator
|
||||
@topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
|
||||
|
||||
render json: autocomplete_array_json(@topics)
|
||||
end
|
||||
|
||||
|
@ -141,21 +107,21 @@ class MainController < ApplicationController
|
|||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
search = '%' + term.downcase + '%'
|
||||
query = desc ? 'LOWER("desc") like ?' : 'LOWER("name") like ?'
|
||||
if !user
|
||||
# !connor why is the limit 5 done here and not above? also, why not limit after sorting alphabetically?
|
||||
@maps = Map.where(query, search).limit(5).order('"name"')
|
||||
elsif user
|
||||
@maps = Map.where(query, search).where('user_id = ?', user).order('"name"')
|
||||
builder = policy_scope(Map)
|
||||
|
||||
if desc
|
||||
builder = builder.where('LOWER("desc") like ?', search)
|
||||
else
|
||||
builder = builder.where('LOWER("name") like ?', search)
|
||||
end
|
||||
builder = builder.where(user: user) if user
|
||||
@maps = builder.order(:name)
|
||||
else
|
||||
@maps = []
|
||||
end
|
||||
|
||||
#read this next line as 'delete a map if its private and you're either 1. logged out or 2. logged in but not the map creator
|
||||
@maps.to_a.delete_if {|m| m.permission == "private" && (!authenticated? || (authenticated? && current_user.id != m.user_id)) }
|
||||
|
||||
render json: autocomplete_map_array_json(@maps)
|
||||
end
|
||||
|
||||
|
@ -166,7 +132,10 @@ class MainController < ApplicationController
|
|||
|
||||
#remove "mapper:" if appended at beginning
|
||||
term = term[7..-1] if term.downcase[0..6] == "mapper:"
|
||||
@mappers = User.where('LOWER("name") like ?', term.downcase + '%').order('"name"')
|
||||
search = term.downcase + '%'
|
||||
builder = policy_scope(User) # TODO do I need to policy scope? I guess yes to verify_policy_scoped
|
||||
builder = builder.where('LOWER("name") like ?', search)
|
||||
@mappers = builder.order(:name)
|
||||
else
|
||||
@mappers = []
|
||||
end
|
||||
|
@ -181,7 +150,7 @@ class MainController < ApplicationController
|
|||
topic2id = params[:topic2id]
|
||||
|
||||
if term && !term.empty?
|
||||
@synapses = Synapse.where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"')
|
||||
@synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"')
|
||||
|
||||
# remove any duplicate synapse types that just differ by
|
||||
# leading or trailing whitespaces
|
||||
|
@ -195,23 +164,18 @@ class MainController < ApplicationController
|
|||
boolean = true
|
||||
end
|
||||
}
|
||||
|
||||
#limit to 5 results
|
||||
@synapses = @synapses.slice(0,5)
|
||||
elsif topic1id && !topic1id.empty?
|
||||
@one = Synapse.where('node1_id = ? AND node2_id = ?', topic1id, topic2id)
|
||||
@two = Synapse.where('node2_id = ? AND node1_id = ?', topic1id, topic2id)
|
||||
@one = policy_scope(Synapse).where('node1_id = ? AND node2_id = ?', topic1id, topic2id)
|
||||
@two = policy_scope(Synapse).where('node2_id = ? AND node1_id = ?', topic1id, topic2id)
|
||||
@synapses = @one + @two
|
||||
@synapses.sort! {|s1,s2| s1.desc <=> s2.desc }.to_a
|
||||
|
||||
#permissions
|
||||
@synapses.delete_if {|s| s.permission == "private" && !authenticated? }
|
||||
@synapses.delete_if {|s| s.permission == "private" && authenticated? && current_user.id != s.user_id }
|
||||
else
|
||||
@synapses = []
|
||||
end
|
||||
|
||||
#limit to 5 results
|
||||
@synapses = @synapses.slice(0,5)
|
||||
|
||||
render json: autocomplete_synapse_array_json(@synapses)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
class MappingsController < ApplicationController
|
||||
|
||||
before_filter :require_user, only: [:create, :update, :destroy]
|
||||
before_action :require_user, only: [:create, :update, :destroy]
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
respond_to :json
|
||||
|
||||
# GET /mappings/1.json
|
||||
def show
|
||||
@mapping = Mapping.find(params[:id])
|
||||
authorize @mapping
|
||||
|
||||
render json: @mapping
|
||||
end
|
||||
|
@ -14,11 +17,12 @@ class MappingsController < ApplicationController
|
|||
# POST /mappings.json
|
||||
def create
|
||||
@mapping = Mapping.new(mapping_params)
|
||||
|
||||
@mapping.map.touch(:updated_at)
|
||||
authorize @mapping
|
||||
@mapping.user = current_user
|
||||
|
||||
if @mapping.save
|
||||
render json: @mapping, status: :created
|
||||
Events::NewMapping.publish!(@mapping, current_user)
|
||||
else
|
||||
render json: @mapping.errors, status: :unprocessable_entity
|
||||
end
|
||||
|
@ -27,8 +31,7 @@ class MappingsController < ApplicationController
|
|||
# PUT /mappings/1.json
|
||||
def update
|
||||
@mapping = Mapping.find(params[:id])
|
||||
|
||||
@mapping.map.touch(:updated_at)
|
||||
authorize @mapping
|
||||
|
||||
if @mapping.update_attributes(mapping_params)
|
||||
head :no_content
|
||||
|
@ -40,18 +43,16 @@ class MappingsController < ApplicationController
|
|||
# DELETE /mappings/1.json
|
||||
def destroy
|
||||
@mapping = Mapping.find(params[:id])
|
||||
@map = @mapping.map
|
||||
authorize @mapping
|
||||
|
||||
@mapping.destroy
|
||||
|
||||
@map.touch(:updated_at)
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def mapping_params
|
||||
params.require(:mapping).permit(:id, :xloc, :yloc, :mappable_id, :mappable_type, :map_id, :user_id)
|
||||
params.require(:mapping).permit(:id, :xloc, :yloc, :mappable_id, :mappable_type, :map_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,106 +1,119 @@
|
|||
class MapsController < ApplicationController
|
||||
|
||||
before_filter :require_user, only: [:create, :update, :screenshot, :destroy]
|
||||
before_action :require_user, only: [:create, :update, :screenshot, :destroy]
|
||||
after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :usermaps]
|
||||
after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :usermaps]
|
||||
|
||||
respond_to :html, :json
|
||||
respond_to :html, :json, :csv
|
||||
|
||||
autocomplete :map, :name, :full => true, :extra_data => [:user_id]
|
||||
|
||||
# GET /explore/active
|
||||
# GET /explore/featured
|
||||
# GET /explore/mapper/:id
|
||||
def index
|
||||
|
||||
if request.path == "/explore"
|
||||
redirect_to activemaps_url and return
|
||||
end
|
||||
|
||||
@current = current_user
|
||||
@user = nil
|
||||
@maps = []
|
||||
@mapperId = nil
|
||||
|
||||
if !params[:page]
|
||||
page = 1
|
||||
else
|
||||
page = params[:page]
|
||||
end
|
||||
|
||||
if request.path.index("/explore/active") != nil
|
||||
@maps = Map.where("maps.permission != ?", "private").order("updated_at DESC").page(page).per(20)
|
||||
@request = "active"
|
||||
|
||||
elsif request.path.index("/explore/featured") != nil
|
||||
@maps = Map.where("maps.featured = ? AND maps.permission != ?", true, "private").order("updated_at DESC").page(page).per(20)
|
||||
@request = "featured"
|
||||
|
||||
elsif request.path.index('/explore/mine') != nil # looking for maps by me
|
||||
if !authenticated?
|
||||
redirect_to activemaps_url and return
|
||||
end
|
||||
# don't need to exclude private maps because they all belong to you
|
||||
@maps = Map.where("maps.user_id = ?", @current.id).order("updated_at DESC").page(page).per(20)
|
||||
@request = "you"
|
||||
|
||||
elsif request.path.index('/explore/mapper/') != nil # looking for maps by a mapper
|
||||
@user = User.find(params[:id])
|
||||
@maps = Map.where("maps.user_id = ? AND maps.permission != ?", @user.id, "private").order("updated_at DESC").page(page).per(20)
|
||||
@request = "mapper"
|
||||
end
|
||||
def activemaps
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(Map).order("updated_at DESC")
|
||||
.page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if @request == "active" && authenticated?
|
||||
redirect_to root_url and return
|
||||
end
|
||||
respond_with(@maps, @request, @user)
|
||||
# root url => main/home. main/home renders maps/activemaps view.
|
||||
redirect_to root_url and return if authenticated?
|
||||
respond_with(@maps, @user)
|
||||
}
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/featured
|
||||
def featuredmaps
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(
|
||||
Map.where("maps.featured = ? AND maps.permission != ?",
|
||||
true, "private")
|
||||
).order("updated_at DESC").page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mine
|
||||
def mymaps
|
||||
return redirect_to activemaps_url if !authenticated?
|
||||
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(
|
||||
Map.where("maps.user_id = ?", current_user.id)
|
||||
).order("updated_at DESC").page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mapper/:id
|
||||
def usermaps
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@user = User.find(params[:id])
|
||||
@maps = policy_scope(Map.where(user: @user))
|
||||
.order("updated_at DESC").page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id
|
||||
def show
|
||||
|
||||
@current = current_user
|
||||
@map = Map.find(params[:id]).authorize_to_show(@current)
|
||||
|
||||
if not @map
|
||||
redirect_to root_url, notice: "Access denied. That map is private." and return
|
||||
end
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@allmappers = @map.contributors
|
||||
@alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) }
|
||||
@allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) }
|
||||
@alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
|
||||
@allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) }
|
||||
@allmappings = @map.mappings.to_a.delete_if {|m|
|
||||
object = m.mappable
|
||||
!object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id)))
|
||||
!object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id)))
|
||||
}
|
||||
@allmessages = @map.messages.sort_by(&:created_at)
|
||||
|
||||
respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @map)
|
||||
respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @allmessages, @map)
|
||||
}
|
||||
format.json { render json: @map }
|
||||
format.csv { redirect_to action: :export, format: :csv }
|
||||
format.xls { redirect_to action: :export, format: :xls }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/export
|
||||
def export
|
||||
map = Map.find(params[:id])
|
||||
authorize map
|
||||
exporter = MapExportService.new(current_user, map)
|
||||
respond_to do |format|
|
||||
format.json { render json: exporter.json }
|
||||
format.csv { send_data exporter.csv }
|
||||
format.xls { @spreadsheet = exporter.xls }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# GET maps/:id/contains
|
||||
def contains
|
||||
|
||||
@current = current_user
|
||||
@map = Map.find(params[:id]).authorize_to_show(@current)
|
||||
|
||||
if not @map
|
||||
redirect_to root_url, notice: "Access denied. That map is private." and return
|
||||
end
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
@allmappers = @map.contributors
|
||||
@alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) }
|
||||
@allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) }
|
||||
@alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
|
||||
@allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) }
|
||||
@allmappings = @map.mappings.to_a.delete_if {|m|
|
||||
object = m.mappable
|
||||
!object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id)))
|
||||
!object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id)))
|
||||
}
|
||||
|
||||
@json = Hash.new()
|
||||
|
@ -109,6 +122,7 @@ class MapsController < ApplicationController
|
|||
@json['synapses'] = @allsynapses
|
||||
@json['mappings'] = @allmappings
|
||||
@json['mappers'] = @allmappers
|
||||
@json['messages'] = @map.messages.sort_by(&:created_at)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @json }
|
||||
|
@ -117,7 +131,6 @@ class MapsController < ApplicationController
|
|||
|
||||
# POST maps
|
||||
def create
|
||||
|
||||
@user = current_user
|
||||
@map = Map.new()
|
||||
@map.name = params[:name]
|
||||
|
@ -125,52 +138,59 @@ class MapsController < ApplicationController
|
|||
@map.permission = params[:permission]
|
||||
@map.user = @user
|
||||
@map.arranged = false
|
||||
@map.save
|
||||
|
||||
if params[:topicsToMap]
|
||||
@all = params[:topicsToMap]
|
||||
@all = @all.split(',')
|
||||
@all.each do |topic|
|
||||
topic = topic.split('/')
|
||||
@mapping = Mapping.new()
|
||||
@mapping.user = @user
|
||||
@mapping.map = @map
|
||||
@mapping.mappable = Topic.find(topic[0])
|
||||
@mapping.xloc = topic[1]
|
||||
@mapping.yloc = topic[2]
|
||||
@mapping.save
|
||||
mapping = Mapping.new()
|
||||
mapping.user = @user
|
||||
mapping.mappable = Topic.find(topic[0])
|
||||
mapping.xloc = topic[1]
|
||||
mapping.yloc = topic[2]
|
||||
@map.topicmappings << mapping
|
||||
authorize mapping, :create
|
||||
mapping.save
|
||||
end
|
||||
|
||||
if params[:synapsesToMap]
|
||||
@synAll = params[:synapsesToMap]
|
||||
@synAll = @synAll.split(',')
|
||||
@synAll.each do |synapse_id|
|
||||
@mapping = Mapping.new()
|
||||
@mapping.user = @user
|
||||
@mapping.map = @map
|
||||
@mapping.mappable = Synapse.find(synapse_id)
|
||||
@mapping.save
|
||||
mapping = Mapping.new()
|
||||
mapping.user = @user
|
||||
mapping.map = @map
|
||||
mapping.mappable = Synapse.find(synapse_id)
|
||||
@map.synapsemappings << mapping
|
||||
authorize mapping, :create
|
||||
mapping.save
|
||||
end
|
||||
end
|
||||
|
||||
@map.arranged = true
|
||||
@map.save
|
||||
end
|
||||
|
||||
authorize @map
|
||||
|
||||
if @map.save
|
||||
respond_to do |format|
|
||||
format.json { render :json => @map }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json { render :json => "invalid params" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT maps/:id
|
||||
def update
|
||||
@current = current_user
|
||||
@map = Map.find(params[:id]).authorize_to_edit(@current)
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
respond_to do |format|
|
||||
if !@map
|
||||
format.json { render json: "unauthorized" }
|
||||
elsif @map.update_attributes(map_params)
|
||||
if @map.update_attributes(map_params)
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @map.errors, status: :unprocessable_entity }
|
||||
|
@ -180,10 +200,9 @@ class MapsController < ApplicationController
|
|||
|
||||
# POST maps/:id/upload_screenshot
|
||||
def screenshot
|
||||
@current = current_user
|
||||
@map = Map.find(params[:id]).authorize_to_edit(@current)
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
if @map
|
||||
png = Base64.decode64(params[:encoded_image]['data:image/png;base64,'.length .. -1])
|
||||
StringIO.open(png) do |data|
|
||||
data.class.class_eval { attr_accessor :original_filename, :content_type }
|
||||
|
@ -197,27 +216,19 @@ class MapsController < ApplicationController
|
|||
else
|
||||
render :json => {:message => "Failed to upload image."}
|
||||
end
|
||||
else
|
||||
render :json => {:message => "Unauthorized to set map screenshot."}
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE maps/:id
|
||||
def destroy
|
||||
@current = current_user
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
@map = Map.find(params[:id]).authorize_to_delete(@current)
|
||||
|
||||
@map.delete if @map
|
||||
@map.delete
|
||||
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
if @map
|
||||
render json: "success"
|
||||
else
|
||||
render json: "unauthorized"
|
||||
format.json do
|
||||
head :no_content
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -225,6 +236,6 @@ class MapsController < ApplicationController
|
|||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def map_params
|
||||
params.require(:map).permit(:id, :name, :arranged, :desc, :permission, :user_id)
|
||||
params.require(:map).permit(:id, :name, :arranged, :desc, :permission)
|
||||
end
|
||||
end
|
||||
|
|
67
app/controllers/messages_controller.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
class MessagesController < ApplicationController
|
||||
|
||||
before_action :require_user, except: [:show]
|
||||
after_action :verify_authorized
|
||||
|
||||
# GET /messages/1.json
|
||||
def show
|
||||
@message = Message.find(params[:id])
|
||||
authorize @message
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @message }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /messages
|
||||
# POST /messages.json
|
||||
def create
|
||||
@message = Message.new(message_params)
|
||||
@message.user = current_user
|
||||
authorize @message
|
||||
|
||||
respond_to do |format|
|
||||
if @message.save
|
||||
format.json { render json: @message, status: :created, location: messages_url }
|
||||
else
|
||||
format.json { render json: @message.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /messages/1
|
||||
# PUT /messages/1.json
|
||||
def update
|
||||
@message = Message.find(params[:id])
|
||||
authorize @message
|
||||
|
||||
respond_to do |format|
|
||||
if @message.update_attributes(message_params)
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @message.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /messages/1
|
||||
# DELETE /messages/1.json
|
||||
def destroy
|
||||
@message = Message.find(params[:id])
|
||||
authorize @message
|
||||
|
||||
@message.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def message_params
|
||||
#params.require(:message).permit(:id, :resource_id, :message)
|
||||
params.permit(:id, :resource_id, :resource_type, :message)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
class MetacodeSetsController < ApplicationController
|
||||
|
||||
before_filter :require_admin
|
||||
before_action :require_admin
|
||||
|
||||
# GET /metacode_sets
|
||||
# GET /metacode_sets.json
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
class MetacodesController < ApplicationController
|
||||
before_filter :require_admin, except: [:index]
|
||||
before_action :require_admin, except: [:index, :show]
|
||||
|
||||
# GET /metacodes
|
||||
# GET /metacodes.json
|
||||
def index
|
||||
@metacodes = Metacode.order("name").all
|
||||
@metacodes.map do |metacode|
|
||||
metacode.icon = ActionController::Base.helpers.asset_path(metacode.icon)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
|
@ -15,23 +12,23 @@ class MetacodesController < ApplicationController
|
|||
redirect_to root_url, notice: "You need to be an admin for that."
|
||||
return false
|
||||
end
|
||||
render action: "index"
|
||||
render :index
|
||||
}
|
||||
format.json { render json: @metacodes }
|
||||
end
|
||||
end
|
||||
|
||||
### SHOW IS CURRENTLY DISABLED
|
||||
# GET /metacodes/1
|
||||
# GET /metacodes/1.json
|
||||
# def show
|
||||
# @metacode = Metacode.find(params[:id])
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html # show.html.erb
|
||||
# format.json { render json: @metacode }
|
||||
# end
|
||||
# end
|
||||
# GET /metacodes/Action.json
|
||||
# GET /metacodes/action.json
|
||||
def show
|
||||
@metacode = Metacode.where('DOWNCASE(name) = ?', downcase(params[:name])).first if params[:name]
|
||||
@metacode = Metacode.find(params[:id]) unless @metacode
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @metacode }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /metacodes/new
|
||||
# GET /metacodes/new.json
|
||||
|
@ -59,7 +56,7 @@ class MetacodesController < ApplicationController
|
|||
format.html { redirect_to metacodes_url, notice: 'Metacode was successfully created.' }
|
||||
format.json { render json: @metacode, status: :created, location: metacodes_url }
|
||||
else
|
||||
format.html { render action: "new" }
|
||||
format.html { render :new }
|
||||
format.json { render json: @metacode.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
|
@ -71,34 +68,20 @@ class MetacodesController < ApplicationController
|
|||
@metacode = Metacode.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @metacode.update_attributes(metacode_params)
|
||||
if @metacode.update(metacode_params)
|
||||
format.html { redirect_to metacodes_url, notice: 'Metacode was successfully updated.' }
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.html { render action: "edit" }
|
||||
format.html { render :edit }
|
||||
format.json { render json: @metacode.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
### DESTROY IS CURRENTLY DISABLED
|
||||
# DELETE /metacodes/1
|
||||
# DELETE /metacodes/1.json
|
||||
# def destroy
|
||||
# @metacode = Metacode.find(params[:id])
|
||||
# @metacode.destroy
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html { redirect_to metacodes_url }
|
||||
# format.json { head :no_content }
|
||||
# end
|
||||
# end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def metacode_params
|
||||
params.require(:metacode).permit(:id, :name, :icon, :color)
|
||||
params.require(:metacode).permit(:id, :name, :aws_icon, :manual_icon, :color)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
class SynapsesController < ApplicationController
|
||||
include TopicsHelper
|
||||
|
||||
before_filter :require_user, only: [:create, :update, :destroy]
|
||||
before_action :require_user, only: [:create, :update, :destroy]
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
respond_to :json
|
||||
|
||||
# GET /synapses/1.json
|
||||
def show
|
||||
@synapse = Synapse.find(params[:id])
|
||||
|
||||
#.authorize_to_show(@current)
|
||||
|
||||
#if not @synapse
|
||||
# redirect_to root_url and return
|
||||
#end
|
||||
authorize @synapse
|
||||
|
||||
render json: @synapse
|
||||
end
|
||||
|
@ -22,7 +19,8 @@ class SynapsesController < ApplicationController
|
|||
# POST /synapses.json
|
||||
def create
|
||||
@synapse = Synapse.new(synapse_params)
|
||||
@synapse.update_attribute :desc, "" if @synapse.desc.nil?
|
||||
@synapse.desc = "" if @synapse.desc.nil?
|
||||
authorize @synapse
|
||||
|
||||
respond_to do |format|
|
||||
if @synapse.save
|
||||
|
@ -37,7 +35,8 @@ class SynapsesController < ApplicationController
|
|||
# PUT /synapses/1.json
|
||||
def update
|
||||
@synapse = Synapse.find(params[:id])
|
||||
@synapse.update_attribute :desc, "" if @synapse.desc.nil?
|
||||
@synapse.desc = "" if @synapse.desc.nil?
|
||||
authorize @synapse
|
||||
|
||||
respond_to do |format|
|
||||
if @synapse.update_attributes(synapse_params)
|
||||
|
@ -50,8 +49,9 @@ class SynapsesController < ApplicationController
|
|||
|
||||
# DELETE synapses/:id
|
||||
def destroy
|
||||
@synapse = Synapse.find(params[:id]).authorize_to_delete(current_user)
|
||||
@synapse.delete if @synapse
|
||||
@synapse = Synapse.find(params[:id])
|
||||
authorize @synapse
|
||||
@synapse.delete
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
class TopicsController < ApplicationController
|
||||
include TopicsHelper
|
||||
|
||||
before_filter :require_user, only: [:create, :update, :destroy]
|
||||
before_action :require_user, only: [:create, :update, :destroy]
|
||||
after_action :verify_authorized, except: :autocomplete_topic
|
||||
|
||||
respond_to :html, :js, :json
|
||||
|
||||
# GET /topics/autocomplete_topic
|
||||
def autocomplete_topic
|
||||
@current = current_user
|
||||
term = params[:term]
|
||||
if term && !term.empty?
|
||||
@topics = Topic.where('LOWER("name") like ?', term.downcase + '%').order('"name"')
|
||||
|
||||
#read this next line as 'delete a topic if its private and you're either
|
||||
#1. logged out or 2. logged in but not the topic creator
|
||||
@topics.to_a.delete_if {|t| t.permission == "private" &&
|
||||
(!authenticated? || (authenticated? && @current.id != t.user_id)) }
|
||||
@topics = policy_scope(Topic.where('LOWER("name") like ?', term.downcase + '%')).order('"name"')
|
||||
else
|
||||
@topics = []
|
||||
end
|
||||
|
@ -24,29 +19,17 @@ class TopicsController < ApplicationController
|
|||
|
||||
# GET topics/:id
|
||||
def show
|
||||
@current = current_user
|
||||
@topic = Topic.find(params[:id]).authorize_to_show(@current)
|
||||
|
||||
if not @topic
|
||||
redirect_to root_url, notice: "Access denied. That topic is private." and return
|
||||
end
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@alltopics = ([@topic] + @topic.relatives).delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } # should limit to topics visible to user
|
||||
@allsynapses = @topic.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) }
|
||||
|
||||
@allcreators = []
|
||||
@alltopics.each do |t|
|
||||
if @allcreators.index(t.user) == nil
|
||||
@allcreators.push(t.user)
|
||||
end
|
||||
end
|
||||
@allsynapses.each do |s|
|
||||
if @allcreators.index(s.user) == nil
|
||||
@allcreators.push(s.user)
|
||||
end
|
||||
end
|
||||
@alltopics = [@topic].concat(policy_scope(Topic.relatives1(@topic.id)).to_a).concat(policy_scope(Topic.relatives2(@topic.id)).to_a)
|
||||
@allsynapses = policy_scope(Synapse.for_topic(@topic.id)).to_a
|
||||
puts @alltopics.length
|
||||
puts @allsynapses.length
|
||||
@allcreators = @alltopics.map(&:user).uniq
|
||||
@allcreators += @allsynapses.map(&:user).uniq
|
||||
|
||||
respond_with(@allsynapses, @alltopics, @allcreators, @topic)
|
||||
}
|
||||
|
@ -56,27 +39,14 @@ class TopicsController < ApplicationController
|
|||
|
||||
# GET topics/:id/network
|
||||
def network
|
||||
@current = current_user
|
||||
@topic = Topic.find(params[:id]).authorize_to_show(@current)
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
|
||||
if not @topic
|
||||
redirect_to root_url, notice: "Access denied. That topic is private." and return
|
||||
end
|
||||
@alltopics = [@topic].concat(policy_scope(Topic.relatives1(@topic.id)).to_a).concat(policy_scope(Topic.relatives2(@topic.id)).to_a)
|
||||
@allsynapses = policy_scope(Synapse.for_topic(@topic.id))
|
||||
|
||||
@alltopics = @topic.relatives.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) }
|
||||
@allsynapses = @topic.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) }
|
||||
@allcreators = []
|
||||
@allcreators.push(@topic.user)
|
||||
@alltopics.each do |t|
|
||||
if @allcreators.index(t.user) == nil
|
||||
@allcreators.push(t.user)
|
||||
end
|
||||
end
|
||||
@allsynapses.each do |s|
|
||||
if @allcreators.index(s.user) == nil
|
||||
@allcreators.push(s.user)
|
||||
end
|
||||
end
|
||||
@allcreators = @alltopics.map(&:user).uniq
|
||||
@allcreators += @allsynapses.map(&:user).uniq
|
||||
|
||||
@json = Hash.new()
|
||||
@json['topic'] = @topic
|
||||
|
@ -91,29 +61,19 @@ class TopicsController < ApplicationController
|
|||
|
||||
# GET topics/:id/relative_numbers
|
||||
def relative_numbers
|
||||
@current = current_user
|
||||
@topic = Topic.find(params[:id]).authorize_to_show(@current)
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
|
||||
if not @topic
|
||||
redirect_to root_url, notice: "Access denied. That topic is private." and return
|
||||
topicsAlreadyHas = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||
|
||||
@alltopics = policy_scope(Topic.relatives1(@topic.id)).to_a.concat(policy_scope(Topic.relatives2(@topic.id)).to_a).uniq
|
||||
@alltopics.delete_if do |topic|
|
||||
topicsAlreadyHas.index(topic.id) != nil
|
||||
end
|
||||
|
||||
@topicsAlreadyHas = params[:network] ? params[:network].split(',') : []
|
||||
|
||||
@alltopics = @topic.relatives.to_a.delete_if {|t|
|
||||
@topicsAlreadyHas.index(t.id.to_s) != nil ||
|
||||
(t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)))
|
||||
}
|
||||
|
||||
@alltopics.uniq!
|
||||
|
||||
@json = Hash.new()
|
||||
@json = Hash.new(0)
|
||||
@alltopics.each do |t|
|
||||
if @json[t.metacode.id]
|
||||
@json[t.metacode.id] += 1
|
||||
else
|
||||
@json[t.metacode.id] = 1
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -123,45 +83,32 @@ class TopicsController < ApplicationController
|
|||
|
||||
# GET topics/:id/relatives
|
||||
def relatives
|
||||
@current = current_user
|
||||
@topic = Topic.find(params[:id]).authorize_to_show(@current)
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
|
||||
if not @topic
|
||||
redirect_to root_url, notice: "Access denied. That topic is private." and return
|
||||
topicsAlreadyHas = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||
|
||||
alltopics = policy_scope(Topic.relatives1(@topic.id)).to_a.concat(policy_scope(Topic.relatives2(@topic.id)).to_a).uniq
|
||||
alltopics.delete_if do |topic|
|
||||
topicsAlreadyHas.index(topic.id.to_s) != nil
|
||||
end
|
||||
|
||||
@topicsAlreadyHas = params[:network] ? params[:network].split(',') : []
|
||||
|
||||
@alltopics = @topic.relatives.to_a.delete_if {|t|
|
||||
@topicsAlreadyHas.index(t.id.to_s) != nil ||
|
||||
(params[:metacode] && t.metacode_id.to_s != params[:metacode]) ||
|
||||
(t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)))
|
||||
}
|
||||
|
||||
@alltopics.uniq!
|
||||
|
||||
@allsynapses = @topic.synapses.to_a.delete_if {|s|
|
||||
(s.topic1 == @topic && @alltopics.index(s.topic2) == nil) ||
|
||||
(s.topic2 == @topic && @alltopics.index(s.topic1) == nil)
|
||||
}
|
||||
|
||||
@creatorsAlreadyHas = params[:creators] ? params[:creators].split(',') : []
|
||||
@allcreators = []
|
||||
@alltopics.each do |t|
|
||||
if @allcreators.index(t.user) == nil && @creatorsAlreadyHas.index(t.user_id.to_s) == nil
|
||||
@allcreators.push(t.user)
|
||||
end
|
||||
end
|
||||
@allsynapses.each do |s|
|
||||
if @allcreators.index(s.user) == nil && @creatorsAlreadyHas.index(s.user_id.to_s) == nil
|
||||
@allcreators.push(s.user)
|
||||
#find synapses between topics in alltopics array
|
||||
allsynapses = policy_scope(Synapse.for_topic(@topic.id)).to_a
|
||||
synapse_ids = (allsynapses.map(&:node1_id) + allsynapses.map(&:node2_id)).uniq
|
||||
allsynapses.delete_if do |synapse|
|
||||
synapse_ids.index(synapse.id) != nil
|
||||
end
|
||||
|
||||
creatorsAlreadyHas = params[:creators] ? params[:creators].split(',').map(&:to_i) : []
|
||||
allcreators = (alltopics.map(&:user) + allsynapses.map(&:user)).uniq.delete_if do |user|
|
||||
creatorsAlreadyHas.index(user.id) != nil
|
||||
end
|
||||
|
||||
@json = Hash.new()
|
||||
@json['topics'] = @alltopics
|
||||
@json['synapses'] = @allsynapses
|
||||
@json['creators'] = @allcreators
|
||||
@json['topics'] = alltopics
|
||||
@json['synapses'] = allsynapses
|
||||
@json['creators'] = allcreators
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @json }
|
||||
|
@ -172,6 +119,7 @@ class TopicsController < ApplicationController
|
|||
# POST /topics.json
|
||||
def create
|
||||
@topic = Topic.new(topic_params)
|
||||
authorize @topic
|
||||
|
||||
respond_to do |format|
|
||||
if @topic.save
|
||||
|
@ -186,6 +134,7 @@ class TopicsController < ApplicationController
|
|||
# PUT /topics/1.json
|
||||
def update
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
|
||||
respond_to do |format|
|
||||
if @topic.update_attributes(topic_params)
|
||||
|
@ -198,10 +147,10 @@ class TopicsController < ApplicationController
|
|||
|
||||
# DELETE topics/:id
|
||||
def destroy
|
||||
@current = current_user
|
||||
@topic = Topic.find(params[:id]).authorize_to_delete(@current)
|
||||
@topic.delete if @topic
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
|
||||
@topic.delete
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Users::RegistrationsController < Devise::RegistrationsController
|
||||
before_filter :configure_sign_up_params, only: [:create]
|
||||
before_filter :configure_account_update_params, only: [:update]
|
||||
before_action :configure_sign_up_params, only: [:create]
|
||||
before_action :configure_account_update_params, only: [:update]
|
||||
|
||||
protected
|
||||
def after_sign_up_path_for(resource)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class UsersController < ApplicationController
|
||||
before_filter :require_user, only: [:edit, :update, :updatemetacodes]
|
||||
before_action :require_user, only: [:edit, :update, :updatemetacodes]
|
||||
|
||||
respond_to :html, :json
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
module ApplicationHelper
|
||||
def get_metacodeset
|
||||
@m = user.settings.metacodes
|
||||
@m = current_user.settings.metacodes
|
||||
set = @m[0].include?("metacodeset") ? MetacodeSet.find(@m[0].sub("metacodeset-","").to_i) : false
|
||||
return set
|
||||
end
|
||||
|
||||
def user_metacodes
|
||||
@m = user.settings.metacodes
|
||||
@m = current_user.settings.metacodes
|
||||
set = get_metacodeset
|
||||
if set
|
||||
@metacodes = set.metacodes.to_a
|
||||
|
@ -15,4 +15,8 @@ module ApplicationHelper
|
|||
end
|
||||
@metacodes.sort! {|m1,m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1)
|
||||
end
|
||||
|
||||
def determine_invite_link
|
||||
"#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : "")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module TopicsHelper
|
|||
topic['id'] = t.id
|
||||
topic['label'] = t.name
|
||||
topic['value'] = t.name
|
||||
topic['description'] = t.desc.truncate(70) # make this return matched results
|
||||
topic['description'] = t.desc ? t.desc.truncate(70) : '' # make this return matched results
|
||||
topic['type'] = t.metacode.name
|
||||
topic['typeImageURL'] = t.metacode.icon
|
||||
topic['permission'] = t.permission
|
||||
|
|
10
app/models/concerns/routing.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Routing
|
||||
extend ActiveSupport::Concern
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
included do
|
||||
def default_url_options
|
||||
ActionMailer::Base.default_url_options
|
||||
end
|
||||
end
|
||||
end
|
31
app/models/event.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
class Event < ActiveRecord::Base
|
||||
KINDS = %w[topic_added_to_map synapse_added_to_map]
|
||||
|
||||
#has_many :notifications, dependent: :destroy
|
||||
belongs_to :eventable, polymorphic: true
|
||||
belongs_to :map
|
||||
belongs_to :user
|
||||
|
||||
scope :chronologically, -> { order('created_at asc') }
|
||||
|
||||
after_create :notify_webhooks!, if: :map
|
||||
|
||||
validates_inclusion_of :kind, :in => KINDS
|
||||
validates_presence_of :eventable
|
||||
|
||||
#def notify!(user)
|
||||
# notifications.create!(user: user)
|
||||
#end
|
||||
|
||||
def belongs_to?(this_user)
|
||||
self.user_id == this_user.id
|
||||
end
|
||||
|
||||
def notify_webhooks!
|
||||
#group = self.discussion.group
|
||||
self.map.webhooks.each { |webhook| WebhookService.publish! webhook: webhook, event: self }
|
||||
#group.webhooks.each { |webhook| WebhookService.publish! webhook: webhook, event: self }
|
||||
end
|
||||
handle_asynchronously :notify_webhooks!
|
||||
|
||||
end
|
18
app/models/events/new_mapping.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class Events::NewMapping < Event
|
||||
#after_create :notify_users!
|
||||
|
||||
def self.publish!(mapping, user)
|
||||
create!(kind: mapping.mappable_type == "Topic" ? "topic_added_to_map" : "synapse_added_to_map",
|
||||
eventable: mapping,
|
||||
map: mapping.map,
|
||||
user: user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#def notify_users!
|
||||
# unless comment_vote.user == comment_vote.comment_user
|
||||
# notify!(comment_vote.comment_user)
|
||||
# end
|
||||
#end
|
||||
end
|
|
@ -6,6 +6,10 @@ class Map < ActiveRecord::Base
|
|||
has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy
|
||||
has_many :topics, through: :topicmappings, source: :mappable, source_type: "Topic"
|
||||
has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse"
|
||||
has_many :messages, as: :resource, dependent: :destroy
|
||||
|
||||
has_many :webhooks, as: :hookable
|
||||
has_many :events, -> { includes :user }, as: :eventable, dependent: :destroy
|
||||
|
||||
# This method associates the attribute ":image" with a file attachment
|
||||
has_attached_file :screenshot, :styles => {
|
||||
|
@ -13,6 +17,7 @@ class Map < ActiveRecord::Base
|
|||
#:full => ['940x630#', :png]
|
||||
},
|
||||
:default_url => 'https://s3.amazonaws.com/metamaps-assets/site/missing-map.png'
|
||||
|
||||
validates :name, presence: true
|
||||
validates :arranged, inclusion: { in: [true, false] }
|
||||
validates :permission, presence: true
|
||||
|
@ -41,73 +46,44 @@ class Map < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def topic_count
|
||||
self.topics.length
|
||||
topics.length
|
||||
end
|
||||
|
||||
def synapse_count
|
||||
self.synapses.length
|
||||
synapses.length
|
||||
end
|
||||
|
||||
def user_name
|
||||
self.user.name
|
||||
user.name
|
||||
end
|
||||
|
||||
def user_image
|
||||
self.user.image.url
|
||||
user.image.url
|
||||
end
|
||||
|
||||
def contributor_count
|
||||
self.contributors.length
|
||||
contributors.length
|
||||
end
|
||||
|
||||
def screenshot_url
|
||||
self.screenshot.url(:thumb)
|
||||
screenshot.url(:thumb)
|
||||
end
|
||||
|
||||
def created_at_str
|
||||
self.created_at.strftime("%m/%d/%Y")
|
||||
created_at.strftime("%m/%d/%Y")
|
||||
end
|
||||
|
||||
def updated_at_str
|
||||
self.updated_at.strftime("%m/%d/%Y")
|
||||
updated_at.strftime("%m/%d/%Y")
|
||||
end
|
||||
|
||||
def as_json(options={})
|
||||
json = super(:methods =>[:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :screenshot_url], :except => [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at])
|
||||
json[:created_at_clean] = self.created_at_str
|
||||
json[:updated_at_clean] = self.updated_at_str
|
||||
json[:created_at_clean] = created_at_str
|
||||
json[:updated_at_clean] = updated_at_str
|
||||
json
|
||||
end
|
||||
|
||||
##### PERMISSIONS ######
|
||||
|
||||
def authorize_to_delete(user)
|
||||
if (self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# returns false if user not allowed to 'show' Topic, Synapse, or Map
|
||||
def authorize_to_show(user)
|
||||
if (self.permission == "private" && self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# returns false if user not allowed to 'edit' Topic, Synapse, or Map
|
||||
def authorize_to_edit(user)
|
||||
if !user
|
||||
return false
|
||||
elsif (self.permission == "private" && self.user != user)
|
||||
return false
|
||||
elsif (self.permission == "public" && self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
def decode_base64(imgBase64)
|
||||
decoded_data = Base64.decode64(imgBase64)
|
||||
|
||||
|
|
|
@ -4,11 +4,16 @@ class Mapping < ActiveRecord::Base
|
|||
scope :synapsemapping, -> { where(mappable_type: :Synapse) }
|
||||
|
||||
belongs_to :mappable, polymorphic: true
|
||||
|
||||
belongs_to :map, :class_name => "Map", :foreign_key => "map_id"
|
||||
|
||||
belongs_to :map, :class_name => "Map", :foreign_key => "map_id", touch: true
|
||||
belongs_to :user
|
||||
|
||||
validates :xloc, presence: true,
|
||||
unless: Proc.new { |m| m.mappable_type == 'Synapse' }
|
||||
validates :yloc, presence: true,
|
||||
unless: Proc.new { |m| m.mappable_type == 'Synapse' }
|
||||
validates :map, presence: true
|
||||
validates :mappable, presence: true
|
||||
|
||||
def user_name
|
||||
self.user.name
|
||||
end
|
||||
|
|
19
app/models/message.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Message < ActiveRecord::Base
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :resource, polymorphic: true
|
||||
|
||||
def user_name
|
||||
self.user.name
|
||||
end
|
||||
|
||||
def user_image
|
||||
self.user.image.url
|
||||
end
|
||||
|
||||
def as_json(options={})
|
||||
json = super(:methods =>[:user_name, :user_image])
|
||||
json
|
||||
end
|
||||
|
||||
end
|
|
@ -1,9 +1,37 @@
|
|||
class Metacode < ActiveRecord::Base
|
||||
|
||||
has_many :in_metacode_sets
|
||||
has_many :metacode_sets, :through => :in_metacode_sets
|
||||
has_many :topics
|
||||
|
||||
# This method associates the attribute ":aws_icon" with a file attachment
|
||||
has_attached_file :aws_icon, :styles => {
|
||||
:ninetysix => ['96x96#', :png],
|
||||
},
|
||||
:default_url => 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_wildcard.png'
|
||||
|
||||
# Validate the attached icon is image/jpg, image/png, etc
|
||||
validates_attachment_content_type :aws_icon, :content_type => /\Aimage\/.*\Z/
|
||||
|
||||
validate :aws_xor_manual_icon
|
||||
validate :manual_icon_https
|
||||
before_create do
|
||||
self.manual_icon = nil if self.manual_icon == ""
|
||||
end
|
||||
|
||||
def icon(*args)
|
||||
if manual_icon.present?
|
||||
manual_icon
|
||||
else
|
||||
aws_icon(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def as_json(options={})
|
||||
default = super(options)
|
||||
default[:icon] = icon
|
||||
default.except('aws_icon_file_name', 'aws_icon_content_type', 'aws_icon_file_size', 'aws_icon_updated_at', 'manual_icon')
|
||||
end
|
||||
|
||||
def hasSelected(user)
|
||||
return true if user.settings.metacodes.include? self.id.to_s
|
||||
return false
|
||||
|
@ -13,4 +41,23 @@ class Metacode < ActiveRecord::Base
|
|||
return true if self.metacode_sets.include? metacode_set
|
||||
return false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def aws_xor_manual_icon
|
||||
if aws_icon.blank? && manual_icon.blank?
|
||||
errors.add(:base, "Either aws_icon or manual_icon is required")
|
||||
end
|
||||
if aws_icon.present? && manual_icon.present?
|
||||
errors.add(:base, "Specify aws_icon or manual_icon, not both")
|
||||
end
|
||||
end
|
||||
|
||||
def manual_icon_https
|
||||
if manual_icon.present?
|
||||
unless manual_icon.starts_with? 'https'
|
||||
errors.add(:base, "Manual icon must begin with https")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
33
app/models/permitted_params.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class PermittedParams < Struct.new(:params)
|
||||
|
||||
%w[map synapse topic mapping token].each do |kind|
|
||||
define_method(kind) do
|
||||
permitted_attributes = self.send("#{kind}_attributes")
|
||||
params.require(kind).permit(*permitted_attributes)
|
||||
end
|
||||
alias_method :"api_#{kind}", kind.to_sym
|
||||
end
|
||||
|
||||
alias :read_attribute_for_serialization :send
|
||||
|
||||
def token_attributes
|
||||
[:description]
|
||||
end
|
||||
|
||||
def map_attributes
|
||||
[:name, :desc, :permission, :arranged]
|
||||
end
|
||||
|
||||
def synapse_attributes
|
||||
[:desc, :category, :weight, :permission, :node1_id, :node2_id]
|
||||
end
|
||||
|
||||
def topic_attributes
|
||||
[:name, :desc, :link, :permission, :metacode_id]
|
||||
end
|
||||
|
||||
def mapping_attributes
|
||||
[:xloc, :yloc, :map_id, :mappable_type, :mappable_id]
|
||||
end
|
||||
|
||||
end
|
|
@ -1,5 +1,4 @@
|
|||
class Synapse < ActiveRecord::Base
|
||||
|
||||
belongs_to :user
|
||||
|
||||
belongs_to :topic1, :class_name => "Topic", :foreign_key => "node1_id"
|
||||
|
@ -11,44 +10,32 @@ class Synapse < ActiveRecord::Base
|
|||
validates :desc, length: { minimum: 0, allow_nil: false }
|
||||
|
||||
validates :permission, presence: true
|
||||
validates :node1_id, presence: true
|
||||
validates :node2_id, presence: true
|
||||
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
||||
|
||||
validates :category, inclusion: { in: ['from-to', 'both'], allow_nil: true }
|
||||
|
||||
scope :for_topic, ->(topic_id = nil) {
|
||||
where("node1_id = ? OR node2_id = ?", topic_id, topic_id)
|
||||
}
|
||||
|
||||
# :nocov:
|
||||
def user_name
|
||||
self.user.name
|
||||
user.name
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# :nocov:
|
||||
def user_image
|
||||
self.user.image.url
|
||||
user.image.url
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# :nocov:
|
||||
def as_json(options={})
|
||||
super(:methods =>[:user_name, :user_image])
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
##### PERMISSIONS ######
|
||||
|
||||
# returns false if user not allowed to 'show' Topic, Synapse, or Map
|
||||
def authorize_to_show(user)
|
||||
if (self.permission == "private" && self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# returns false if user not allowed to 'edit' Topic, Synapse, or Map
|
||||
def authorize_to_edit(user)
|
||||
if (self.permission == "private" && self.user != user)
|
||||
return false
|
||||
elsif (self.permission == "public" && self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
def authorize_to_delete(user)
|
||||
if (self.user == user || user.admin)
|
||||
return self
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
|
22
app/models/token.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Token < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
before_create :assign_token
|
||||
|
||||
CHARS = 32
|
||||
|
||||
private
|
||||
def assign_token
|
||||
self.token = generate_token
|
||||
end
|
||||
|
||||
def generate_token
|
||||
loop do
|
||||
candidate = SecureRandom.base64(CHARS).gsub(/\W/, '')
|
||||
if candidate.size >= CHARS
|
||||
return candidate[0...CHARS]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -5,8 +5,8 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id', dependent: :destroy
|
||||
has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id', dependent: :destroy
|
||||
has_many :topics1, :through => :synapses2, :source => :topic1
|
||||
has_many :topics2, :through => :synapses1, :source => :topic2
|
||||
has_many :topics1, :through => :synapses2, source: :topic1
|
||||
has_many :topics2, :through => :synapses1, source: :topic2
|
||||
|
||||
has_many :mappings, as: :mappable, dependent: :destroy
|
||||
has_many :maps, :through => :mappings
|
||||
|
@ -41,6 +41,18 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
belongs_to :metacode
|
||||
|
||||
scope :relatives1, ->(topic_id = nil) {
|
||||
includes(:topics1)
|
||||
.where('synapses.node1_id = ?', topic_id)
|
||||
.references(:synapses)
|
||||
}
|
||||
|
||||
scope :relatives2, ->(topic_id = nil) {
|
||||
includes(:topics2)
|
||||
.where('synapses.node2_id = ?', topic_id)
|
||||
.references(:synapses)
|
||||
}
|
||||
|
||||
def user_name
|
||||
user.name
|
||||
end
|
||||
|
@ -69,6 +81,38 @@ class Topic < ActiveRecord::Base
|
|||
super(:methods =>[:user_name, :user_image, :map_count, :synapse_count, :inmaps, :inmapsLinks])
|
||||
end
|
||||
|
||||
# TODO move to a decorator?
|
||||
def synapses_csv(output_format = 'array')
|
||||
output = []
|
||||
synapses.each do |synapse|
|
||||
if synapse.category == 'from-to'
|
||||
if synapse.node1_id == id
|
||||
output << synapse.node1_id.to_s + '->' + synapse.node2_id.to_s
|
||||
elsif synapse.node2_id == id
|
||||
output << synapse.node2_id.to_s + '<-' + synapse.node1_id.to_s
|
||||
else
|
||||
fail 'invalid synapse on topic in synapse_csv'
|
||||
end
|
||||
elsif synapse.category == 'both'
|
||||
if synapse.node1_id == id
|
||||
output << synapse.node1_id.to_s + '<->' + synapse.node2_id.to_s
|
||||
elsif synapse.node2_id == id
|
||||
output << synapse.node2_id.to_s + '<->' + synapse.node1_id.to_s
|
||||
else
|
||||
fail 'invalid synapse on topic in synapse_csv'
|
||||
end
|
||||
end
|
||||
end
|
||||
if output_format == 'array'
|
||||
return output
|
||||
elsif output_format == 'text'
|
||||
return output.join('; ')
|
||||
else
|
||||
fail 'invalid argument to synapses_csv'
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
def topic_autocomplete_method
|
||||
"Get: #{self.name}"
|
||||
end
|
||||
|
@ -76,42 +120,4 @@ class Topic < ActiveRecord::Base
|
|||
def mk_permission
|
||||
Perm.short(permission)
|
||||
end
|
||||
|
||||
# has no viewable synapses helper function
|
||||
def has_viewable_synapses(current)
|
||||
result = false
|
||||
synapses.each do |synapse|
|
||||
if synapse.authorize_to_show(current)
|
||||
result = true
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
##### PERMISSIONS ######
|
||||
|
||||
# returns false if user not allowed to 'show' Topic, Synapse, or Map
|
||||
def authorize_to_show(user)
|
||||
if (self.permission == "private" && self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# returns false if user not allowed to 'edit' Topic, Synapse, or Map
|
||||
def authorize_to_edit(user)
|
||||
if (self.permission == "private" && self.user != user)
|
||||
return false
|
||||
elsif (self.permission == "public" && self.user != user)
|
||||
return false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
def authorize_to_delete(user)
|
||||
if (self.user == user || user.admin)
|
||||
return self
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ class User < ActiveRecord::Base
|
|||
has_many :synapses
|
||||
has_many :maps
|
||||
has_many :mappings
|
||||
has_many :tokens
|
||||
|
||||
after_create :generate_code
|
||||
|
||||
|
|
13
app/models/webhook.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class Webhook < ActiveRecord::Base
|
||||
belongs_to :hookable, polymorphic: true
|
||||
|
||||
validates :uri, presence: true
|
||||
validates :hookable, presence: true
|
||||
validates_inclusion_of :kind, in: %w[slack]
|
||||
validates :event_types, length: { minimum: 1 }
|
||||
|
||||
def headers
|
||||
{}
|
||||
end
|
||||
|
||||
end
|
72
app/models/webhooks/slack/base.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
Webhooks::Slack::Base = Struct.new(:event) do
|
||||
include Routing
|
||||
|
||||
def username
|
||||
"Metamaps Bot"
|
||||
end
|
||||
|
||||
def icon_url
|
||||
"https://pbs.twimg.com/profile_images/539300245029392385/dJ1bwnw7.jpeg"
|
||||
end
|
||||
|
||||
def text
|
||||
"something"
|
||||
end
|
||||
|
||||
def attachments
|
||||
[{
|
||||
title: attachment_title,
|
||||
text: attachment_text,
|
||||
fields: attachment_fields,
|
||||
fallback: attachment_fallback
|
||||
}]
|
||||
end
|
||||
|
||||
alias :read_attribute_for_serialization :send
|
||||
|
||||
private
|
||||
|
||||
#def motion_vote_field
|
||||
# {
|
||||
# title: "Vote on this proposal",
|
||||
# value: "#{proposal_link(eventable, "yes")} · " +
|
||||
# "#{proposal_link(eventable, "abstain")} · " +
|
||||
# "#{proposal_link(eventable, "no")} · " +
|
||||
# "#{proposal_link(eventable, "block")}"
|
||||
# }
|
||||
#end
|
||||
|
||||
def view_map_on_metamaps(text = nil)
|
||||
"<#{map_url(eventable.map)}|#{text || eventable.map.name}>"
|
||||
end
|
||||
|
||||
#def view_discussion_on_loomio(params = {})
|
||||
# { value: discussion_link(I18n.t(:"webhooks.slack.view_it_on_loomio"), params) }
|
||||
#end
|
||||
|
||||
#def proposal_link(proposal, position = nil)
|
||||
# discussion_link position || proposal.name, { proposal: proposal.key, position: position }
|
||||
#end
|
||||
|
||||
#def discussion_link(text = nil, params = {})
|
||||
# "<#{discussion_url(eventable.map, params)}|#{text || eventable.discussion.title}>"
|
||||
#end
|
||||
|
||||
def eventable
|
||||
@eventable ||= event.eventable
|
||||
end
|
||||
|
||||
def author
|
||||
@author ||= eventable.author
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#webhooks:
|
||||
# slack:
|
||||
# motion_closed: "*%{name}* has closed"
|
||||
# motion_closing_soon: "*%{name}* has a proposal closing in 24 hours"
|
||||
# motion_outcome_created: "*%{author}* published an outcome in *%{name}*"
|
||||
# motion_outcome_updated: "*%{author}* updated the outcome for *%{name}*"
|
||||
# new_motion: "*%{author}* started a new proposal in *%{name}*"
|
||||
# view_it_on_loomio: "View it on Loomio"
|
26
app/models/webhooks/slack/synapse_added_to_map.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class Webhooks::Slack::SynapseAddedToMap < Webhooks::Slack::Base
|
||||
|
||||
def text
|
||||
"\"*#{eventable.mappable.topic1.name}* #{eventable.mappable.desc || '->'} *#{eventable.mappable.topic2.name}*\" was added as a connection to the map *#{view_map_on_metamaps()}*"
|
||||
end
|
||||
|
||||
def attachment_fallback
|
||||
"" #{}"*#{eventable.name}*\n#{eventable.description}\n"
|
||||
end
|
||||
|
||||
def attachment_title
|
||||
"" #proposal_link(eventable)
|
||||
end
|
||||
|
||||
def attachment_text
|
||||
"" # "#{eventable.description}\n"
|
||||
end
|
||||
|
||||
def attachment_fields
|
||||
[{
|
||||
title: "nothing",
|
||||
value: "nothing"
|
||||
}] #[motion_vote_field]
|
||||
end
|
||||
|
||||
end
|
27
app/models/webhooks/slack/topic_added_to_map.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Webhooks::Slack::TopicAddedToMap < Webhooks::Slack::Base
|
||||
|
||||
def text
|
||||
"New #{eventable.mappable.metacode.name} topic *#{eventable.mappable.name}* was added to the map *#{view_map_on_metamaps()}*"
|
||||
end
|
||||
# todo: it would be sweet if it sends it with the metacode as the icon_url
|
||||
|
||||
def attachment_fallback
|
||||
"" #{}"*#{eventable.name}*\n#{eventable.description}\n"
|
||||
end
|
||||
|
||||
def attachment_title
|
||||
"" #proposal_link(eventable)
|
||||
end
|
||||
|
||||
def attachment_text
|
||||
"" # "#{eventable.description}\n"
|
||||
end
|
||||
|
||||
def attachment_fields
|
||||
[{
|
||||
title: "nothing",
|
||||
value: "nothing"
|
||||
}] #[motion_vote_field]
|
||||
end
|
||||
|
||||
end
|
61
app/policies/application_policy.rb
Normal file
|
@ -0,0 +1,61 @@
|
|||
class ApplicationPolicy
|
||||
attr_reader :user, :record
|
||||
|
||||
def initialize(user, record)
|
||||
@user = user
|
||||
@record = record
|
||||
end
|
||||
|
||||
def index?
|
||||
false
|
||||
end
|
||||
|
||||
def show?
|
||||
scope.where(:id => record.id).exists?
|
||||
end
|
||||
|
||||
def create?
|
||||
false
|
||||
end
|
||||
|
||||
def new?
|
||||
create?
|
||||
end
|
||||
|
||||
def update?
|
||||
false
|
||||
end
|
||||
|
||||
def edit?
|
||||
update?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
false
|
||||
end
|
||||
|
||||
# TODO update this function to enable some flag in the interface
|
||||
# so that admins usually can't do super admin stuff unless they
|
||||
# explicitly say they want to (E.g. seeing/editing/deleting private
|
||||
# maps - they should be able to, but not by accident)
|
||||
def admin_override
|
||||
user && user.admin
|
||||
end
|
||||
|
||||
def scope
|
||||
Pundit.policy_scope!(user, record.class)
|
||||
end
|
||||
|
||||
class Scope
|
||||
attr_reader :user, :scope
|
||||
|
||||
def initialize(user, scope)
|
||||
@user = user
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def resolve
|
||||
scope
|
||||
end
|
||||
end
|
||||
end
|
26
app/policies/main_policy.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class MainPolicy < ApplicationPolicy
|
||||
def initialize(user, record)
|
||||
@user = user
|
||||
@record = nil
|
||||
end
|
||||
|
||||
def home?
|
||||
true
|
||||
end
|
||||
|
||||
def searchtopics?
|
||||
true
|
||||
end
|
||||
|
||||
def searchmaps?
|
||||
true
|
||||
end
|
||||
|
||||
def searchmappers?
|
||||
true
|
||||
end
|
||||
|
||||
def searchsynapses?
|
||||
true
|
||||
end
|
||||
end
|
57
app/policies/map_policy.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
class MapPolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
visible = ['public', 'commons']
|
||||
permission = 'maps.permission IN (?)'
|
||||
if user
|
||||
scope.where(permission + ' OR maps.user_id = ?', visible, user.id)
|
||||
else
|
||||
scope.where(permission, visible)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def activemaps?
|
||||
user.blank? # redirect to root url if authenticated for some reason
|
||||
end
|
||||
|
||||
def featuredmaps?
|
||||
true
|
||||
end
|
||||
|
||||
def mymaps?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def usermaps?
|
||||
true
|
||||
end
|
||||
|
||||
def show?
|
||||
record.permission == 'commons' || record.permission == 'public' || record.user == user
|
||||
end
|
||||
|
||||
def export?
|
||||
show?
|
||||
end
|
||||
|
||||
def contains?
|
||||
show?
|
||||
end
|
||||
|
||||
def create?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.present? && (record.permission == 'commons' || record.user == user)
|
||||
end
|
||||
|
||||
def screenshot?
|
||||
update?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
record.user == user || admin_override
|
||||
end
|
||||
end
|
43
app/policies/mapping_policy.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
class MappingPolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
# TODO base this on the map policy
|
||||
# it would be nice if we could also base this on the mappable, but that
|
||||
# gets really complicated. Devin thinks it's OK to SHOW a mapping for
|
||||
# a private topic, since you can't see the private topic anyways
|
||||
visible = ['public', 'commons']
|
||||
permission = 'maps.permission IN (?)'
|
||||
if user
|
||||
scope.joins(:maps).where(permission + ' OR maps.user_id = ?', visible, user.id)
|
||||
else
|
||||
scope.where(permission, visible)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show?
|
||||
map_policy.show? && mappable_policy.show?
|
||||
end
|
||||
|
||||
def create?
|
||||
record.map.present? && map_policy.update?
|
||||
end
|
||||
|
||||
def update?
|
||||
record.mappable_type == 'Topic' && map_policy.update?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
map_policy.update? || admin_override
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
def map_policy
|
||||
@map_policy ||= Pundit.policy(user, record.map)
|
||||
end
|
||||
|
||||
def mappable_policy
|
||||
@mappable_policy ||= Pundit.policy(user, record.mappable)
|
||||
end
|
||||
end
|
36
app/policies/message_policy.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
class MessagePolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
visible = ['public', 'commons']
|
||||
permission = 'maps.permission IN (?)'
|
||||
if user
|
||||
scope.joins(:maps).where(permission + ' OR maps.user_id = ?', visible, user.id)
|
||||
else
|
||||
scope.where(permission, visible)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show?
|
||||
resource_policy.show?
|
||||
end
|
||||
|
||||
def create?
|
||||
record.resource.present? && resource_policy.update?
|
||||
end
|
||||
|
||||
def update?
|
||||
record.user == user
|
||||
end
|
||||
|
||||
def destroy?
|
||||
record.user == user || admin_override
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
def resource_policy
|
||||
@resource_policy ||= Pundit.policy(user, record.resource)
|
||||
end
|
||||
|
||||
end
|
30
app/policies/synapse_policy.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
class SynapsePolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
visible = ['public', 'commons']
|
||||
permission = 'synapses.permission IN (?)'
|
||||
if user
|
||||
scope.where(permission + ' OR synapses.user_id = ?', visible, user.id)
|
||||
else
|
||||
scope.where(permission, visible)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create?
|
||||
user.present?
|
||||
# todo add validation against whether you can see both topics
|
||||
end
|
||||
|
||||
def show?
|
||||
record.permission == 'commons' || record.permission == 'public' || record.user == user
|
||||
end
|
||||
|
||||
def update?
|
||||
user.present? && (record.permission == 'commons' || record.user == user)
|
||||
end
|
||||
|
||||
def destroy?
|
||||
record.user == user || admin_override
|
||||
end
|
||||
end
|
24
app/policies/token_policy.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class TokenPolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
if user
|
||||
scope.where('tokens.user_id = ?', user.id)
|
||||
else
|
||||
where(:id => nil).where("id IS NOT ?", nil) # to just return none
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def my_tokens?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user.present? && record.user == user
|
||||
end
|
||||
|
||||
end
|
45
app/policies/topic_policy.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
class TopicPolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
visible = ['public', 'commons']
|
||||
permission = 'topics.permission IN (?)'
|
||||
if user
|
||||
scope.where(permission + ' OR topics.user_id = ?', visible, user.id)
|
||||
else
|
||||
scope.where(permission, visible)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def show?
|
||||
record.permission == 'commons' || record.permission == 'public' || record.user == user
|
||||
end
|
||||
|
||||
def update?
|
||||
user.present? && (record.permission == 'commons' || record.user == user)
|
||||
end
|
||||
|
||||
def destroy?
|
||||
record.user == user || admin_override
|
||||
end
|
||||
|
||||
def autocomplete_topic?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def network?
|
||||
show?
|
||||
end
|
||||
|
||||
def relative_numbers?
|
||||
show?
|
||||
end
|
||||
|
||||
def relatives?
|
||||
show?
|
||||
end
|
||||
end
|
15
app/serializers/event_serializer.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class EventSerializer < ActiveModel::Serializer
|
||||
embed :ids, include: true
|
||||
attributes :id, :sequence_id, :kind, :map_id, :created_at
|
||||
|
||||
has_one :actor, serializer: NewUserSerializer, root: 'users'
|
||||
has_one :map, serializer: NewMapSerializer
|
||||
|
||||
def actor
|
||||
object.user || object.eventable.try(:user)
|
||||
end
|
||||
|
||||
def map
|
||||
object.eventable.try(:map) || object.eventable.map
|
||||
end
|
||||
end
|
16
app/serializers/new_map_serializer.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class NewMapSerializer < ActiveModel::Serializer
|
||||
embed :ids, include: true
|
||||
attributes :id,
|
||||
:name,
|
||||
:desc,
|
||||
:permission,
|
||||
:screenshot,
|
||||
:created_at,
|
||||
:updated_at
|
||||
|
||||
has_many :topics, serializer: NewTopicSerializer
|
||||
has_many :synapses, serializer: NewSynapseSerializer
|
||||
has_many :mappings, serializer: NewMappingSerializer
|
||||
has_many :contributors, root: :users, serializer: NewUserSerializer
|
||||
|
||||
end
|
19
app/serializers/new_mapping_serializer.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class NewMappingSerializer < ActiveModel::Serializer
|
||||
embed :ids, include: true
|
||||
attributes :id,
|
||||
:xloc,
|
||||
:yloc,
|
||||
:created_at,
|
||||
:updated_at,
|
||||
:mappable_id,
|
||||
:mappable_type
|
||||
|
||||
has_one :user, serializer: NewUserSerializer
|
||||
has_one :map, serializer: NewMapSerializer
|
||||
|
||||
def filter(keys)
|
||||
keys.delete(:xloc) unless object.mappable_type == "Topic"
|
||||
keys.delete(:yloc) unless object.mappable_type == "Topic"
|
||||
keys
|
||||
end
|
||||
end
|
7
app/serializers/new_metacode_serializer.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class NewMetacodeSerializer < ActiveModel::Serializer
|
||||
attributes :id,
|
||||
:name,
|
||||
:manual_icon,
|
||||
:color,
|
||||
:aws_icon
|
||||
end
|
15
app/serializers/new_synapse_serializer.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class NewSynapseSerializer < ActiveModel::Serializer
|
||||
embed :ids, include: true
|
||||
attributes :id,
|
||||
:desc,
|
||||
:category,
|
||||
:weight,
|
||||
:permission,
|
||||
:created_at,
|
||||
:updated_at
|
||||
|
||||
has_one :topic1, root: :topics, serializer: NewTopicSerializer
|
||||
has_one :topic2, root: :topics, serializer: NewTopicSerializer
|
||||
has_one :user, serializer: NewUserSerializer
|
||||
|
||||
end
|
14
app/serializers/new_topic_serializer.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class NewTopicSerializer < ActiveModel::Serializer
|
||||
embed :ids, include: true
|
||||
attributes :id,
|
||||
:name,
|
||||
:desc,
|
||||
:link,
|
||||
:permission,
|
||||
:created_at,
|
||||
:updated_at
|
||||
|
||||
has_one :user, serializer: NewUserSerializer
|
||||
has_one :metacode, serializer: NewMetacodeSerializer
|
||||
|
||||
end
|
15
app/serializers/new_user_serializer.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class NewUserSerializer < ActiveModel::Serializer
|
||||
attributes :id,
|
||||
:name,
|
||||
:avatar,
|
||||
:is_admin,
|
||||
:generation
|
||||
|
||||
def avatar
|
||||
object.image.url(:sixtyfour)
|
||||
end
|
||||
|
||||
def is_admin
|
||||
object.admin
|
||||
end
|
||||
end
|
8
app/serializers/token_serializer.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class TokenSerializer < ActiveModel::Serializer
|
||||
attributes :id,
|
||||
:token,
|
||||
:description,
|
||||
:created_at,
|
||||
:updated_at
|
||||
|
||||
end
|
3
app/serializers/webhook_serializer.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class WebhookSerializer < ActiveModel::Serializer
|
||||
attributes :text, :username, :icon_url #, :attachments
|
||||
end
|
84
app/services/map_export_service.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
class MapExportService < Struct.new(:user, :map)
|
||||
def json
|
||||
# marshal_dump turns OpenStruct into a Hash
|
||||
{
|
||||
topics: exportable_topics.map(&:marshal_dump),
|
||||
synapses: exportable_synapses.map(&:marshal_dump)
|
||||
}
|
||||
end
|
||||
|
||||
def csv(options = {})
|
||||
CSV.generate(options) do |csv|
|
||||
to_spreadsheet.each do |line|
|
||||
csv << line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def xls
|
||||
to_spreadsheet
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def topic_headings
|
||||
[:id, :name, :metacode, :x, :y, :description, :link, :user, :permission]
|
||||
end
|
||||
def synapse_headings
|
||||
[:topic1, :topic2, :category, :description, :user, :permission]
|
||||
end
|
||||
|
||||
def exportable_topics
|
||||
visible_topics ||= Pundit.policy_scope!(user, map.topics)
|
||||
topic_mappings = Mapping.includes(mappable: [:metacode, :user])
|
||||
.where(mappable: visible_topics, map: map)
|
||||
topic_mappings.map do |mapping|
|
||||
topic = mapping.mappable
|
||||
OpenStruct.new(
|
||||
id: topic.id,
|
||||
name: topic.name,
|
||||
metacode: topic.metacode.name,
|
||||
x: mapping.xloc,
|
||||
y: mapping.yloc,
|
||||
description: topic.desc,
|
||||
link: topic.link,
|
||||
user: topic.user.name,
|
||||
permission: topic.permission
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def exportable_synapses
|
||||
visible_synapses = Pundit.policy_scope!(user, map.synapses)
|
||||
visible_synapses.map do |synapse|
|
||||
OpenStruct.new(
|
||||
topic1: synapse.node1_id,
|
||||
topic2: synapse.node2_id,
|
||||
category: synapse.category,
|
||||
description: synapse.desc,
|
||||
user: synapse.user.name,
|
||||
permission: synapse.permission
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def to_spreadsheet
|
||||
spreadsheet = []
|
||||
spreadsheet << ["Topics"]
|
||||
spreadsheet << topic_headings.map(&:capitalize)
|
||||
exportable_topics.each do |topics|
|
||||
# convert exportable_topics into an array of arrays
|
||||
spreadsheet << topic_headings.map { |h| topics.send(h) }
|
||||
end
|
||||
|
||||
spreadsheet << []
|
||||
spreadsheet << ["Synapses"]
|
||||
spreadsheet << synapse_headings.map(&:capitalize)
|
||||
exportable_synapses.each do |synapse|
|
||||
# convert exportable_synapses into an array of arrays
|
||||
spreadsheet << synapse_headings.map { |h| synapse.send(h) }
|
||||
end
|
||||
|
||||
spreadsheet
|
||||
end
|
||||
end
|
18
app/services/webhook_service.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class WebhookService
|
||||
|
||||
def self.publish!(webhook:, event:)
|
||||
return false unless webhook.event_types.include? event.kind
|
||||
HTTParty.post webhook.uri, body: payload_for(webhook, event), headers: webhook.headers
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.payload_for(webhook, event)
|
||||
WebhookSerializer.new(webhook_object_for(webhook, event), root: false).to_json
|
||||
end
|
||||
|
||||
def self.webhook_object_for(webhook, event)
|
||||
"Webhooks::#{webhook.kind.classify}::#{event.kind.classify}".constantize.new(event)
|
||||
end
|
||||
|
||||
end
|
5
app/views/doorkeeper/applications/_delete_form.html.erb
Normal file
|
@ -0,0 +1,5 @@
|
|||
<%- submit_btn_css ||= 'button red-button' %>
|
||||
<%= form_tag oauth_application_path(application) do %>
|
||||
<input type="hidden" name="_method" value="delete">
|
||||
<%= submit_tag t('doorkeeper.applications.buttons.destroy'), onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')", class: submit_btn_css %>
|
||||
<% end %>
|
36
app/views/doorkeeper/applications/_form.html.erb
Normal file
|
@ -0,0 +1,36 @@
|
|||
<%= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f| %>
|
||||
<% if application.errors.any? %>
|
||||
<div class="alert alert-danger" data-alert><p><%= t('doorkeeper.applications.form.error') %></p></div>
|
||||
<% end %>
|
||||
|
||||
<%= content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do %>
|
||||
<%= f.label :name, class: 'col-sm-2 control-label' %>
|
||||
<div class="col-sm-10">
|
||||
<%= f.text_field :name, class: 'form-control' %>
|
||||
<%= doorkeeper_errors_for application, :name %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do %>
|
||||
<%= f.label :redirect_uri, class: 'col-sm-2 control-label' %>
|
||||
<div class="col-sm-10">
|
||||
<%= f.text_area :redirect_uri, class: 'form-control' %>
|
||||
<%= doorkeeper_errors_for application, :redirect_uri %>
|
||||
<span class="help-block">
|
||||
<%= t('doorkeeper.applications.help.redirect_uri') %>.
|
||||
</span>
|
||||
<% if Doorkeeper.configuration.native_redirect_uri %>
|
||||
<span class="help-block">
|
||||
<%= raw t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: "<code>#{ Doorkeeper.configuration.native_redirect_uri }</code>") %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<%= f.submit t('doorkeeper.applications.buttons.submit'), class: "btn btn-primary" %>
|
||||
<%= link_to t('doorkeeper.applications.buttons.cancel'), oauth_applications_path, :class => "button link-button red-button", :data => { :bypass => 'true' } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
10
app/views/doorkeeper/applications/_script.html.erb
Normal file
|
@ -0,0 +1,10 @@
|
|||
<script type="text/javascript">
|
||||
Metamaps.Apps = {
|
||||
init: function () {
|
||||
Metamaps.Famous.explore.setApps('registered');
|
||||
Metamaps.Famous.explore.show();
|
||||
Metamaps.GlobalUI.Search.open();
|
||||
Metamaps.GlobalUI.Search.lock();
|
||||
}
|
||||
};
|
||||
</script>
|
10
app/views/doorkeeper/applications/edit.html.erb
Normal file
|
@ -0,0 +1,10 @@
|
|||
<div id="yield">
|
||||
<div class="centerContent">
|
||||
<div class="page-header">
|
||||
<h2><%= t('.title') %></h2>
|
||||
</div>
|
||||
|
||||
<%= render 'form', application: @application %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render 'script' %>
|