merge develop
1
.babelrc
|
@ -4,6 +4,7 @@
|
||||||
"es2015"
|
"es2015"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"lodash",
|
||||||
"transform-class-properties"
|
"transform-class-properties"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
38
.codeclimate.yml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
engines:
|
||||||
|
brakeman:
|
||||||
|
enabled: true
|
||||||
|
bundler-audit:
|
||||||
|
enabled: true
|
||||||
|
duplication:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
languages:
|
||||||
|
count_threshold: 3 # rule of three
|
||||||
|
ruby:
|
||||||
|
mass_threshold: 36 # default: 18
|
||||||
|
javascript:
|
||||||
|
mass_threshold: 80 # default: 40
|
||||||
|
eslint:
|
||||||
|
enabled: true
|
||||||
|
channel: "eslint-3"
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
rubocop:
|
||||||
|
enabled: true
|
||||||
|
exclude_fingerprints:
|
||||||
|
- 74f18007b920e8d81148d2f6a2756534
|
||||||
|
ratings:
|
||||||
|
paths:
|
||||||
|
- 'Gemfile.lock'
|
||||||
|
- '**.erb'
|
||||||
|
- '**.rb'
|
||||||
|
- '**.js'
|
||||||
|
- '**.jsx'
|
||||||
|
exclude_paths:
|
||||||
|
- app/assets/images/
|
||||||
|
- app/assets/javascripts/lib/
|
||||||
|
- frontend/src/patched/
|
||||||
|
- db/
|
||||||
|
- script/
|
||||||
|
- spec/
|
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
**/*{.,-}min.js
|
||||||
|
frontend/src/patched/*
|
||||||
|
app/assets/javascripts/lib/*
|
26
.eslintrc.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module.exports = {
|
||||||
|
"sourceType": "module",
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extends": "standard",
|
||||||
|
"installedESLint": true,
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"promise",
|
||||||
|
"standard",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-uses-react": [2],
|
||||||
|
"react/jsx-uses-vars": [2],
|
||||||
|
"space-before-function-paren": [2, "never"],
|
||||||
|
"yoda": [2, "never", { "exceptRange": true }]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
|
# Node JS env
|
||||||
|
export NODE_REALTIME_PORT='5000' # should match REALTIME_SERVER, below
|
||||||
|
|
||||||
|
# Rails env
|
||||||
export DB_USERNAME='postgres'
|
export DB_USERNAME='postgres'
|
||||||
export DB_PASSWORD='3112'
|
export DB_PASSWORD='3112'
|
||||||
export DB_HOST='localhost'
|
export DB_HOST='localhost'
|
||||||
export DB_PORT='5432'
|
export DB_PORT='5432'
|
||||||
export DB_NAME='metamap002'
|
export DB_NAME='metamap002'
|
||||||
|
|
||||||
export REALTIME_SERVER='http://localhost:5001'
|
export REALTIME_SERVER='http://localhost:5000'
|
||||||
export MAILER_DEFAULT_URL='localhost:3000'
|
export MAILER_DEFAULT_URL='localhost:3000'
|
||||||
export DEVISE_MAILER_SENDER='team@metamaps.cc'
|
export DEVISE_MAILER_SENDER='team@metamaps.cc'
|
||||||
|
|
||||||
|
@ -14,10 +18,10 @@ export SECRET_KEY_BASE='267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c800
|
||||||
# # you can safely leave these blank, unless you're deploying an instance, in
|
# # you can safely leave these blank, unless you're deploying an instance, in
|
||||||
# # which case you'll need to set them up
|
# # which case you'll need to set them up
|
||||||
#
|
#
|
||||||
|
# export S3_REGION
|
||||||
# export S3_BUCKET_NAME
|
# export S3_BUCKET_NAME
|
||||||
# export AWS_ACCESS_KEY_ID
|
# export AWS_ACCESS_KEY_ID
|
||||||
# export AWS_SECRET_ACCESS_KEY
|
# export AWS_SECRET_ACCESS_KEY
|
||||||
# export SSO_KEY
|
|
||||||
#
|
#
|
||||||
# export SMTP_DOMAIN
|
# export SMTP_DOMAIN
|
||||||
# export SMTP_PASSWORD
|
# export SMTP_PASSWORD
|
||||||
|
|
5
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============
|
||||||
|
100BD/C = (100)(__)(__)/(__)=__
|
1
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
||||||
#assety stuff
|
#assety stuff
|
||||||
public/assets
|
public/assets
|
||||||
public/metamaps_mobile
|
public/metamaps_mobile
|
||||||
|
public/api/index.html
|
||||||
vendor/
|
vendor/
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
|
@ -6,9 +6,16 @@ AllCops:
|
||||||
- 'bin/**/*'
|
- 'bin/**/*'
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
- 'app/assets/javascripts/node_modules/**/*'
|
- 'app/assets/javascripts/node_modules/**/*'
|
||||||
|
- 'Vagrantfile'
|
||||||
|
|
||||||
Rails:
|
Rails:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
Metrics/LineLength:
|
Metrics/LineLength:
|
||||||
Max: 100
|
Max: 100
|
||||||
|
|
||||||
|
Metrics/AbcSize:
|
||||||
|
Max: 16
|
||||||
|
|
||||||
|
Style/Documentation:
|
||||||
|
Enabled: false
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
if ENV['COVERAGE'] == 'on'
|
if ENV['COVERAGE'] == 'on'
|
||||||
SimpleCov.start 'rails'
|
SimpleCov.start 'rails' do
|
||||||
|
add_group 'Policies', 'app/policies'
|
||||||
|
add_group 'Services', 'app/services'
|
||||||
|
add_group 'Serializers', 'app/serializers'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,10 @@ before_script:
|
||||||
- . $HOME/.nvm/nvm.sh
|
- . $HOME/.nvm/nvm.sh
|
||||||
- nvm install stable
|
- nvm install stable
|
||||||
- nvm use stable
|
- nvm use stable
|
||||||
- npm install
|
- npm install --no-optional
|
||||||
script:
|
script:
|
||||||
- bundle exec rspec && npm test && bundle exec brakeman -q -z
|
- bundle exec rspec && bundle exec brakeman -q -z && npm test
|
||||||
|
addons:
|
||||||
|
code_climate:
|
||||||
|
repo_token: 479d3bf56798fbc7fff3fc8151a5ed09e8ac368fd5af332c437b9e07dbebb44e
|
||||||
|
postgresql: "9.4"
|
||||||
|
|
35
Gemfile
|
@ -1,47 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '2.3.0'
|
ruby '2.3.0'
|
||||||
|
|
||||||
gem 'rails'
|
gem 'rails', '~> 5.0.0'
|
||||||
|
|
||||||
gem 'active_model_serializers', '~> 0.8.1'
|
gem 'active_model_serializers'
|
||||||
gem 'aws-sdk', '< 2.0'
|
gem 'aws-sdk'
|
||||||
gem 'best_in_place' # in-place editing
|
gem 'best_in_place'
|
||||||
gem 'delayed_job', '~> 4.0.2'
|
gem 'delayed_job'
|
||||||
gem 'delayed_job_active_record', '~> 4.0.1'
|
gem 'delayed_job_active_record'
|
||||||
|
gem 'sucker_punch'
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'doorkeeper'
|
gem 'doorkeeper'
|
||||||
gem 'dotenv-rails'
|
gem 'dotenv-rails'
|
||||||
gem 'exception_notification'
|
gem 'exception_notification'
|
||||||
gem 'formtastic'
|
|
||||||
gem 'formula'
|
|
||||||
gem 'httparty'
|
gem 'httparty'
|
||||||
gem 'json'
|
gem 'json'
|
||||||
gem 'kaminari' # pagination
|
gem 'kaminari'
|
||||||
|
gem 'mailboxer'
|
||||||
gem 'paperclip'
|
gem 'paperclip'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
gem 'pundit_extra'
|
gem 'pundit_extra'
|
||||||
|
gem 'rack-attack'
|
||||||
gem 'rack-cors'
|
gem 'rack-cors'
|
||||||
gem 'rails3-jquery-autocomplete'
|
|
||||||
gem 'redis'
|
gem 'redis'
|
||||||
gem 'slack-notifier'
|
gem 'slack-notifier'
|
||||||
gem 'snorlax'
|
gem 'snorlax'
|
||||||
gem 'uservoice-ruby'
|
|
||||||
|
|
||||||
|
# asset stuff
|
||||||
gem 'jquery-rails'
|
gem 'jquery-rails'
|
||||||
gem 'jquery-ui-rails'
|
gem 'jquery-ui-rails'
|
||||||
gem 'jbuilder'
|
|
||||||
|
|
||||||
group :assets do
|
|
||||||
gem 'coffee-rails'
|
|
||||||
gem 'sass-rails'
|
gem 'sass-rails'
|
||||||
gem 'uglifier'
|
gem 'uglifier'
|
||||||
# gem 'therubyracer'
|
|
||||||
end
|
|
||||||
|
|
||||||
group :production do
|
|
||||||
gem 'rails_12factor'
|
|
||||||
end
|
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'factory_girl_rails'
|
gem 'factory_girl_rails'
|
||||||
|
@ -53,11 +44,11 @@ group :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
gem 'puma'
|
||||||
gem 'better_errors'
|
gem 'better_errors'
|
||||||
gem 'binding_of_caller'
|
gem 'binding_of_caller'
|
||||||
gem 'pry-byebug'
|
gem 'pry-byebug'
|
||||||
gem 'pry-rails'
|
gem 'pry-rails'
|
||||||
gem 'quiet_assets'
|
|
||||||
gem 'tunemygc'
|
gem 'tunemygc'
|
||||||
gem 'rubocop'
|
gem 'rubocop'
|
||||||
end
|
end
|
||||||
|
|
298
Gemfile.lock
|
@ -1,51 +1,57 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actionmailer (4.2.6)
|
actioncable (5.0.0.1)
|
||||||
actionpack (= 4.2.6)
|
actionpack (= 5.0.0.1)
|
||||||
actionview (= 4.2.6)
|
nio4r (~> 1.2)
|
||||||
activejob (= 4.2.6)
|
websocket-driver (~> 0.6.1)
|
||||||
|
actionmailer (5.0.0.1)
|
||||||
|
actionpack (= 5.0.0.1)
|
||||||
|
actionview (= 5.0.0.1)
|
||||||
|
activejob (= 5.0.0.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (4.2.6)
|
actionpack (5.0.0.1)
|
||||||
actionview (= 4.2.6)
|
actionview (= 5.0.0.1)
|
||||||
activesupport (= 4.2.6)
|
activesupport (= 5.0.0.1)
|
||||||
rack (~> 1.6)
|
rack (~> 2.0)
|
||||||
rack-test (~> 0.6.2)
|
rack-test (~> 0.6.3)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (4.2.6)
|
actionview (5.0.0.1)
|
||||||
activesupport (= 4.2.6)
|
activesupport (= 5.0.0.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubis (~> 2.7.0)
|
erubis (~> 2.7.0)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
active_model_serializers (0.8.3)
|
active_model_serializers (0.10.2)
|
||||||
activemodel (>= 3.0)
|
actionpack (>= 4.1, < 6)
|
||||||
activejob (4.2.6)
|
activemodel (>= 4.1, < 6)
|
||||||
activesupport (= 4.2.6)
|
jsonapi (~> 0.1.1.beta2)
|
||||||
globalid (>= 0.3.0)
|
railties (>= 4.1, < 6)
|
||||||
activemodel (4.2.6)
|
activejob (5.0.0.1)
|
||||||
activesupport (= 4.2.6)
|
activesupport (= 5.0.0.1)
|
||||||
builder (~> 3.1)
|
globalid (>= 0.3.6)
|
||||||
activerecord (4.2.6)
|
activemodel (5.0.0.1)
|
||||||
activemodel (= 4.2.6)
|
activesupport (= 5.0.0.1)
|
||||||
activesupport (= 4.2.6)
|
activerecord (5.0.0.1)
|
||||||
arel (~> 6.0)
|
activemodel (= 5.0.0.1)
|
||||||
activesupport (4.2.6)
|
activesupport (= 5.0.0.1)
|
||||||
|
arel (~> 7.0)
|
||||||
|
activesupport (5.0.0.1)
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
json (~> 1.7, >= 1.7.7)
|
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
thread_safe (~> 0.3, >= 0.3.4)
|
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.3.8)
|
addressable (2.3.8)
|
||||||
arel (6.0.3)
|
arel (7.1.2)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
aws-sdk (1.66.0)
|
aws-sdk (2.6.3)
|
||||||
aws-sdk-v1 (= 1.66.0)
|
aws-sdk-resources (= 2.6.3)
|
||||||
aws-sdk-v1 (1.66.0)
|
aws-sdk-core (2.6.3)
|
||||||
json (~> 1.4)
|
jmespath (~> 1.0)
|
||||||
nokogiri (>= 1.4.4)
|
aws-sdk-resources (2.6.3)
|
||||||
|
aws-sdk-core (= 2.6.3)
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
best_in_place (3.1.0)
|
best_in_place (3.1.0)
|
||||||
actionpack (>= 3.2)
|
actionpack (>= 3.2)
|
||||||
|
@ -56,29 +62,28 @@ GEM
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
brakeman (3.3.2)
|
brakeman (3.4.0)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
byebug (9.0.5)
|
byebug (9.0.5)
|
||||||
|
carrierwave (0.11.2)
|
||||||
|
activemodel (>= 3.2.0)
|
||||||
|
activesupport (>= 3.2.0)
|
||||||
|
json (>= 1.7)
|
||||||
|
mime-types (>= 1.16)
|
||||||
|
mimemagic (>= 0.3.0)
|
||||||
climate_control (0.0.3)
|
climate_control (0.0.3)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.1)
|
coderay (1.1.1)
|
||||||
coffee-rails (4.1.1)
|
|
||||||
coffee-script (>= 2.2.0)
|
|
||||||
railties (>= 4.0.0, < 5.1.x)
|
|
||||||
coffee-script (2.4.1)
|
|
||||||
coffee-script-source
|
|
||||||
execjs
|
|
||||||
coffee-script-source (1.10.0)
|
|
||||||
concurrent-ruby (1.0.2)
|
concurrent-ruby (1.0.2)
|
||||||
debug_inspector (0.0.2)
|
debug_inspector (0.0.2)
|
||||||
delayed_job (4.0.6)
|
delayed_job (4.1.2)
|
||||||
activesupport (>= 3.0, < 5.0)
|
activesupport (>= 3.0, < 5.1)
|
||||||
delayed_job_active_record (4.0.3)
|
delayed_job_active_record (4.1.1)
|
||||||
activerecord (>= 3.0, < 5.0)
|
activerecord (>= 3.0, < 5.1)
|
||||||
delayed_job (>= 3.0, < 4.1)
|
delayed_job (>= 3.0, < 5)
|
||||||
devise (4.1.1)
|
devise (4.2.0)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0, < 5.1)
|
railties (>= 4.1.0, < 5.1)
|
||||||
|
@ -86,37 +91,29 @@ GEM
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
diff-lcs (1.2.5)
|
diff-lcs (1.2.5)
|
||||||
docile (1.1.5)
|
docile (1.1.5)
|
||||||
doorkeeper (3.1.0)
|
doorkeeper (4.2.0)
|
||||||
railties (>= 3.2)
|
railties (>= 4.2)
|
||||||
dotenv (2.1.1)
|
dotenv (2.1.1)
|
||||||
dotenv-rails (2.1.1)
|
dotenv-rails (2.1.1)
|
||||||
dotenv (= 2.1.1)
|
dotenv (= 2.1.1)
|
||||||
railties (>= 4.0, < 5.1)
|
railties (>= 4.0, < 5.1)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
exception_notification (4.1.4)
|
exception_notification (4.2.1)
|
||||||
actionmailer (~> 4.0)
|
actionmailer (>= 4.0, < 6)
|
||||||
activesupport (~> 4.0)
|
activesupport (>= 4.0, < 6)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
ezcrypto (0.7.2)
|
|
||||||
factory_girl (4.7.0)
|
factory_girl (4.7.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
factory_girl_rails (4.7.0)
|
factory_girl_rails (4.7.0)
|
||||||
factory_girl (~> 4.7.0)
|
factory_girl (~> 4.7.0)
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
formtastic (3.1.4)
|
globalid (0.3.7)
|
||||||
actionpack (>= 3.2.13)
|
|
||||||
formula (1.1.1)
|
|
||||||
rails (> 3.0.0)
|
|
||||||
globalid (0.3.6)
|
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
httparty (0.13.7)
|
httparty (0.14.0)
|
||||||
json (~> 1.8)
|
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
jbuilder (2.5.0)
|
jmespath (1.3.1)
|
||||||
activesupport (>= 3.0.0, < 5.1)
|
jquery-rails (4.2.1)
|
||||||
multi_json (~> 1.2)
|
|
||||||
jquery-rails (4.1.1)
|
|
||||||
rails-dom-testing (>= 1, < 3)
|
rails-dom-testing (>= 1, < 3)
|
||||||
railties (>= 4.2.0)
|
railties (>= 4.2.0)
|
||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
|
@ -125,6 +122,8 @@ GEM
|
||||||
json (1.8.3)
|
json (1.8.3)
|
||||||
json-schema (2.6.2)
|
json-schema (2.6.2)
|
||||||
addressable (~> 2.3.8)
|
addressable (~> 2.3.8)
|
||||||
|
jsonapi (0.1.1.beta2)
|
||||||
|
json (~> 1.8)
|
||||||
kaminari (0.17.0)
|
kaminari (0.17.0)
|
||||||
actionpack (>= 3.0.0)
|
actionpack (>= 3.0.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -132,32 +131,34 @@ GEM
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.4)
|
mail (2.6.4)
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
|
mailboxer (0.14.0)
|
||||||
|
carrierwave (>= 0.5.8)
|
||||||
|
rails (>= 4.2.0)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mimemagic (0.3.0)
|
mimemagic (0.3.2)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.1.0)
|
||||||
minitest (5.9.0)
|
minitest (5.9.1)
|
||||||
multi_json (1.12.1)
|
|
||||||
multi_xml (0.5.5)
|
multi_xml (0.5.5)
|
||||||
|
nio4r (1.2.1)
|
||||||
nokogiri (1.6.8)
|
nokogiri (1.6.8)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.1.0)
|
||||||
pkg-config (~> 1.1.7)
|
pkg-config (~> 1.1.7)
|
||||||
oauth (0.5.1)
|
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
paperclip (4.3.6)
|
paperclip (5.1.0)
|
||||||
activemodel (>= 3.2.0)
|
activemodel (>= 4.2.0)
|
||||||
activesupport (>= 3.2.0)
|
activesupport (>= 4.2.0)
|
||||||
cocaine (~> 0.5.5)
|
cocaine (~> 0.5.5)
|
||||||
mime-types
|
mime-types
|
||||||
mimemagic (= 0.3.0)
|
mimemagic (~> 0.3.0)
|
||||||
parser (2.3.1.2)
|
parser (2.3.1.4)
|
||||||
ast (~> 2.2)
|
ast (~> 2.2)
|
||||||
pg (0.18.4)
|
pg (0.19.0)
|
||||||
pkg-config (1.1.7)
|
pkg-config (1.1.7)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pry (0.10.3)
|
pry (0.10.4)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.8.1)
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
|
@ -166,69 +167,62 @@ GEM
|
||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
pry-rails (0.3.4)
|
pry-rails (0.3.4)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.10)
|
||||||
|
puma (3.6.2)
|
||||||
pundit (1.1.0)
|
pundit (1.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
pundit_extra (0.2.0)
|
pundit_extra (0.3.0)
|
||||||
quiet_assets (1.1.0)
|
rack (2.0.1)
|
||||||
railties (>= 3.1, < 5.0)
|
rack-attack (5.0.1)
|
||||||
rack (1.6.4)
|
rack
|
||||||
rack-cors (0.4.0)
|
rack-cors (0.4.0)
|
||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (4.2.6)
|
rails (5.0.0.1)
|
||||||
actionmailer (= 4.2.6)
|
actioncable (= 5.0.0.1)
|
||||||
actionpack (= 4.2.6)
|
actionmailer (= 5.0.0.1)
|
||||||
actionview (= 4.2.6)
|
actionpack (= 5.0.0.1)
|
||||||
activejob (= 4.2.6)
|
actionview (= 5.0.0.1)
|
||||||
activemodel (= 4.2.6)
|
activejob (= 5.0.0.1)
|
||||||
activerecord (= 4.2.6)
|
activemodel (= 5.0.0.1)
|
||||||
activesupport (= 4.2.6)
|
activerecord (= 5.0.0.1)
|
||||||
|
activesupport (= 5.0.0.1)
|
||||||
bundler (>= 1.3.0, < 2.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 4.2.6)
|
railties (= 5.0.0.1)
|
||||||
sprockets-rails
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-deprecated_sanitizer (1.0.3)
|
rails-dom-testing (2.0.1)
|
||||||
activesupport (>= 4.2.0.alpha)
|
activesupport (>= 4.2.0, < 6.0)
|
||||||
rails-dom-testing (1.0.7)
|
|
||||||
activesupport (>= 4.2.0.beta, < 5.0)
|
|
||||||
nokogiri (~> 1.6.0)
|
nokogiri (~> 1.6.0)
|
||||||
rails-deprecated_sanitizer (>= 1.0.1)
|
|
||||||
rails-html-sanitizer (1.0.3)
|
rails-html-sanitizer (1.0.3)
|
||||||
loofah (~> 2.0)
|
loofah (~> 2.0)
|
||||||
rails3-jquery-autocomplete (1.0.15)
|
railties (5.0.0.1)
|
||||||
rails (>= 3.2)
|
actionpack (= 5.0.0.1)
|
||||||
rails_12factor (0.0.3)
|
activesupport (= 5.0.0.1)
|
||||||
rails_serve_static_assets
|
method_source
|
||||||
rails_stdout_logging
|
|
||||||
rails_serve_static_assets (0.0.5)
|
|
||||||
rails_stdout_logging (0.0.5)
|
|
||||||
railties (4.2.6)
|
|
||||||
actionpack (= 4.2.6)
|
|
||||||
activesupport (= 4.2.6)
|
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.1.0)
|
rainbow (2.1.0)
|
||||||
rake (11.2.2)
|
rake (11.3.0)
|
||||||
redis (3.3.0)
|
redis (3.3.1)
|
||||||
responders (2.2.0)
|
responders (2.3.0)
|
||||||
railties (>= 4.2.0, < 5.1)
|
railties (>= 4.2.0, < 5.1)
|
||||||
rspec-core (3.4.4)
|
rspec-core (3.5.3)
|
||||||
rspec-support (~> 3.4.0)
|
rspec-support (~> 3.5.0)
|
||||||
rspec-expectations (3.4.0)
|
rspec-expectations (3.5.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.4.0)
|
rspec-support (~> 3.5.0)
|
||||||
rspec-mocks (3.4.1)
|
rspec-mocks (3.5.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.4.0)
|
rspec-support (~> 3.5.0)
|
||||||
rspec-rails (3.4.2)
|
rspec-rails (3.5.2)
|
||||||
actionpack (>= 3.0, < 4.3)
|
actionpack (>= 3.0)
|
||||||
activesupport (>= 3.0, < 4.3)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0, < 4.3)
|
railties (>= 3.0)
|
||||||
rspec-core (~> 3.4.0)
|
rspec-core (~> 3.5.0)
|
||||||
rspec-expectations (~> 3.4.0)
|
rspec-expectations (~> 3.5.0)
|
||||||
rspec-mocks (~> 3.4.0)
|
rspec-mocks (~> 3.5.0)
|
||||||
rspec-support (~> 3.4.0)
|
rspec-support (~> 3.5.0)
|
||||||
rspec-support (3.4.1)
|
rspec-support (3.5.0)
|
||||||
rubocop (0.41.1)
|
rubocop (0.43.0)
|
||||||
parser (>= 2.3.1.1, < 3.0)
|
parser (>= 2.3.1.1, < 3.0)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
rainbow (>= 1.99.1, < 3.0)
|
rainbow (>= 1.99.1, < 3.0)
|
||||||
|
@ -236,84 +230,81 @@ GEM
|
||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||||
ruby-progressbar (1.8.1)
|
ruby-progressbar (1.8.1)
|
||||||
sass (3.4.22)
|
sass (3.4.22)
|
||||||
sass-rails (5.0.4)
|
sass-rails (5.0.6)
|
||||||
railties (>= 4.0.0, < 5.0)
|
railties (>= 4.0.0, < 6)
|
||||||
sass (~> 3.1)
|
sass (~> 3.1)
|
||||||
sprockets (>= 2.8, < 4.0)
|
sprockets (>= 2.8, < 4.0)
|
||||||
sprockets-rails (>= 2.0, < 4.0)
|
sprockets-rails (>= 2.0, < 4.0)
|
||||||
tilt (>= 1.1, < 3)
|
tilt (>= 1.1, < 3)
|
||||||
shoulda-matchers (3.1.1)
|
shoulda-matchers (3.1.1)
|
||||||
activesupport (>= 4.0.0)
|
activesupport (>= 4.0.0)
|
||||||
simplecov (0.11.2)
|
simplecov (0.12.0)
|
||||||
docile (~> 1.1.0)
|
docile (~> 1.1.0)
|
||||||
json (~> 1.8)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.0)
|
simplecov-html (0.10.0)
|
||||||
slack-notifier (1.5.1)
|
slack-notifier (1.5.1)
|
||||||
slop (3.6.0)
|
slop (3.6.0)
|
||||||
snorlax (0.1.6)
|
snorlax (0.1.6)
|
||||||
rails (> 4.1)
|
rails (> 4.1)
|
||||||
sprockets (3.6.0)
|
sprockets (3.7.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
sprockets-rails (3.0.4)
|
sprockets-rails (3.2.0)
|
||||||
actionpack (>= 4.0)
|
actionpack (>= 4.0)
|
||||||
activesupport (>= 4.0)
|
activesupport (>= 4.0)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
|
sucker_punch (2.0.2)
|
||||||
|
concurrent-ruby (~> 1.0.0)
|
||||||
thor (0.19.1)
|
thor (0.19.1)
|
||||||
thread_safe (0.3.5)
|
thread_safe (0.3.5)
|
||||||
tilt (2.0.5)
|
tilt (2.0.5)
|
||||||
tunemygc (1.0.65)
|
tunemygc (1.0.68)
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uglifier (3.0.0)
|
uglifier (3.0.2)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unicode-display_width (1.1.0)
|
unicode-display_width (1.1.1)
|
||||||
uservoice-ruby (0.0.11)
|
|
||||||
ezcrypto (>= 0.7.2)
|
|
||||||
json (>= 1.7.5)
|
|
||||||
oauth (>= 0.4.7)
|
|
||||||
warden (1.2.6)
|
warden (1.2.6)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
|
websocket-driver (0.6.4)
|
||||||
|
websocket-extensions (>= 0.1.0)
|
||||||
|
websocket-extensions (0.1.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.8.1)
|
active_model_serializers
|
||||||
aws-sdk (< 2.0)
|
aws-sdk
|
||||||
best_in_place
|
best_in_place
|
||||||
better_errors
|
better_errors
|
||||||
binding_of_caller
|
binding_of_caller
|
||||||
brakeman
|
brakeman
|
||||||
coffee-rails
|
delayed_job
|
||||||
delayed_job (~> 4.0.2)
|
delayed_job_active_record
|
||||||
delayed_job_active_record (~> 4.0.1)
|
|
||||||
devise
|
devise
|
||||||
doorkeeper
|
doorkeeper
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
exception_notification
|
exception_notification
|
||||||
factory_girl_rails
|
factory_girl_rails
|
||||||
formtastic
|
|
||||||
formula
|
|
||||||
httparty
|
httparty
|
||||||
jbuilder
|
|
||||||
jquery-rails
|
jquery-rails
|
||||||
jquery-ui-rails
|
jquery-ui-rails
|
||||||
json
|
json
|
||||||
json-schema
|
json-schema
|
||||||
kaminari
|
kaminari
|
||||||
|
mailboxer
|
||||||
paperclip
|
paperclip
|
||||||
pg
|
pg
|
||||||
pry-byebug
|
pry-byebug
|
||||||
pry-rails
|
pry-rails
|
||||||
|
puma
|
||||||
pundit
|
pundit
|
||||||
pundit_extra
|
pundit_extra
|
||||||
quiet_assets
|
rack-attack
|
||||||
rack-cors
|
rack-cors
|
||||||
rails
|
rails (~> 5.0.0)
|
||||||
rails3-jquery-autocomplete
|
|
||||||
rails_12factor
|
|
||||||
redis
|
redis
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rubocop
|
rubocop
|
||||||
|
@ -322,9 +313,12 @@ DEPENDENCIES
|
||||||
simplecov
|
simplecov
|
||||||
slack-notifier
|
slack-notifier
|
||||||
snorlax
|
snorlax
|
||||||
|
sucker_punch
|
||||||
tunemygc
|
tunemygc
|
||||||
uglifier
|
uglifier
|
||||||
uservoice-ruby
|
|
||||||
|
RUBY VERSION
|
||||||
|
ruby 2.3.0p0
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.11.2
|
1.13.6
|
||||||
|
|
19
README.md
|
@ -5,15 +5,19 @@ Metamaps
|
||||||
|
|
||||||
## What is Metamaps?
|
## What is Metamaps?
|
||||||
|
|
||||||
Metamaps is a free and open source technology for changemakers, innovators, educators and students. It enables individuals and communities to build and visualize their shared knowledge and unlock their collective intelligence.
|
Metamaps is a free and open-source technology for changemakers, innovators, educators and students. It enables individuals and communities to build and visualize their shared knowledge and unlock their collective intelligence.
|
||||||
|
|
||||||
You can find a version of this software running at [metamaps.cc][site-beta], where the technology is being tested in a private beta.
|
You can find a version of this software running at [metamaps.cc][site-beta], where the technology is being tested in an open beta.
|
||||||
|
|
||||||
Metamaps is created and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch by using whichever of these channels you prefer:
|
Metamaps is developed and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch by using whichever of these channels you prefer:
|
||||||
|
|
||||||
## Community
|
## How do I learn more?
|
||||||
|
|
||||||
- To send us a personal message or request an invite to the open beta, get in touch with us at team@metamaps.cc or @metamapps on Twitter.
|
- Contact: [team@metamaps.cc](mailto:team@metamaps.cc) or [@metamapps](https://twitter.com/metamapps) on Twitter
|
||||||
|
- User Documentation: [docs.metamaps.cc](https://docs.metamaps.cc)
|
||||||
|
- User Community: [hylo.com/c/metamaps](https://www.hylo.com/c/metamaps)
|
||||||
|
- Development Roadmap: [github.com/metamaps/metamaps/milestones](https://github.com/metamaps/metamaps/milestones)
|
||||||
|
- To send us a personal message or request an invite to the open beta, get in touch with us via email, Twitter, or Hylo
|
||||||
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
||||||
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
||||||
|
|
||||||
|
@ -51,10 +55,6 @@ We haven't set up instructions for using Vagrant on Windows, but there are instr
|
||||||
|
|
||||||
- [For Windows][windows-installation]
|
- [For Windows][windows-installation]
|
||||||
|
|
||||||
## Contributing guidelines
|
|
||||||
|
|
||||||
Cloning this repository directly is primarily for those wishing to contribute to our codebase. Check out our [contributing instructions][contributing] to get involved.
|
|
||||||
|
|
||||||
## Licensing information
|
## Licensing information
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
|
||||||
|
@ -65,7 +65,6 @@ The license can be read [here][license].
|
||||||
|
|
||||||
Copyright (c) 2016 Connor Turland
|
Copyright (c) 2016 Connor Turland
|
||||||
|
|
||||||
[site-blog]: http://blog.metamaps.cc
|
|
||||||
[site-beta]: http://metamaps.cc
|
[site-beta]: http://metamaps.cc
|
||||||
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
||||||
[contributing]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md
|
[contributing]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md
|
||||||
|
|
1
Rakefile
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env rake
|
#!/usr/bin/env rake
|
||||||
|
# frozen_string_literal: true
|
||||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||||
|
|
||||||
|
|
2
Vagrantfile
vendored
|
@ -31,7 +31,7 @@ sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD '3112';"
|
||||||
|
|
||||||
SCRIPT
|
SCRIPT
|
||||||
|
|
||||||
VAGRANTFILE_API_VERSION = '2'.freeze
|
VAGRANTFILE_API_VERSION = '2'
|
||||||
|
|
||||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
config.vm.box = 'trusty64'
|
config.vm.box = 'trusty64'
|
||||||
|
|
6
app/assets/config/manifest.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// JS and CSS bundles
|
||||||
|
//= link_directory ../javascripts .js
|
||||||
|
//= link_directory ../stylesheets .css
|
||||||
|
|
||||||
|
// Other
|
||||||
|
//= link_tree ../images
|
BIN
app/assets/images/.DS_Store
vendored
BIN
app/assets/images/import-example.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
app/assets/images/junto.gif
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
app/assets/images/metadata.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
app/assets/images/metamaps-intro-poster.webp
Normal file
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
app/assets/images/user_sprite.png
Executable file → Normal file
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
app/assets/images/view-only.png
Normal file
After Width: | Height: | Size: 421 B |
18
app/assets/javascripts/Metamaps.ServerData.js.erb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/* global Metamaps */
|
||||||
|
|
||||||
|
/* erb variables from rails */
|
||||||
|
Metamaps.ServerData = Metamaps.ServerData || {}
|
||||||
|
Metamaps.ServerData['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>'
|
||||||
|
Metamaps.ServerData['user.png'] = '<%= asset_path('user.png') %>'
|
||||||
|
Metamaps.ServerData['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>'
|
||||||
|
Metamaps.ServerData['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
|
||||||
|
Metamaps.ServerData['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
||||||
|
Metamaps.ServerData['synapse16.png'] = '<%= asset_path('synapse16.png') %>'
|
||||||
|
Metamaps.ServerData['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>'
|
||||||
|
Metamaps.ServerData['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>'
|
||||||
|
Metamaps.ServerData.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
||||||
|
Metamaps.ServerData.REALTIME_SERVER = '<%= ENV['REALTIME_SERVER'] %>'
|
||||||
|
Metamaps.ServerData.RAILS_ENV = '<%= ENV['RAILS_ENV'] %>'
|
||||||
|
Metamaps.ServerData.VERSION = '<%= METAMAPS_VERSION %>'
|
||||||
|
Metamaps.ServerData.BUILD = '<%= METAMAPS_BUILD %>'
|
||||||
|
Metamaps.ServerData.LAST_UPDATED = '<%= METAMAPS_LAST_UPDATED %>'
|
|
@ -1,3 +1,4 @@
|
||||||
|
// eslint-disable spaced-comment
|
||||||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||||
// listed below.
|
// listed below.
|
||||||
//
|
//
|
||||||
|
@ -13,35 +14,8 @@
|
||||||
//= require jquery
|
//= require jquery
|
||||||
//= require jquery-ui
|
//= require jquery-ui
|
||||||
//= require jquery_ujs
|
//= require jquery_ujs
|
||||||
//= require ./webpacked/metamaps.bundle
|
//= require action_cable
|
||||||
//= require_directory ./lib
|
//= require_directory ./lib
|
||||||
//= require ./src/Metamaps.GlobalUI
|
//= require ./webpacked/metamaps.bundle
|
||||||
//= require ./src/Metamaps.Router
|
//= require ./Metamaps.ServerData
|
||||||
//= require ./src/Metamaps.Backbone
|
//= require homepageVimeoFallback
|
||||||
//= require ./src/Metamaps.Views
|
|
||||||
//= require ./src/views/chatView
|
|
||||||
//= require ./src/views/videoView
|
|
||||||
//= require ./src/views/room
|
|
||||||
//= require ./src/JIT
|
|
||||||
//= require ./src/check-canvas-support
|
|
||||||
//= require ./src/Metamaps
|
|
||||||
//= require ./src/Metamaps.Create
|
|
||||||
//= require ./src/Metamaps.TopicCard
|
|
||||||
//= require ./src/Metamaps.SynapseCard
|
|
||||||
//= require ./src/Metamaps.Visualize
|
|
||||||
//= require ./src/Metamaps.Util
|
|
||||||
//= require ./src/Metamaps.Realtime
|
|
||||||
//= require ./src/Metamaps.Control
|
|
||||||
//= require ./src/Metamaps.Filter
|
|
||||||
//= require ./src/Metamaps.Listeners
|
|
||||||
//= require ./src/Metamaps.Organize
|
|
||||||
//= require ./src/Metamaps.Topic
|
|
||||||
//= require ./src/Metamaps.Synapse
|
|
||||||
//= require ./src/Metamaps.Map
|
|
||||||
//= require ./src/Metamaps.Account
|
|
||||||
//= require ./src/Metamaps.Mapper
|
|
||||||
//= require ./src/Metamaps.Mobile
|
|
||||||
//= require ./src/Metamaps.Admin
|
|
||||||
//= require ./src/Metamaps.Import
|
|
||||||
//= require ./src/Metamaps.JIT
|
|
||||||
//= require ./src/Metamaps.Debug
|
|
||||||
|
|
29
app/assets/javascripts/homepageVimeoFallback.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* global $ */
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
if (window.location.pathname === '/') {
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: 'https://player.vimeo.com',
|
||||||
|
error: function (e) {
|
||||||
|
$('.homeVideo').hide()
|
||||||
|
$('.homeVideo').replaceWith($('<video/>', {
|
||||||
|
poster: '/assets/metamaps-intro-poster.webp',
|
||||||
|
width: '560',
|
||||||
|
height: '315',
|
||||||
|
class: 'homeVideo',
|
||||||
|
controls: ''
|
||||||
|
}))
|
||||||
|
$('.homeVideo').append($('<source/>', {
|
||||||
|
src: 'https://metamaps.cc/videos/metamaps-intro.mp4',
|
||||||
|
type: 'video/mp4'
|
||||||
|
}))
|
||||||
|
$('.homeVideo').append(
|
||||||
|
'<p>You can watch our instruction video at ' +
|
||||||
|
'<a href="https://metamaps.cc/videos/metamaps-intro.mp4">' +
|
||||||
|
'https://metamaps.cc/videos/metamaps-intro.mp4</a>.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}// if
|
||||||
|
})
|
|
@ -1,39 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
685
app/assets/javascripts/lib/best_in_place.js
Normal file
|
@ -0,0 +1,685 @@
|
||||||
|
/*
|
||||||
|
* BestInPlace (for jQuery)
|
||||||
|
* version: 3.0.0.alpha (2014)
|
||||||
|
*
|
||||||
|
* By Bernat Farrero based on the work of Jan Varwig.
|
||||||
|
* Examples at http://bernatfarrero.com
|
||||||
|
*
|
||||||
|
* Licensed under the MIT:
|
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
*
|
||||||
|
* @requires jQuery
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* Attention.
|
||||||
|
* The format of the JSON object given to the select inputs is the following:
|
||||||
|
* [["key", "value"],["key", "value"]]
|
||||||
|
* The format of the JSON object given to the checkbox inputs is the following:
|
||||||
|
* ["falseValue", "trueValue"]
|
||||||
|
|
||||||
|
*/
|
||||||
|
//= require jquery.autosize
|
||||||
|
|
||||||
|
function BestInPlaceEditor(e) {
|
||||||
|
'use strict';
|
||||||
|
this.element = e;
|
||||||
|
this.initOptions();
|
||||||
|
this.bindForm();
|
||||||
|
this.initPlaceHolder();
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
BestInPlaceEditor.prototype = {
|
||||||
|
// Public Interface Functions //////////////////////////////////////////////
|
||||||
|
|
||||||
|
activate: function () {
|
||||||
|
'use strict';
|
||||||
|
var to_display;
|
||||||
|
if (this.isPlaceHolder()) {
|
||||||
|
to_display = "";
|
||||||
|
} else if (this.original_content) {
|
||||||
|
to_display = this.original_content;
|
||||||
|
} else {
|
||||||
|
switch (this.formType) {
|
||||||
|
case 'input':
|
||||||
|
case 'textarea':
|
||||||
|
if (this.display_raw) {
|
||||||
|
to_display = this.element.html().replace(/&/gi, '&');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var value = this.element.data('bipValue');
|
||||||
|
if (typeof value === 'undefined') {
|
||||||
|
to_display = '';
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
to_display = this.element.data('bipValue').replace(/&/gi, '&');
|
||||||
|
} else {
|
||||||
|
to_display = this.element.data('bipValue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
to_display = this.element.html();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldValue = this.isPlaceHolder() ? "" : this.element.html();
|
||||||
|
this.display_value = to_display;
|
||||||
|
jQuery(this.activator).unbind("click", this.clickHandler);
|
||||||
|
this.activateForm();
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
||||||
|
},
|
||||||
|
|
||||||
|
abort: function () {
|
||||||
|
'use strict';
|
||||||
|
this.activateText(this.oldValue);
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||||
|
},
|
||||||
|
|
||||||
|
abortIfConfirm: function () {
|
||||||
|
'use strict';
|
||||||
|
if (!this.useConfirm) {
|
||||||
|
this.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirm(BestInPlaceEditor.defaults.locales[''].confirmMessage)) {
|
||||||
|
this.abort();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function () {
|
||||||
|
'use strict';
|
||||||
|
var editor = this,
|
||||||
|
value = this.getValue();
|
||||||
|
|
||||||
|
// Avoid request if no change is made
|
||||||
|
if (this.formType in {"input": 1, "textarea": 1} && value === this.oldValue) {
|
||||||
|
this.abort();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.ajax({
|
||||||
|
"type": this.requestMethod(),
|
||||||
|
"dataType": BestInPlaceEditor.defaults.ajaxDataType,
|
||||||
|
"data": editor.requestData(),
|
||||||
|
"success": function (data, status, xhr) {
|
||||||
|
editor.loadSuccessCallback(data, status, xhr);
|
||||||
|
},
|
||||||
|
"error": function (request, error) {
|
||||||
|
editor.loadErrorCallback(request, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
switch (this.formType) {
|
||||||
|
case "select":
|
||||||
|
this.previousCollectionValue = value;
|
||||||
|
|
||||||
|
// search for the text for the span
|
||||||
|
$.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "checkbox":
|
||||||
|
$.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (value !== "") {
|
||||||
|
if (this.display_raw) {
|
||||||
|
editor.element.html(value);
|
||||||
|
} else {
|
||||||
|
editor.element.text(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editor.element.html(this.placeHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.element.data('bipValue', value);
|
||||||
|
editor.element.attr('data-bip-value', value);
|
||||||
|
|
||||||
|
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
|
||||||
|
},
|
||||||
|
|
||||||
|
activateText: function (value) {
|
||||||
|
'use strict';
|
||||||
|
this.element.html(value);
|
||||||
|
if (this.isPlaceHolder()) {
|
||||||
|
this.element.html(this.placeHolder);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper Functions ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
initOptions: function () {
|
||||||
|
// Try parent supplied info
|
||||||
|
'use strict';
|
||||||
|
var self = this;
|
||||||
|
self.element.parents().each(function () {
|
||||||
|
var $parent = jQuery(this);
|
||||||
|
self.url = self.url || $parent.data("bipUrl");
|
||||||
|
self.activator = self.activator || $parent.data("bipActivator");
|
||||||
|
self.okButton = self.okButton || $parent.data("bipOkButton");
|
||||||
|
self.okButtonClass = self.okButtonClass || $parent.data("bipOkButtonClass");
|
||||||
|
self.cancelButton = self.cancelButton || $parent.data("bipCancelButton");
|
||||||
|
self.cancelButtonClass = self.cancelButtonClass || $parent.data("bipCancelButtonClass");
|
||||||
|
self.skipBlur = self.skipBlur || $parent.data("bipSkipBlur");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load own attributes (overrides all others)
|
||||||
|
self.url = self.element.data("bipUrl") || self.url || document.location.pathname;
|
||||||
|
self.collection = self.element.data("bipCollection") || self.collection;
|
||||||
|
self.formType = self.element.data("bipType") || "input";
|
||||||
|
self.objectName = self.element.data("bipObject") || self.objectName;
|
||||||
|
self.attributeName = self.element.data("bipAttribute") || self.attributeName;
|
||||||
|
self.activator = self.element.data("bipActivator") || self.element;
|
||||||
|
self.okButton = self.element.data("bipOkButton") || self.okButton;
|
||||||
|
self.okButtonClass = self.element.data("bipOkButtonClass") || self.okButtonClass || BestInPlaceEditor.defaults.okButtonClass;
|
||||||
|
self.cancelButton = self.element.data("bipCancelButton") || self.cancelButton;
|
||||||
|
self.cancelButtonClass = self.element.data("bipCancelButtonClass") || self.cancelButtonClass || BestInPlaceEditor.defaults.cancelButtonClass;
|
||||||
|
self.skipBlur = self.element.data("bipSkipBlur") || self.skipBlur || BestInPlaceEditor.defaults.skipBlur;
|
||||||
|
self.isNewObject = self.element.data("bipNewObject");
|
||||||
|
self.dataExtraPayload = self.element.data("bipExtraPayload");
|
||||||
|
|
||||||
|
// Fix for default values of 0
|
||||||
|
if (self.element.data("bipPlaceholder") == null) {
|
||||||
|
self.placeHolder = BestInPlaceEditor.defaults.locales[''].placeHolder;
|
||||||
|
} else {
|
||||||
|
self.placeHolder = self.element.data("bipPlaceholder");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner_class = self.element.data("bipInnerClass");
|
||||||
|
self.html_attrs = self.element.data("bipHtmlAttrs");
|
||||||
|
self.original_content = self.element.data("bipOriginalContent") || self.original_content;
|
||||||
|
|
||||||
|
// if set the input won't be satinized
|
||||||
|
self.display_raw = self.element.data("bip-raw");
|
||||||
|
|
||||||
|
self.useConfirm = self.element.data("bip-confirm");
|
||||||
|
|
||||||
|
if (self.formType === "select" || self.formType === "checkbox") {
|
||||||
|
self.values = self.collection;
|
||||||
|
self.collectionValue = self.element.data("bipValue") || self.collectionValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bindForm: function () {
|
||||||
|
'use strict';
|
||||||
|
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
||||||
|
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
initPlaceHolder: function () {
|
||||||
|
'use strict';
|
||||||
|
// TODO add placeholder for select and checkbox
|
||||||
|
if (this.element.html() === "") {
|
||||||
|
this.element.addClass('bip-placeholder');
|
||||||
|
this.element.html(this.placeHolder);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isPlaceHolder: function () {
|
||||||
|
'use strict';
|
||||||
|
// TODO: It only work when form is deactivated.
|
||||||
|
// Condition will fail when form is activated
|
||||||
|
return this.element.html() === "" || this.element.html() === this.placeHolder;
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Trim and Strips HTML from text
|
||||||
|
sanitizeValue: function (s) {
|
||||||
|
'use strict';
|
||||||
|
return jQuery.trim(s);
|
||||||
|
},
|
||||||
|
|
||||||
|
requestMethod: function() {
|
||||||
|
'use strict';
|
||||||
|
return this.isNewObject ? 'post' : BestInPlaceEditor.defaults.ajaxMethod;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Generate the data sent in the POST request */
|
||||||
|
requestData: function () {
|
||||||
|
'use strict';
|
||||||
|
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
||||||
|
var csrf_token = jQuery('meta[name=csrf-token]').attr('content'),
|
||||||
|
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
||||||
|
|
||||||
|
var data = {}
|
||||||
|
data['_method'] = this.requestMethod()
|
||||||
|
|
||||||
|
data[this.objectName] = this.dataExtraPayload || {}
|
||||||
|
|
||||||
|
data[this.objectName][this.attributeName] = this.getValue()
|
||||||
|
|
||||||
|
if (csrf_param !== undefined && csrf_token !== undefined) {
|
||||||
|
data[csrf_param] = csrf_token
|
||||||
|
}
|
||||||
|
return jQuery.param(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
ajax: function (options) {
|
||||||
|
'use strict';
|
||||||
|
options.url = this.url;
|
||||||
|
options.beforeSend = function (xhr) {
|
||||||
|
xhr.setRequestHeader("Accept", "application/json");
|
||||||
|
};
|
||||||
|
return jQuery.ajax(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handlers ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
loadSuccessCallback: function (data, status, xhr) {
|
||||||
|
'use strict';
|
||||||
|
data = jQuery.trim(data);
|
||||||
|
//Update original content with current text.
|
||||||
|
if (this.display_raw) {
|
||||||
|
this.original_content = this.element.html();
|
||||||
|
} else {
|
||||||
|
this.original_content = this.element.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data !== "") {
|
||||||
|
var response = jQuery.parseJSON(data);
|
||||||
|
if (response !== null && response.hasOwnProperty("display_as")) {
|
||||||
|
this.element.data('bip-original-content', this.element.text());
|
||||||
|
this.element.html(response.display_as);
|
||||||
|
}
|
||||||
|
if (this.isNewObject && response && response[this.objectName]) {
|
||||||
|
if (response[this.objectName]["id"]) {
|
||||||
|
this.isNewObject = false
|
||||||
|
this.url += "/" + response[this.objectName]["id"] // in REST a POST /thing url should become PUT /thing/123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.element.toggleClass('bip-placeholder', this.isPlaceHolder());
|
||||||
|
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:success"), [data, status, xhr]);
|
||||||
|
this.element.trigger(jQuery.Event("ajax:success"), [data, status, xhr]);
|
||||||
|
|
||||||
|
// Binding back after being clicked
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||||
|
|
||||||
|
if (this.collectionValue !== null && this.formType === "select") {
|
||||||
|
this.collectionValue = this.previousCollectionValue;
|
||||||
|
this.previousCollectionValue = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadErrorCallback: function (request, error) {
|
||||||
|
'use strict';
|
||||||
|
this.activateText(this.oldValue);
|
||||||
|
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
||||||
|
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
||||||
|
|
||||||
|
// Binding back after being clicked
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||||
|
},
|
||||||
|
|
||||||
|
clickHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.editor.activate();
|
||||||
|
},
|
||||||
|
|
||||||
|
setHtmlAttributes: function () {
|
||||||
|
'use strict';
|
||||||
|
var formField = this.element.find(this.formType);
|
||||||
|
|
||||||
|
if (this.html_attrs) {
|
||||||
|
var attrs = this.html_attrs;
|
||||||
|
$.each(attrs, function (key, val) {
|
||||||
|
formField.attr(key, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
placeButtons: function (output, field) {
|
||||||
|
'use strict';
|
||||||
|
if (field.okButton) {
|
||||||
|
output.append(
|
||||||
|
jQuery(document.createElement('input'))
|
||||||
|
.attr('type', 'submit')
|
||||||
|
.attr('class', field.okButtonClass)
|
||||||
|
.attr('value', field.okButton)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (field.cancelButton) {
|
||||||
|
output.append(
|
||||||
|
jQuery(document.createElement('input'))
|
||||||
|
.attr('type', 'button')
|
||||||
|
.attr('class', field.cancelButtonClass)
|
||||||
|
.attr('value', field.cancelButton)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Button cases:
|
||||||
|
// If no buttons, then blur saves, ESC cancels
|
||||||
|
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
||||||
|
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
||||||
|
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
||||||
|
BestInPlaceEditor.forms = {
|
||||||
|
"input": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
var output = jQuery(document.createElement('form'))
|
||||||
|
.addClass('form_in_place')
|
||||||
|
.attr('action', 'javascript:void(0);')
|
||||||
|
.attr('style', 'display:inline');
|
||||||
|
var input_elt = jQuery(document.createElement('input'))
|
||||||
|
.attr('type', 'text')
|
||||||
|
.attr('name', this.attributeName)
|
||||||
|
.val(this.display_value);
|
||||||
|
|
||||||
|
// Add class to form input
|
||||||
|
if (this.inner_class) {
|
||||||
|
input_elt.addClass(this.inner_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.append(input_elt);
|
||||||
|
this.placeButtons(output, this);
|
||||||
|
|
||||||
|
this.element.html(output);
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
|
||||||
|
this.element.find("input[type='text']")[0].select();
|
||||||
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
||||||
|
if (this.cancelButton) {
|
||||||
|
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
||||||
|
}
|
||||||
|
if (!this.okButton) {
|
||||||
|
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
||||||
|
}
|
||||||
|
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
||||||
|
this.blurTimer = null;
|
||||||
|
this.userClicked = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.sanitizeValue(this.element.find("input").val());
|
||||||
|
},
|
||||||
|
|
||||||
|
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||||
|
inputBlurHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.data.editor.okButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.abort();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
if (event.data.editor.cancelButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submitHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelButtonHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.abort();
|
||||||
|
event.stopPropagation(); // Without this, click isn't handled
|
||||||
|
},
|
||||||
|
|
||||||
|
keyupHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
event.data.editor.abort();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"select": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
var output = jQuery(document.createElement('form'))
|
||||||
|
.attr('action', 'javascript:void(0)')
|
||||||
|
.attr('style', 'display:inline'),
|
||||||
|
selected = '',
|
||||||
|
select_elt = jQuery(document.createElement('select'))
|
||||||
|
.attr('class', this.inner_class !== null ? this.inner_class : ''),
|
||||||
|
currentCollectionValue = this.collectionValue,
|
||||||
|
key, value,
|
||||||
|
a = this.values;
|
||||||
|
|
||||||
|
$.each(a, function(index, arr){
|
||||||
|
key = arr[0];
|
||||||
|
value = arr[1];
|
||||||
|
var option_elt = jQuery(document.createElement('option'))
|
||||||
|
.val(key)
|
||||||
|
.html(value);
|
||||||
|
|
||||||
|
if (currentCollectionValue) {
|
||||||
|
if (String(key) === String(currentCollectionValue)) option_elt.attr('selected', 'selected');
|
||||||
|
}
|
||||||
|
select_elt.append(option_elt);
|
||||||
|
});
|
||||||
|
output.append(select_elt);
|
||||||
|
|
||||||
|
this.element.html(output);
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||||
|
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||||
|
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
||||||
|
this.element.find("select")[0].focus();
|
||||||
|
|
||||||
|
// automatically click on the select so you
|
||||||
|
// don't have to click twice
|
||||||
|
try {
|
||||||
|
var e = document.createEvent("MouseEvents");
|
||||||
|
e.initMouseEvent("mousedown", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||||
|
this.element.find("select")[0].dispatchEvent(e);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// browser doesn't support this, e.g. IE8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.sanitizeValue(this.element.find("select").val());
|
||||||
|
},
|
||||||
|
|
||||||
|
blurHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
keyupHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
event.data.editor.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"checkbox": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
this.collectionValue = !this.getValue();
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.collectionValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
// grab width and height of text
|
||||||
|
var width = this.element.css('width');
|
||||||
|
var height = this.element.css('height');
|
||||||
|
|
||||||
|
// construct form
|
||||||
|
var output = jQuery(document.createElement('form'))
|
||||||
|
.addClass('form_in_place')
|
||||||
|
.attr('action', 'javascript:void(0);')
|
||||||
|
.attr('style', 'display:inline');
|
||||||
|
var textarea_elt = jQuery(document.createElement('textarea'))
|
||||||
|
.attr('name', this.attributeName)
|
||||||
|
.val(this.sanitizeValue(this.display_value));
|
||||||
|
|
||||||
|
if (this.inner_class !== null) {
|
||||||
|
textarea_elt.addClass(this.inner_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.append(textarea_elt);
|
||||||
|
|
||||||
|
this.placeButtons(output, this);
|
||||||
|
|
||||||
|
this.element.html(output);
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
|
||||||
|
// set width and height of textarea
|
||||||
|
jQuery(this.element.find("textarea")[0]).css({'min-width': width, 'min-height': height});
|
||||||
|
jQuery(this.element.find("textarea")[0]).autosize();
|
||||||
|
|
||||||
|
this.element.find("textarea")[0].focus();
|
||||||
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
||||||
|
|
||||||
|
if (this.cancelButton) {
|
||||||
|
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.skipBlur) {
|
||||||
|
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
||||||
|
}
|
||||||
|
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
||||||
|
this.blurTimer = null;
|
||||||
|
this.userClicked = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.sanitizeValue(this.element.find("textarea").val());
|
||||||
|
},
|
||||||
|
|
||||||
|
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||||
|
blurHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.data.editor.okButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.abortIfConfirm();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
if (event.data.editor.cancelButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submitHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelButtonHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.abortIfConfirm();
|
||||||
|
event.stopPropagation(); // Without this, click isn't handled
|
||||||
|
},
|
||||||
|
|
||||||
|
keyupHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
event.data.editor.abortIfConfirm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BestInPlaceEditor.defaults = {
|
||||||
|
locales: {},
|
||||||
|
ajaxMethod: "put", //TODO Change to patch when support to 3.2 is dropped
|
||||||
|
ajaxDataType: 'text',
|
||||||
|
okButtonClass: '',
|
||||||
|
cancelButtonClass: '',
|
||||||
|
skipBlur: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default locale
|
||||||
|
BestInPlaceEditor.defaults.locales[''] = {
|
||||||
|
confirmMessage: "Are you sure you want to discard your changes?",
|
||||||
|
uninitializedForm: "The form was not properly initialized. getValue is unbound",
|
||||||
|
placeHolder: '-'
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery.fn.best_in_place = function () {
|
||||||
|
'use strict';
|
||||||
|
function setBestInPlace(element) {
|
||||||
|
if (!element.data('bestInPlaceEditor')) {
|
||||||
|
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery(this.context).delegate(this.selector, 'click', function () {
|
||||||
|
var el = jQuery(this);
|
||||||
|
if (setBestInPlace(el)) {
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.each(function () {
|
||||||
|
setBestInPlace(jQuery(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,780 +0,0 @@
|
||||||
/*
|
|
||||||
BestInPlace (for jQuery)
|
|
||||||
version: 0.1.0 (01/01/2011)
|
|
||||||
@requires jQuery >= v1.4
|
|
||||||
@requires jQuery.purr to display pop-up windows
|
|
||||||
|
|
||||||
By Bernat Farrero based on the work of Jan Varwig.
|
|
||||||
Examples at http://bernatfarrero.com
|
|
||||||
|
|
||||||
Licensed under the MIT:
|
|
||||||
http://www.opensource.org/licenses/mit-license.php
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
Attention.
|
|
||||||
The format of the JSON object given to the select inputs is the following:
|
|
||||||
[["key", "value"],["key", "value"]]
|
|
||||||
The format of the JSON object given to the checkbox inputs is the following:
|
|
||||||
["falseValue", "trueValue"]
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function BestInPlaceEditor(e) {
|
|
||||||
this.element = e;
|
|
||||||
this.initOptions();
|
|
||||||
this.bindForm();
|
|
||||||
this.initNil();
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
BestInPlaceEditor.prototype = {
|
|
||||||
// Public Interface Functions //////////////////////////////////////////////
|
|
||||||
|
|
||||||
activate : function() {
|
|
||||||
var to_display = "";
|
|
||||||
if (this.isNil()) {
|
|
||||||
to_display = "";
|
|
||||||
}
|
|
||||||
else if (this.original_content) {
|
|
||||||
to_display = this.original_content;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.sanitize) {
|
|
||||||
to_display = this.element.text();
|
|
||||||
} else {
|
|
||||||
to_display = this.element.html();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.oldValue = this.isNil() ? "" : this.element.html();
|
|
||||||
this.display_value = to_display;
|
|
||||||
jQuery(this.activator).unbind("click", this.clickHandler);
|
|
||||||
this.activateForm();
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
|
||||||
},
|
|
||||||
|
|
||||||
abort : function() {
|
|
||||||
this.activateText(this.oldValue);
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
||||||
},
|
|
||||||
|
|
||||||
abortIfConfirm : function () {
|
|
||||||
if (!this.useConfirm) {
|
|
||||||
this.abort();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirm("Are you sure you want to discard your changes?")) {
|
|
||||||
this.abort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
update : function() {
|
|
||||||
var editor = this;
|
|
||||||
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
|
||||||
{ // Avoid request if no change is made
|
|
||||||
this.abort();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
editor.ajax({
|
|
||||||
"type" : "post",
|
|
||||||
"dataType" : "text",
|
|
||||||
"data" : editor.requestData(),
|
|
||||||
"success" : function(data){ editor.loadSuccessCallback(data); },
|
|
||||||
"error" : function(request, error){ editor.loadErrorCallback(request, error); }
|
|
||||||
});
|
|
||||||
if (this.formType == "select") {
|
|
||||||
var value = this.getValue();
|
|
||||||
this.previousCollectionValue = value;
|
|
||||||
|
|
||||||
jQuery.each(this.values, function(i, v) {
|
|
||||||
if (value == v[0]) {
|
|
||||||
editor.element.html(v[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (this.formType == "checkbox") {
|
|
||||||
editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
|
|
||||||
} else {
|
|
||||||
if (this.getValue() !== "") {
|
|
||||||
editor.element.text(this.getValue());
|
|
||||||
} else {
|
|
||||||
editor.element.html(this.nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
|
||||||
},
|
|
||||||
|
|
||||||
activateForm : function() {
|
|
||||||
alert("The form was not properly initialized. activateForm is unbound");
|
|
||||||
},
|
|
||||||
|
|
||||||
activateText : function(value){
|
|
||||||
this.element.html(value);
|
|
||||||
if(this.isNil()) this.element.html(this.nil);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Helper Functions ////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
initOptions : function() {
|
|
||||||
// Try parent supplied info
|
|
||||||
var self = this;
|
|
||||||
self.element.parents().each(function(){
|
|
||||||
$parent = jQuery(this);
|
|
||||||
self.url = self.url || $parent.attr("data-url");
|
|
||||||
self.collection = self.collection || $parent.attr("data-collection");
|
|
||||||
self.formType = self.formType || $parent.attr("data-type");
|
|
||||||
self.objectName = self.objectName || $parent.attr("data-object");
|
|
||||||
self.attributeName = self.attributeName || $parent.attr("data-attribute");
|
|
||||||
self.activator = self.activator || $parent.attr("data-activator");
|
|
||||||
self.okButton = self.okButton || $parent.attr("data-ok-button");
|
|
||||||
self.okButtonClass = self.okButtonClass || $parent.attr("data-ok-button-class");
|
|
||||||
self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button");
|
|
||||||
self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
|
|
||||||
self.nil = self.nil || $parent.attr("data-nil");
|
|
||||||
self.inner_class = self.inner_class || $parent.attr("data-inner-class");
|
|
||||||
self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs");
|
|
||||||
self.original_content = self.original_content || $parent.attr("data-original-content");
|
|
||||||
self.collectionValue = self.collectionValue || $parent.attr("data-value");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Try Rails-id based if parents did not explicitly supply something
|
|
||||||
self.element.parents().each(function(){
|
|
||||||
var res = this.id.match(/^(\w+)_(\d+)$/i);
|
|
||||||
if (res) {
|
|
||||||
self.objectName = self.objectName || res[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load own attributes (overrides all others)
|
|
||||||
self.url = self.element.attr("data-url") || self.url || document.location.pathname;
|
|
||||||
self.collection = self.element.attr("data-collection") || self.collection;
|
|
||||||
self.formType = self.element.attr("data-type") || self.formtype || "input";
|
|
||||||
self.objectName = self.element.attr("data-object") || self.objectName;
|
|
||||||
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
|
||||||
self.activator = self.element.attr("data-activator") || self.element;
|
|
||||||
self.okButton = self.element.attr("data-ok-button") || self.okButton;
|
|
||||||
self.okButtonClass = self.element.attr("data-ok-button-class") || self.okButtonClass || "";
|
|
||||||
self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
|
|
||||||
self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
|
|
||||||
self.nil = self.element.attr("data-nil") || self.nil || "—";
|
|
||||||
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
|
||||||
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
|
||||||
self.original_content = self.element.attr("data-original-content") || self.original_content;
|
|
||||||
self.collectionValue = self.element.attr("data-value") || self.collectionValue;
|
|
||||||
|
|
||||||
if (!self.element.attr("data-sanitize")) {
|
|
||||||
self.sanitize = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.sanitize = (self.element.attr("data-sanitize") == "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self.element.attr("data-use-confirm")) {
|
|
||||||
self.useConfirm = true;
|
|
||||||
} else {
|
|
||||||
self.useConfirm = (self.element.attr("data-use-confirm") != "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
|
|
||||||
{
|
|
||||||
self.values = jQuery.parseJSON(self.collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
bindForm : function() {
|
|
||||||
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
|
||||||
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
|
||||||
},
|
|
||||||
|
|
||||||
initNil: function() {
|
|
||||||
if (this.element.html() === "")
|
|
||||||
{
|
|
||||||
this.element.html(this.nil);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isNil: function() {
|
|
||||||
// TODO: It only work when form is deactivated.
|
|
||||||
// Condition will fail when form is activated
|
|
||||||
return this.element.html() === "" || this.element.html() === this.nil;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
alert("The form was not properly initialized. getValue is unbound");
|
|
||||||
},
|
|
||||||
|
|
||||||
// Trim and Strips HTML from text
|
|
||||||
sanitizeValue : function(s) {
|
|
||||||
return jQuery.trim(s);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Generate the data sent in the POST request */
|
|
||||||
requestData : function() {
|
|
||||||
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
|
||||||
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
|
|
||||||
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
|
||||||
|
|
||||||
var data = "_method=put";
|
|
||||||
data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
|
|
||||||
|
|
||||||
if (csrf_param !== undefined && csrf_token !== undefined) {
|
|
||||||
data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
ajax : function(options) {
|
|
||||||
options.url = this.url;
|
|
||||||
options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
|
|
||||||
return jQuery.ajax(options);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Handlers ////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
loadSuccessCallback : function(data) {
|
|
||||||
data = jQuery.trim(data);
|
|
||||||
|
|
||||||
if(data && data!=""){
|
|
||||||
var response = jQuery.parseJSON(jQuery.trim(data));
|
|
||||||
if (response !== null && response.hasOwnProperty("display_as")) {
|
|
||||||
this.element.attr("data-original-content", this.element.text());
|
|
||||||
this.original_content = this.element.text();
|
|
||||||
this.element.html(response["display_as"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:success"), data);
|
|
||||||
this.element.trigger(jQuery.Event("ajax:success"), data);
|
|
||||||
} else {
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:success"));
|
|
||||||
this.element.trigger(jQuery.Event("ajax:success"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binding back after being clicked
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
||||||
|
|
||||||
if (this.collectionValue !== null && this.formType == "select") {
|
|
||||||
this.collectionValue = this.previousCollectionValue;
|
|
||||||
this.previousCollectionValue = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadErrorCallback : function(request, error) {
|
|
||||||
this.activateText(this.oldValue);
|
|
||||||
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
|
||||||
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
|
||||||
|
|
||||||
// Binding back after being clicked
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
||||||
},
|
|
||||||
|
|
||||||
clickHandler : function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.data.editor.activate();
|
|
||||||
},
|
|
||||||
|
|
||||||
setHtmlAttributes : function() {
|
|
||||||
var formField = this.element.find(this.formType);
|
|
||||||
|
|
||||||
if(this.html_attrs){
|
|
||||||
var attrs = jQuery.parseJSON(this.html_attrs);
|
|
||||||
for(var key in attrs){
|
|
||||||
formField.attr(key, attrs[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Button cases:
|
|
||||||
// If no buttons, then blur saves, ESC cancels
|
|
||||||
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
|
||||||
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
|
||||||
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
|
||||||
BestInPlaceEditor.forms = {
|
|
||||||
"input" : {
|
|
||||||
activateForm : function() {
|
|
||||||
var output = jQuery(document.createElement('form'))
|
|
||||||
.addClass('form_in_place')
|
|
||||||
.attr('action', 'javascript:void(0);')
|
|
||||||
.attr('style', 'display:inline');
|
|
||||||
var input_elt = jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'text')
|
|
||||||
.attr('name', this.attributeName)
|
|
||||||
.val(this.display_value);
|
|
||||||
if(this.inner_class !== null) {
|
|
||||||
input_elt.addClass(this.inner_class);
|
|
||||||
}
|
|
||||||
output.append(input_elt);
|
|
||||||
if(this.okButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'submit')
|
|
||||||
.attr('class', this.okButtonClass)
|
|
||||||
.attr('value', this.okButton)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(this.cancelButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'button')
|
|
||||||
.attr('class', this.cancelButtonClass)
|
|
||||||
.attr('value', this.cancelButton)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
// START METAMAPS CODE
|
|
||||||
//this.element.find("input[type='text']")[0].select();
|
|
||||||
this.element.find("input[type='text']")[0].focus();
|
|
||||||
// END METAMAPS CODE
|
|
||||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
|
||||||
if (this.cancelButton) {
|
|
||||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
|
||||||
}
|
|
||||||
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
|
||||||
// START METAMAPS CODE
|
|
||||||
this.element.find("input[type='text']").bind('keydown', {editor: this}, BestInPlaceEditor.forms.input.keydownHandler);
|
|
||||||
// END METAMAPS CODE
|
|
||||||
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
|
||||||
this.blurTimer = null;
|
|
||||||
this.userClicked = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("input").val());
|
|
||||||
},
|
|
||||||
|
|
||||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
|
||||||
inputBlurHandler : function(event) {
|
|
||||||
if (event.data.editor.okButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.abort();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
if (event.data.editor.cancelButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
submitHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelButtonHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.abort();
|
|
||||||
event.stopPropagation(); // Without this, click isn't handled
|
|
||||||
},
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) {
|
|
||||||
event.data.editor.abort();
|
|
||||||
}
|
|
||||||
// START METAMAPS CODE
|
|
||||||
else if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
// END METAMAPS CODE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"date" : {
|
|
||||||
activateForm : function() {
|
|
||||||
var that = this,
|
|
||||||
output = jQuery(document.createElement('form'))
|
|
||||||
.addClass('form_in_place')
|
|
||||||
.attr('action', 'javascript:void(0);')
|
|
||||||
.attr('style', 'display:inline'),
|
|
||||||
input_elt = jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'text')
|
|
||||||
.attr('name', this.attributeName)
|
|
||||||
.attr('value', this.sanitizeValue(this.display_value));
|
|
||||||
if(this.inner_class !== null) {
|
|
||||||
input_elt.addClass(this.inner_class);
|
|
||||||
}
|
|
||||||
output.append(input_elt)
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
this.element.find('input')[0].select();
|
|
||||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
|
||||||
this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
|
||||||
|
|
||||||
this.element.find('input')
|
|
||||||
.datepicker({
|
|
||||||
onClose: function() {
|
|
||||||
that.update();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.datepicker('show');
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("input").val());
|
|
||||||
},
|
|
||||||
|
|
||||||
submitHandler : function(event) {
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
// START METAMAPS CODE
|
|
||||||
keydownHandler : function(event) {
|
|
||||||
if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// END METAMAPS CODE
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) {
|
|
||||||
event.data.editor.abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"select" : {
|
|
||||||
activateForm : function() {
|
|
||||||
var output = jQuery(document.createElement('form'))
|
|
||||||
.attr('action', 'javascript:void(0)')
|
|
||||||
.attr('style', 'display:inline');
|
|
||||||
selected = '',
|
|
||||||
oldValue = this.oldValue,
|
|
||||||
select_elt = jQuery(document.createElement('select'))
|
|
||||||
.attr('class', this.inned_class !== null ? this.inner_class : '' ),
|
|
||||||
currentCollectionValue = this.collectionValue;
|
|
||||||
|
|
||||||
jQuery.each(this.values, function (index, value) {
|
|
||||||
var option_elt = jQuery(document.createElement('option'))
|
|
||||||
// .attr('value', value[0])
|
|
||||||
.val(value[0])
|
|
||||||
.html(value[1]);
|
|
||||||
if(value[0] == currentCollectionValue) {
|
|
||||||
option_elt.attr('selected', 'selected');
|
|
||||||
}
|
|
||||||
select_elt.append(option_elt);
|
|
||||||
});
|
|
||||||
output.append(select_elt);
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
|
||||||
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
|
||||||
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
|
||||||
this.element.find("select")[0].focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("select").val());
|
|
||||||
// return this.element.find("select").val();
|
|
||||||
},
|
|
||||||
|
|
||||||
blurHandler : function(event) {
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) event.data.editor.abort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"checkbox" : {
|
|
||||||
activateForm : function() {
|
|
||||||
this.collectionValue = !this.getValue();
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
this.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.collectionValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"textarea" : {
|
|
||||||
activateForm : function() {
|
|
||||||
// grab width and height of text
|
|
||||||
width = this.element.css('width');
|
|
||||||
height = this.element.css('height');
|
|
||||||
|
|
||||||
// construct form
|
|
||||||
var output = jQuery(document.createElement('form'))
|
|
||||||
.attr('action', 'javascript:void(0)')
|
|
||||||
.attr('style', 'display:inline')
|
|
||||||
.append(jQuery(document.createElement('textarea'))
|
|
||||||
.val(this.sanitizeValue(this.display_value)));
|
|
||||||
if(this.okButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'submit')
|
|
||||||
.attr('value', this.okButton)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if(this.cancelButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'button')
|
|
||||||
.attr('value', this.cancelButton)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
|
|
||||||
// set width and height of textarea
|
|
||||||
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
|
||||||
jQuery(this.element.find("textarea")[0]).elastic();
|
|
||||||
|
|
||||||
this.element.find("textarea")[0].focus();
|
|
||||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
|
||||||
if (this.cancelButton) {
|
|
||||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
|
||||||
}
|
|
||||||
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
|
||||||
// START METAMAPS CODE
|
|
||||||
this.element.find("textarea").bind('keydown', {editor: this}, BestInPlaceEditor.forms.textarea.keydownHandler);
|
|
||||||
// END METAMAPS CODE
|
|
||||||
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
|
||||||
this.blurTimer = null;
|
|
||||||
this.userClicked = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("textarea").val());
|
|
||||||
},
|
|
||||||
|
|
||||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
|
||||||
blurHandler : function(event) {
|
|
||||||
if (event.data.editor.okButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.abortIfConfirm();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
if (event.data.editor.cancelButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
submitHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelButtonHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.abortIfConfirm();
|
|
||||||
event.stopPropagation(); // Without this, click isn't handled
|
|
||||||
},
|
|
||||||
|
|
||||||
// START METAMAPS CODE
|
|
||||||
keydownHandler : function(event) {
|
|
||||||
if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// END METAMAPS CODE
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) {
|
|
||||||
event.data.editor.abortIfConfirm();
|
|
||||||
}
|
|
||||||
// START METAMAPS CODE
|
|
||||||
else if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
// END METAMAPS CODE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.best_in_place = function() {
|
|
||||||
|
|
||||||
function setBestInPlace(element) {
|
|
||||||
if (!element.data('bestInPlaceEditor')) {
|
|
||||||
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery(this.context).delegate(this.selector, 'click', function () {
|
|
||||||
var el = jQuery(this);
|
|
||||||
if (setBestInPlace(el))
|
|
||||||
el.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.each(function () {
|
|
||||||
setBestInPlace(jQuery(this));
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name Elastic
|
|
||||||
* @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
|
|
||||||
* @version 1.6.5
|
|
||||||
* @requires Jquery 1.2.6+
|
|
||||||
*
|
|
||||||
* @author Jan Jarfalk
|
|
||||||
* @author-email jan.jarfalk@unwrongest.com
|
|
||||||
* @author-website http://www.unwrongest.com
|
|
||||||
*
|
|
||||||
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function(jQuery){
|
|
||||||
if (typeof jQuery.fn.elastic !== 'undefined') return;
|
|
||||||
|
|
||||||
jQuery.fn.extend({
|
|
||||||
elastic: function() {
|
|
||||||
// We will create a div clone of the textarea
|
|
||||||
// by copying these attributes from the textarea to the div.
|
|
||||||
var mimics = [
|
|
||||||
'paddingTop',
|
|
||||||
'paddingRight',
|
|
||||||
'paddingBottom',
|
|
||||||
'paddingLeft',
|
|
||||||
'fontSize',
|
|
||||||
'lineHeight',
|
|
||||||
'fontFamily',
|
|
||||||
'width',
|
|
||||||
'fontWeight'];
|
|
||||||
|
|
||||||
return this.each( function() {
|
|
||||||
|
|
||||||
// Elastic only works on textareas
|
|
||||||
if ( this.type != 'textarea' ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $textarea = jQuery(this),
|
|
||||||
$twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
|
|
||||||
lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
|
|
||||||
minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
|
|
||||||
maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
|
|
||||||
goalheight = 0,
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
// Opera returns max-height of -1 if not set
|
|
||||||
if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
|
|
||||||
|
|
||||||
// Append the twin to the DOM
|
|
||||||
// We are going to meassure the height of this, not the textarea.
|
|
||||||
$twin.appendTo($textarea.parent());
|
|
||||||
|
|
||||||
// Copy the essential styles (mimics) from the textarea to the twin
|
|
||||||
i = mimics.length;
|
|
||||||
while(i--){
|
|
||||||
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Sets a given height and overflow state on the textarea
|
|
||||||
function setHeightAndOverflow(height, overflow){
|
|
||||||
curratedHeight = Math.floor(parseInt(height,10));
|
|
||||||
if($textarea.height() != curratedHeight){
|
|
||||||
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// This function will update the height of the textarea if necessary
|
|
||||||
function update() {
|
|
||||||
|
|
||||||
// Get curated content from the textarea.
|
|
||||||
var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
|
||||||
|
|
||||||
// Compare curated content with curated twin.
|
|
||||||
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
|
||||||
|
|
||||||
if(textareaContent+' ' != twinContent){
|
|
||||||
|
|
||||||
// Add an extra white space so new rows are added when you are at the end of a row.
|
|
||||||
$twin.html(textareaContent+' ');
|
|
||||||
|
|
||||||
// Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
|
|
||||||
if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
|
|
||||||
|
|
||||||
var goalheight = $twin.height()+lineHeight;
|
|
||||||
if(goalheight >= maxheight) {
|
|
||||||
setHeightAndOverflow(maxheight,'auto');
|
|
||||||
} else if(goalheight <= minheight) {
|
|
||||||
setHeightAndOverflow(minheight,'hidden');
|
|
||||||
} else {
|
|
||||||
setHeightAndOverflow(goalheight,'hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide scrollbars
|
|
||||||
$textarea.css({'overflow':'hidden'});
|
|
||||||
|
|
||||||
// Update textarea size on keyup, change, cut and paste
|
|
||||||
$textarea.bind('keyup change cut paste', function(){
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compact textarea on blur
|
|
||||||
// Lets animate this....
|
|
||||||
$textarea.bind('blur',function(){
|
|
||||||
if($twin.height() < maxheight){
|
|
||||||
if($twin.height() > minheight) {
|
|
||||||
$textarea.height($twin.height());
|
|
||||||
} else {
|
|
||||||
$textarea.height(minheight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// And this line is to catch the browser paste event
|
|
||||||
$textarea.on("input paste", function(e){ setTimeout( update, 250); });
|
|
||||||
|
|
||||||
// Run update once when elastic is initialized
|
|
||||||
update();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})(jQuery);
|
|
2
app/assets/javascripts/lib/canvas-to-blob.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
!function(t){"use strict";var e=t.HTMLCanvasElement&&t.HTMLCanvasElement.prototype,o=t.Blob&&function(){try{return Boolean(new Blob)}catch(t){return!1}}(),n=o&&t.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(t){return!1}}(),r=t.BlobBuilder||t.WebKitBlobBuilder||t.MozBlobBuilder||t.MSBlobBuilder,a=/^data:((.*?)(;charset=.*?)?)(;base64)?,/,i=(o||r)&&t.atob&&t.ArrayBuffer&&t.Uint8Array&&function(t){var e,i,l,u,b,c,d,B,f;if(e=t.match(a),!e)throw new Error("invalid data URI");for(i=e[2]?e[1]:"text/plain"+(e[3]||";charset=US-ASCII"),l=!!e[4],u=t.slice(e[0].length),b=l?atob(u):decodeURIComponent(u),c=new ArrayBuffer(b.length),d=new Uint8Array(c),B=0;B<b.length;B+=1)d[B]=b.charCodeAt(B);return o?new Blob([n?d:c],{type:i}):(f=new r,f.append(c),f.getBlob(i))};t.HTMLCanvasElement&&!e.toBlob&&(e.mozGetAsFile?e.toBlob=function(t,o,n){t(n&&e.toDataURL&&i?i(this.toDataURL(o,n)):this.mozGetAsFile("blob",o))}:e.toDataURL&&i&&(e.toBlob=function(t,e,o){t(i(this.toDataURL(e,o)))})),"function"==typeof define&&define.amd?define(function(){return i}):"object"==typeof module&&module.exports?module.exports=i:t.dataURLtoBlob=i}(window);
|
||||||
|
//# sourceMappingURL=canvas-to-blob.min.js.map
|
|
@ -161,6 +161,7 @@ jQuery.browser = browser;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// START METAMAPS CODE
|
||||||
// Add code that makes tab and shift+tab scroll through metacodes
|
// Add code that makes tab and shift+tab scroll through metacodes
|
||||||
$('.new_topic').bind('keydown',this,function(event){
|
$('.new_topic').bind('keydown',this,function(event){
|
||||||
if (event.keyCode == 9 && event.shiftKey) {
|
if (event.keyCode == 9 && event.shiftKey) {
|
||||||
|
@ -175,6 +176,7 @@ jQuery.browser = browser;
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// END METAMAPS CODE
|
||||||
|
|
||||||
// You will need this plugin for the mousewheel to work: http://plugins.jquery.com/project/mousewheel
|
// You will need this plugin for the mousewheel to work: http://plugins.jquery.com/project/mousewheel
|
||||||
if (options.mouseWheel)
|
if (options.mouseWheel)
|
||||||
|
@ -189,14 +191,14 @@ jQuery.browser = browser;
|
||||||
}
|
}
|
||||||
});*/
|
});*/
|
||||||
// END METAMAPS CODE
|
// END METAMAPS CODE
|
||||||
/* ORIGINAL CODE
|
// ORIGINAL CODE
|
||||||
$(container).bind('mousewheel',this,function(event, delta) {
|
// $(container).bind('mousewheel',this,function(event, delta) {
|
||||||
event.data.rotate(delta);
|
// event.data.rotate(delta);
|
||||||
return false;
|
// return false;
|
||||||
});
|
// });
|
||||||
*/
|
//
|
||||||
}
|
}
|
||||||
$(container).bind('mouseover click',this,function(event){
|
$(container).unbind('mouseover click').bind('mouseover click',this,function(event){
|
||||||
|
|
||||||
clearInterval(event.data.autoRotateTimer); // Stop auto rotation if mouse over.
|
clearInterval(event.data.autoRotateTimer); // Stop auto rotation if mouse over.
|
||||||
var text = $(event.target).attr('alt');
|
var text = $(event.target).attr('alt');
|
||||||
|
@ -211,9 +213,9 @@ jQuery.browser = browser;
|
||||||
if ( options.bringToFront && event.type == 'click' )
|
if ( options.bringToFront && event.type == 'click' )
|
||||||
{
|
{
|
||||||
$(options.titleBox).html( ($(event.target).attr('title') ));
|
$(options.titleBox).html( ($(event.target).attr('title') ));
|
||||||
// METAMAPS CODE
|
// START METAMAPS CODE
|
||||||
Metamaps.Create.newTopic.metacode = $(event.target).attr('data-id');
|
Metamaps.Create.newTopic.metacode = $(event.target).attr('data-id');
|
||||||
// NOT METAMAPS CODE
|
// END METAMAPS CODE
|
||||||
var idx = $(event.target).data('itemIndex');
|
var idx = $(event.target).data('itemIndex');
|
||||||
var frontIndex = event.data.frontIndex;
|
var frontIndex = event.data.frontIndex;
|
||||||
//var diff = idx - frontIndex;
|
//var diff = idx - frontIndex;
|
||||||
|
@ -226,6 +228,11 @@ jQuery.browser = browser;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// START METAMAPS CODE - initialize newTopic.metacode
|
||||||
|
var first = $(this.container).find('img').get(0)
|
||||||
|
Metamaps.Create.newTopic.metacode = $(first).data('id')
|
||||||
|
// END METAMAPS CODE
|
||||||
|
|
||||||
// If we have moved out of a carousel item (or the container itself),
|
// If we have moved out of a carousel item (or the container itself),
|
||||||
// restore the text of the front item in 1 second.
|
// restore the text of the front item in 1 second.
|
||||||
$(container).bind('mouseout',this,function(event){
|
$(container).bind('mouseout',this,function(event){
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
/**
|
|
||||||
* jquery.purr.js
|
|
||||||
* Copyright (c) 2008 Net Perspective (net-perspective.com)
|
|
||||||
* Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
|
|
||||||
*
|
|
||||||
* @author R.A. Ray
|
|
||||||
* @projectDescription jQuery plugin for dynamically displaying unobtrusive messages in the browser. Mimics the behavior of the MacOS program "Growl."
|
|
||||||
* @version 0.1.0
|
|
||||||
*
|
|
||||||
* @requires jquery.js (tested with 1.2.6)
|
|
||||||
*
|
|
||||||
* @param fadeInSpeed int - Duration of fade in animation in miliseconds
|
|
||||||
* default: 500
|
|
||||||
* @param fadeOutSpeed int - Duration of fade out animationin miliseconds
|
|
||||||
default: 500
|
|
||||||
* @param removeTimer int - Timeout, in miliseconds, before notice is removed once it is the top non-sticky notice in the list
|
|
||||||
default: 4000
|
|
||||||
* @param isSticky bool - Whether the notice should fade out on its own or wait to be manually closed
|
|
||||||
default: false
|
|
||||||
* @param usingTransparentPNG bool - Whether or not the notice is using transparent .png images in its styling
|
|
||||||
default: false
|
|
||||||
*/
|
|
||||||
|
|
||||||
( function( $ ) {
|
|
||||||
|
|
||||||
$.purr = function ( notice, options )
|
|
||||||
{
|
|
||||||
// Convert notice to a jQuery object
|
|
||||||
notice = $( notice );
|
|
||||||
|
|
||||||
// Add a class to denote the notice as not sticky
|
|
||||||
if ( !options.isSticky )
|
|
||||||
{
|
|
||||||
notice.addClass( 'not-sticky' );
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the container element from the page
|
|
||||||
var cont = document.getElementById( 'purr-container' );
|
|
||||||
|
|
||||||
// If the container doesn't yet exist, we need to create it
|
|
||||||
if ( !cont )
|
|
||||||
{
|
|
||||||
cont = '<div id="purr-container"></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert cont to a jQuery object
|
|
||||||
cont = $( cont );
|
|
||||||
|
|
||||||
// Add the container to the page
|
|
||||||
$( 'body' ).append( cont );
|
|
||||||
|
|
||||||
notify();
|
|
||||||
|
|
||||||
function notify ()
|
|
||||||
{
|
|
||||||
// Set up the close button
|
|
||||||
var close = document.createElement( 'a' );
|
|
||||||
$( close ).attr(
|
|
||||||
{
|
|
||||||
className: 'close',
|
|
||||||
href: '#close',
|
|
||||||
innerHTML: 'Close'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.appendTo( notice )
|
|
||||||
.click( function ()
|
|
||||||
{
|
|
||||||
removeNotice();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the notice to the page and keep it hidden initially
|
|
||||||
notice.appendTo( cont )
|
|
||||||
.hide();
|
|
||||||
|
|
||||||
if ( jQuery.browser.msie && options.usingTransparentPNG )
|
|
||||||
{
|
|
||||||
// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
|
|
||||||
// notice style, we'll just skip the fading in.
|
|
||||||
notice.show();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Fade in the notice we just added
|
|
||||||
notice.fadeIn( options.fadeInSpeed );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the removal interval for the added notice if that notice is not a sticky
|
|
||||||
if ( !options.isSticky )
|
|
||||||
{
|
|
||||||
var topSpotInt = setInterval( function ()
|
|
||||||
{
|
|
||||||
// Check to see if our notice is the first non-sticky notice in the list
|
|
||||||
if ( notice.prevAll( '.not-sticky' ).length == 0 )
|
|
||||||
{
|
|
||||||
// Stop checking once the condition is met
|
|
||||||
clearInterval( topSpotInt );
|
|
||||||
|
|
||||||
// Call the close action after the timeout set in options
|
|
||||||
setTimeout( function ()
|
|
||||||
{
|
|
||||||
removeNotice();
|
|
||||||
}, options.removeTimer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 200 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNotice ()
|
|
||||||
{
|
|
||||||
// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
|
|
||||||
// notice style, we'll just skip the fading out.
|
|
||||||
if ( jQuery.browser.msie && options.usingTransparentPNG )
|
|
||||||
{
|
|
||||||
notice.css( { opacity: 0 } )
|
|
||||||
.animate(
|
|
||||||
{
|
|
||||||
height: '0px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: options.fadeOutSpeed,
|
|
||||||
complete: function ()
|
|
||||||
{
|
|
||||||
notice.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Fade the object out before reducing its height to produce the sliding effect
|
|
||||||
notice.animate(
|
|
||||||
{
|
|
||||||
opacity: '0'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: options.fadeOutSpeed,
|
|
||||||
complete: function ()
|
|
||||||
{
|
|
||||||
notice.animate(
|
|
||||||
{
|
|
||||||
height: '0px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: options.fadeOutSpeed,
|
|
||||||
complete: function ()
|
|
||||||
{
|
|
||||||
notice.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.purr = function ( options )
|
|
||||||
{
|
|
||||||
options = options || {};
|
|
||||||
options.fadeInSpeed = options.fadeInSpeed || 500;
|
|
||||||
options.fadeOutSpeed = options.fadeOutSpeed || 500;
|
|
||||||
options.removeTimer = options.removeTimer || 4000;
|
|
||||||
options.isSticky = options.isSticky || false;
|
|
||||||
options.usingTransparentPNG = options.usingTransparentPNG || false;
|
|
||||||
|
|
||||||
this.each( function()
|
|
||||||
{
|
|
||||||
new $.purr( this, options );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
})( jQuery );
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
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();
|
|
||||||
};
|
|
|
@ -1,41 +0,0 @@
|
||||||
var USERVOICE;
|
|
||||||
if(USERVOICE == undefined) {
|
|
||||||
USERVOICE = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
USERVOICE.load = function (name, id, email, sso_token) {
|
|
||||||
// Include the UserVoice JavaScript SDK (only needed once on a page)
|
|
||||||
UserVoice=window.UserVoice||[];(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/wybK0nSMNuhlWkIKzTyWg.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})();
|
|
||||||
|
|
||||||
//
|
|
||||||
// UserVoice Javascript SDK developer documentation:
|
|
||||||
// https://www.uservoice.com/o/javascript-sdk
|
|
||||||
//
|
|
||||||
|
|
||||||
// Set colors
|
|
||||||
UserVoice.push(['set', {
|
|
||||||
accent_color: '#448dd6',
|
|
||||||
trigger_color: 'white',
|
|
||||||
trigger_background_color: 'rgba(46, 49, 51, 0.6)'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
// Identify the user and pass traits
|
|
||||||
// To enable, replace sample data with actual user traits and uncomment the line
|
|
||||||
if (name) {
|
|
||||||
UserVoice.push(['setSSO', sso_token]);
|
|
||||||
UserVoice.push(['identify', {
|
|
||||||
'email': email, // User’s email address
|
|
||||||
'name': name, // User’s real name
|
|
||||||
'id': id, // Optional: Unique id of the user
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add default trigger to the bottom-left corner of the window:
|
|
||||||
UserVoice.push(['addTrigger', { mode: 'contact', trigger_position: 'bottom-left' }]);
|
|
||||||
|
|
||||||
// Or, use your own custom trigger:
|
|
||||||
//UserVoice.push(['addTrigger', '#barometer_tab', { mode: 'contact' }]);
|
|
||||||
|
|
||||||
// Autoprompt for Satisfaction and SmartVote (only displayed under certain conditions)
|
|
||||||
UserVoice.push(['autoprompt', {}]);
|
|
||||||
};
|
|
|
@ -1,696 +0,0 @@
|
||||||
/* global Metamaps, Backbone, _, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Backbone.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Collaborators
|
|
||||||
* - Metamaps.Creators
|
|
||||||
* - Metamaps.Filter
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Loading
|
|
||||||
* - Metamaps.Map
|
|
||||||
* - Metamaps.Mapper
|
|
||||||
* - Metamaps.Mappers
|
|
||||||
* - Metamaps.Mappings
|
|
||||||
* - Metamaps.Metacodes
|
|
||||||
* - Metamaps.Realtime
|
|
||||||
* - Metamaps.Synapse
|
|
||||||
* - Metamaps.SynapseCard
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.Topic
|
|
||||||
* - Metamaps.TopicCard
|
|
||||||
* - Metamaps.Topics
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Backbone = {}
|
|
||||||
|
|
||||||
Metamaps.Backbone.Map = Backbone.Model.extend({
|
|
||||||
urlRoot: '/maps',
|
|
||||||
blacklist: ['created_at', 'updated_at', 'created_at_clean', 'updated_at_clean', 'user_name', 'contributor_count', 'topic_count', 'synapse_count', 'topics', 'synapses', 'mappings', 'mappers'],
|
|
||||||
toJSON: function (options) {
|
|
||||||
return _.omit(this.attributes, this.blacklist)
|
|
||||||
},
|
|
||||||
save: function (key, val, options) {
|
|
||||||
var attrs
|
|
||||||
|
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
||||||
if (key == null || typeof key === 'object') {
|
|
||||||
attrs = key
|
|
||||||
options = val
|
|
||||||
} else {
|
|
||||||
(attrs = {})[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
var newOptions = options || {}
|
|
||||||
var s = newOptions.success
|
|
||||||
|
|
||||||
newOptions.success = function (model, response, opt) {
|
|
||||||
if (s) s(model, response, opt)
|
|
||||||
model.trigger('saved')
|
|
||||||
}
|
|
||||||
return Backbone.Model.prototype.save.call(this, attrs, newOptions)
|
|
||||||
},
|
|
||||||
initialize: function () {
|
|
||||||
this.on('changeByOther', this.updateView)
|
|
||||||
this.on('saved', this.savedEvent)
|
|
||||||
},
|
|
||||||
savedEvent: function () {
|
|
||||||
Metamaps.Realtime.sendMapChange(this)
|
|
||||||
},
|
|
||||||
authorizeToEdit: function (mapper) {
|
|
||||||
if (mapper && (
|
|
||||||
this.get('permission') === 'commons' ||
|
|
||||||
this.get('collaborator_ids').includes(mapper.get('id')) ||
|
|
||||||
this.get('user_id') === mapper.get('id'))) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authorizePermissionChange: function (mapper) {
|
|
||||||
if (mapper && this.get('user_id') === mapper.get('id')) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getUser: function () {
|
|
||||||
return Metamaps.Mapper.get(this.get('user_id'))
|
|
||||||
},
|
|
||||||
fetchContained: function () {
|
|
||||||
var bb = Metamaps.Backbone
|
|
||||||
var that = this
|
|
||||||
var start = function (data) {
|
|
||||||
that.set('mappers', new bb.MapperCollection(data.mappers))
|
|
||||||
that.set('topics', new bb.TopicCollection(data.topics))
|
|
||||||
that.set('synapses', new bb.SynapseCollection(data.synapses))
|
|
||||||
that.set('mappings', new bb.MappingCollection(data.mappings))
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/maps/' + this.id + '/contains.json',
|
|
||||||
success: start,
|
|
||||||
error: errorFunc,
|
|
||||||
async: false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getTopics: function () {
|
|
||||||
if (!this.get('topics')) {
|
|
||||||
this.fetchContained()
|
|
||||||
}
|
|
||||||
return this.get('topics')
|
|
||||||
},
|
|
||||||
getSynapses: function () {
|
|
||||||
if (!this.get('synapses')) {
|
|
||||||
this.fetchContained()
|
|
||||||
}
|
|
||||||
return this.get('synapses')
|
|
||||||
},
|
|
||||||
getMappings: function () {
|
|
||||||
if (!this.get('mappings')) {
|
|
||||||
this.fetchContained()
|
|
||||||
}
|
|
||||||
return this.get('mappings')
|
|
||||||
},
|
|
||||||
getMappers: function () {
|
|
||||||
if (!this.get('mappers')) {
|
|
||||||
this.fetchContained()
|
|
||||||
}
|
|
||||||
return this.get('mappers')
|
|
||||||
},
|
|
||||||
updateView: function () {
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
var isActiveMap = this.id === map.id
|
|
||||||
if (isActiveMap) {
|
|
||||||
Metamaps.Map.InfoBox.updateNameDescPerm(this.get('name'), this.get('desc'), this.get('permission'))
|
|
||||||
this.updateMapWrapper()
|
|
||||||
// mobile menu
|
|
||||||
$('#header_content').html(this.get('name'))
|
|
||||||
document.title = this.get('name') + ' | Metamaps'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateMapWrapper: function () {
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
var isActiveMap = this.id === map.id
|
|
||||||
var authorized = map && map.authorizeToEdit(Metamaps.Active.Mapper) ? 'canEditMap' : ''
|
|
||||||
var commonsMap = map && map.get('permission') === 'commons' ? 'commonsMap' : ''
|
|
||||||
if (isActiveMap) {
|
|
||||||
$('.wrapper').removeClass('canEditMap commonsMap').addClass(authorized + ' ' + commonsMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Metamaps.Backbone.MapsCollection = Backbone.Collection.extend({
|
|
||||||
model: Metamaps.Backbone.Map,
|
|
||||||
initialize: function (models, options) {
|
|
||||||
this.id = options.id
|
|
||||||
this.sortBy = options.sortBy
|
|
||||||
|
|
||||||
if (options.mapperId) {
|
|
||||||
this.mapperId = options.mapperId
|
|
||||||
}
|
|
||||||
|
|
||||||
// this.page represents the NEXT page to fetch
|
|
||||||
this.page = models.length > 0 ? (models.length < 20 ? 'loadedAll' : 2) : 1
|
|
||||||
},
|
|
||||||
url: function () {
|
|
||||||
if (!this.mapperId) {
|
|
||||||
return '/explore/' + this.id + '.json'
|
|
||||||
} else {
|
|
||||||
return '/explore/mapper/' + this.mapperId + '.json'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
comparator: function (a, b) {
|
|
||||||
a = a.get(this.sortBy)
|
|
||||||
b = b.get(this.sortBy)
|
|
||||||
var temp
|
|
||||||
if (this.sortBy === 'name') {
|
|
||||||
a = a ? a.toLowerCase() : ''
|
|
||||||
b = b ? b.toLowerCase() : ''
|
|
||||||
} else {
|
|
||||||
// this is for updated_at and created_at
|
|
||||||
temp = a
|
|
||||||
a = b
|
|
||||||
b = temp
|
|
||||||
a = (new Date(a)).getTime()
|
|
||||||
b = (new Date(b)).getTime()
|
|
||||||
}
|
|
||||||
return a > b ? 1 : a < b ? -1 : 0
|
|
||||||
},
|
|
||||||
getMaps: function (cb) {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
Metamaps.Loading.show()
|
|
||||||
|
|
||||||
if (this.page !== 'loadedAll') {
|
|
||||||
var numBefore = this.length
|
|
||||||
this.fetch({
|
|
||||||
remove: false,
|
|
||||||
silent: true,
|
|
||||||
data: { page: this.page },
|
|
||||||
success: function (collection, response, options) {
|
|
||||||
// you can pass additional options to the event you trigger here as well
|
|
||||||
if (collection.length - numBefore < 20) {
|
|
||||||
self.page = 'loadedAll'
|
|
||||||
} else {
|
|
||||||
self.page += 1
|
|
||||||
}
|
|
||||||
self.trigger('successOnFetch', cb)
|
|
||||||
},
|
|
||||||
error: function (collection, response, options) {
|
|
||||||
// you can pass additional options to the event you trigger here as well
|
|
||||||
self.trigger('errorOnFetch')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.trigger('successOnFetch', cb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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'],
|
|
||||||
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.MapperCollection = Backbone.Collection.extend({
|
|
||||||
model: Metamaps.Backbone.Mapper,
|
|
||||||
url: '/users'
|
|
||||||
})
|
|
||||||
|
|
||||||
Metamaps.Backbone.init = function () {
|
|
||||||
var self = Metamaps.Backbone
|
|
||||||
|
|
||||||
self.Metacode = Backbone.Model.extend({
|
|
||||||
initialize: function () {
|
|
||||||
var image = new Image()
|
|
||||||
image.crossOrigin = 'Anonymous'
|
|
||||||
image.src = this.get('icon')
|
|
||||||
this.set('image', image)
|
|
||||||
},
|
|
||||||
prepareLiForFilter: function () {
|
|
||||||
var li = ''
|
|
||||||
li += '<li data-id="' + this.id.toString() + '">'
|
|
||||||
li += '<img src="' + this.get('icon') + '" data-id="' + this.id.toString() + '"'
|
|
||||||
li += ' alt="' + this.get('name') + '" />'
|
|
||||||
li += '<p>' + this.get('name').toLowerCase() + '</p></li>'
|
|
||||||
return li
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
self.MetacodeCollection = Backbone.Collection.extend({
|
|
||||||
model: this.Metacode,
|
|
||||||
url: '/metacodes',
|
|
||||||
comparator: function (a, b) {
|
|
||||||
a = a.get('name').toLowerCase()
|
|
||||||
b = b.get('name').toLowerCase()
|
|
||||||
return a > b ? 1 : a < b ? -1 : 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.Topic = Backbone.Model.extend({
|
|
||||||
urlRoot: '/topics',
|
|
||||||
blacklist: ['node', 'created_at', 'updated_at', 'user_name', 'user_image', 'map_count', 'synapse_count'],
|
|
||||||
toJSON: function (options) {
|
|
||||||
return _.omit(this.attributes, this.blacklist)
|
|
||||||
},
|
|
||||||
save: function (key, val, options) {
|
|
||||||
var attrs
|
|
||||||
|
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
||||||
if (key == null || typeof key === 'object') {
|
|
||||||
attrs = key
|
|
||||||
options = val
|
|
||||||
} else {
|
|
||||||
(attrs = {})[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
var newOptions = options || {}
|
|
||||||
var s = newOptions.success
|
|
||||||
|
|
||||||
var permBefore = this.get('permission')
|
|
||||||
|
|
||||||
newOptions.success = function (model, response, opt) {
|
|
||||||
if (s) s(model, response, opt)
|
|
||||||
model.trigger('saved')
|
|
||||||
|
|
||||||
if (permBefore === 'private' && model.get('permission') !== 'private') {
|
|
||||||
model.trigger('noLongerPrivate')
|
|
||||||
}
|
|
||||||
else if (permBefore !== 'private' && model.get('permission') === 'private') {
|
|
||||||
model.trigger('nowPrivate')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Backbone.Model.prototype.save.call(this, attrs, newOptions)
|
|
||||||
},
|
|
||||||
initialize: function () {
|
|
||||||
if (this.isNew()) {
|
|
||||||
this.set({
|
|
||||||
'user_id': Metamaps.Active.Mapper.id,
|
|
||||||
'desc': '',
|
|
||||||
'link': '',
|
|
||||||
'permission': Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.on('changeByOther', this.updateCardView)
|
|
||||||
this.on('change', this.updateNodeView)
|
|
||||||
this.on('saved', this.savedEvent)
|
|
||||||
this.on('nowPrivate', function () {
|
|
||||||
var removeTopicData = {
|
|
||||||
mappableid: this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.JIT.events.removeTopic, [removeTopicData])
|
|
||||||
})
|
|
||||||
this.on('noLongerPrivate', function () {
|
|
||||||
var newTopicData = {
|
|
||||||
mappingid: this.getMapping().id,
|
|
||||||
mappableid: this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData])
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('change:metacode_id', Metamaps.Filter.checkMetacodes, this)
|
|
||||||
},
|
|
||||||
authorizeToEdit: function (mapper) {
|
|
||||||
if (mapper &&
|
|
||||||
(this.get('calculated_permission') === 'commons' ||
|
|
||||||
this.get('collaborator_ids').includes(mapper.get('id')) ||
|
|
||||||
this.get('user_id') === mapper.get('id'))) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authorizePermissionChange: function (mapper) {
|
|
||||||
if (mapper && this.get('user_id') === mapper.get('id')) return true
|
|
||||||
else return false
|
|
||||||
},
|
|
||||||
getDate: function () {},
|
|
||||||
getMetacode: function () {
|
|
||||||
return Metamaps.Metacodes.get(this.get('metacode_id'))
|
|
||||||
},
|
|
||||||
getMapping: function () {
|
|
||||||
if (!Metamaps.Active.Map) return false
|
|
||||||
|
|
||||||
return Metamaps.Mappings.findWhere({
|
|
||||||
map_id: Metamaps.Active.Map.id,
|
|
||||||
mappable_type: 'Topic',
|
|
||||||
mappable_id: this.isNew() ? this.cid : this.id
|
|
||||||
})
|
|
||||||
},
|
|
||||||
createNode: function () {
|
|
||||||
var mapping
|
|
||||||
var node = {
|
|
||||||
adjacencies: [],
|
|
||||||
id: this.isNew() ? this.cid : this.id,
|
|
||||||
name: this.get('name')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
mapping = this.getMapping()
|
|
||||||
node.data = {
|
|
||||||
$mapping: null,
|
|
||||||
$mappingID: mapping.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
|
||||||
},
|
|
||||||
updateNode: function () {
|
|
||||||
var mapping
|
|
||||||
var node = this.get('node')
|
|
||||||
node.setData('topic', this)
|
|
||||||
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
mapping = this.getMapping()
|
|
||||||
node.setData('mapping', mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
|
||||||
},
|
|
||||||
savedEvent: function () {
|
|
||||||
Metamaps.Realtime.sendTopicChange(this)
|
|
||||||
},
|
|
||||||
updateViews: function () {
|
|
||||||
var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic
|
|
||||||
var node = this.get('node')
|
|
||||||
// update topic card, if this topic is the one open there
|
|
||||||
if (onPageWithTopicCard && this == Metamaps.TopicCard.openTopicCard) {
|
|
||||||
Metamaps.TopicCard.showCard(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the node on the map
|
|
||||||
if (onPageWithTopicCard && node) {
|
|
||||||
node.name = this.get('name')
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateCardView: function () {
|
|
||||||
var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic
|
|
||||||
var node = this.get('node')
|
|
||||||
// update topic card, if this topic is the one open there
|
|
||||||
if (onPageWithTopicCard && this == Metamaps.TopicCard.openTopicCard) {
|
|
||||||
Metamaps.TopicCard.showCard(node)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateNodeView: function () {
|
|
||||||
var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic
|
|
||||||
var node = this.get('node')
|
|
||||||
|
|
||||||
// update the node on the map
|
|
||||||
if (onPageWithTopicCard && node) {
|
|
||||||
node.name = this.get('name')
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.TopicCollection = Backbone.Collection.extend({
|
|
||||||
model: self.Topic,
|
|
||||||
url: '/topics'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.Synapse = Backbone.Model.extend({
|
|
||||||
urlRoot: '/synapses',
|
|
||||||
blacklist: ['edge', 'created_at', 'updated_at'],
|
|
||||||
toJSON: function (options) {
|
|
||||||
return _.omit(this.attributes, this.blacklist)
|
|
||||||
},
|
|
||||||
save: function (key, val, options) {
|
|
||||||
var attrs
|
|
||||||
|
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
||||||
if (key == null || typeof key === 'object') {
|
|
||||||
attrs = key
|
|
||||||
options = val
|
|
||||||
} else {
|
|
||||||
(attrs = {})[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
var newOptions = options || {}
|
|
||||||
var s = newOptions.success
|
|
||||||
|
|
||||||
var permBefore = this.get('permission')
|
|
||||||
|
|
||||||
newOptions.success = function (model, response, opt) {
|
|
||||||
if (s) s(model, response, opt)
|
|
||||||
model.trigger('saved')
|
|
||||||
|
|
||||||
if (permBefore === 'private' && model.get('permission') !== 'private') {
|
|
||||||
model.trigger('noLongerPrivate')
|
|
||||||
}
|
|
||||||
else if (permBefore !== 'private' && model.get('permission') === 'private') {
|
|
||||||
model.trigger('nowPrivate')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Backbone.Model.prototype.save.call(this, attrs, newOptions)
|
|
||||||
},
|
|
||||||
initialize: function () {
|
|
||||||
if (this.isNew()) {
|
|
||||||
this.set({
|
|
||||||
'user_id': Metamaps.Active.Mapper.id,
|
|
||||||
'permission': Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons',
|
|
||||||
'category': 'from-to'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.on('changeByOther', this.updateCardView)
|
|
||||||
this.on('change', this.updateEdgeView)
|
|
||||||
this.on('saved', this.savedEvent)
|
|
||||||
this.on('noLongerPrivate', function () {
|
|
||||||
var newSynapseData = {
|
|
||||||
mappingid: this.getMapping().id,
|
|
||||||
mappableid: this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData])
|
|
||||||
})
|
|
||||||
this.on('nowPrivate', function () {
|
|
||||||
$(document).trigger(Metamaps.JIT.events.removeSynapse, [{
|
|
||||||
mappableid: this.id
|
|
||||||
}])
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('change:desc', Metamaps.Filter.checkSynapses, this)
|
|
||||||
},
|
|
||||||
prepareLiForFilter: function () {
|
|
||||||
var li = ''
|
|
||||||
li += '<li data-id="' + this.get('desc') + '">'
|
|
||||||
li += '<img src="' + Metamaps.Erb['synapse16.png'] + '"'
|
|
||||||
li += ' alt="synapse icon" />'
|
|
||||||
li += '<p>' + this.get('desc') + '</p></li>'
|
|
||||||
return li
|
|
||||||
},
|
|
||||||
authorizeToEdit: function (mapper) {
|
|
||||||
if (mapper && (this.get('calculated_permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
|
|
||||||
else return false
|
|
||||||
},
|
|
||||||
authorizePermissionChange: function (mapper) {
|
|
||||||
if (mapper && this.get('user_id') === mapper.get('id')) return true
|
|
||||||
else return false
|
|
||||||
},
|
|
||||||
getTopic1: function () {
|
|
||||||
return Metamaps.Topics.get(this.get('node1_id'))
|
|
||||||
},
|
|
||||||
getTopic2: function () {
|
|
||||||
return Metamaps.Topics.get(this.get('node2_id'))
|
|
||||||
},
|
|
||||||
getDirection: function () {
|
|
||||||
var t1 = this.getTopic1(),
|
|
||||||
t2 = this.getTopic2()
|
|
||||||
|
|
||||||
return t1 && t2 ? [
|
|
||||||
t1.get('node').id,
|
|
||||||
t2.get('node').id
|
|
||||||
] : false
|
|
||||||
},
|
|
||||||
getMapping: function () {
|
|
||||||
if (!Metamaps.Active.Map) return false
|
|
||||||
|
|
||||||
return Metamaps.Mappings.findWhere({
|
|
||||||
map_id: Metamaps.Active.Map.id,
|
|
||||||
mappable_type: 'Synapse',
|
|
||||||
mappable_id: this.isNew() ? this.cid : this.id
|
|
||||||
})
|
|
||||||
},
|
|
||||||
createEdge: function (providedMapping) {
|
|
||||||
var mapping, mappingID
|
|
||||||
var synapseID = this.isNew() ? this.cid : this.id
|
|
||||||
|
|
||||||
var edge = {
|
|
||||||
nodeFrom: this.get('node1_id'),
|
|
||||||
nodeTo: this.get('node2_id'),
|
|
||||||
data: {
|
|
||||||
$synapses: [],
|
|
||||||
$synapseIDs: [synapseID],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
mapping = providedMapping || this.getMapping()
|
|
||||||
mappingID = mapping.isNew() ? mapping.cid : mapping.id
|
|
||||||
edge.data.$mappings = []
|
|
||||||
edge.data.$mappingIDs = [mappingID]
|
|
||||||
}
|
|
||||||
|
|
||||||
return edge
|
|
||||||
},
|
|
||||||
updateEdge: function () {
|
|
||||||
var mapping
|
|
||||||
var edge = this.get('edge')
|
|
||||||
edge.getData('synapses').push(this)
|
|
||||||
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
mapping = this.getMapping()
|
|
||||||
edge.getData('mappings').push(mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
return edge
|
|
||||||
},
|
|
||||||
savedEvent: function () {
|
|
||||||
Metamaps.Realtime.sendSynapseChange(this)
|
|
||||||
},
|
|
||||||
updateViews: function () {
|
|
||||||
this.updateCardView()
|
|
||||||
this.updateEdgeView()
|
|
||||||
},
|
|
||||||
updateCardView: function () {
|
|
||||||
var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic
|
|
||||||
var edge = this.get('edge')
|
|
||||||
|
|
||||||
// update synapse card, if this synapse is the one open there
|
|
||||||
if (onPageWithSynapseCard && edge == Metamaps.SynapseCard.openSynapseCard) {
|
|
||||||
Metamaps.SynapseCard.showCard(edge)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateEdgeView: function () {
|
|
||||||
var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic
|
|
||||||
var edge = this.get('edge')
|
|
||||||
|
|
||||||
// update the edge on the map
|
|
||||||
if (onPageWithSynapseCard && edge) {
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.SynapseCollection = Backbone.Collection.extend({
|
|
||||||
model: self.Synapse,
|
|
||||||
url: '/synapses'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.Mapping = Backbone.Model.extend({
|
|
||||||
urlRoot: '/mappings',
|
|
||||||
blacklist: ['created_at', 'updated_at'],
|
|
||||||
toJSON: function (options) {
|
|
||||||
return _.omit(this.attributes, this.blacklist)
|
|
||||||
},
|
|
||||||
initialize: function () {
|
|
||||||
if (this.isNew()) {
|
|
||||||
this.set({
|
|
||||||
'user_id': Metamaps.Active.Mapper.id,
|
|
||||||
'map_id': Metamaps.Active.Map ? Metamaps.Active.Map.id : null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getMap: function () {
|
|
||||||
return Metamaps.Map.get(this.get('map_id'))
|
|
||||||
},
|
|
||||||
getTopic: function () {
|
|
||||||
if (this.get('mappable_type') === 'Topic') return Metamaps.Topic.get(this.get('mappable_id'))
|
|
||||||
else return false
|
|
||||||
},
|
|
||||||
getSynapse: function () {
|
|
||||||
if (this.get('mappable_type') === 'Synapse') return Metamaps.Synapse.get(this.get('mappable_id'))
|
|
||||||
else return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.MappingCollection = Backbone.Collection.extend({
|
|
||||||
model: self.Mapping,
|
|
||||||
url: '/mappings'
|
|
||||||
})
|
|
||||||
|
|
||||||
Metamaps.Metacodes = Metamaps.Metacodes ? new self.MetacodeCollection(Metamaps.Metacodes) : new self.MetacodeCollection()
|
|
||||||
|
|
||||||
Metamaps.Topics = Metamaps.Topics ? new self.TopicCollection(Metamaps.Topics) : new self.TopicCollection()
|
|
||||||
|
|
||||||
Metamaps.Synapses = Metamaps.Synapses ? new self.SynapseCollection(Metamaps.Synapses) : new self.SynapseCollection()
|
|
||||||
|
|
||||||
Metamaps.Mappers = Metamaps.Mappers ? new self.MapperCollection(Metamaps.Mappers) : new self.MapperCollection()
|
|
||||||
|
|
||||||
Metamaps.Collaborators = Metamaps.Collaborators ? new self.MapperCollection(Metamaps.Collaborators) : new self.MapperCollection()
|
|
||||||
|
|
||||||
// this is for topic view
|
|
||||||
Metamaps.Creators = Metamaps.Creators ? new self.MapperCollection(Metamaps.Creators) : new self.MapperCollection()
|
|
||||||
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
Metamaps.Mappings = Metamaps.Mappings ? new self.MappingCollection(Metamaps.Mappings) : new self.MappingCollection()
|
|
||||||
|
|
||||||
Metamaps.Active.Map = new self.Map(Metamaps.Active.Map)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Metamaps.Active.Topic) Metamaps.Active.Topic = new self.Topic(Metamaps.Active.Topic)
|
|
||||||
|
|
||||||
// attach collection event listeners
|
|
||||||
self.attachCollectionEvents = function () {
|
|
||||||
Metamaps.Topics.on('add remove', function (topic) {
|
|
||||||
Metamaps.Map.InfoBox.updateNumbers()
|
|
||||||
Metamaps.Filter.checkMetacodes()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
})
|
|
||||||
|
|
||||||
Metamaps.Synapses.on('add remove', function (synapse) {
|
|
||||||
Metamaps.Map.InfoBox.updateNumbers()
|
|
||||||
Metamaps.Filter.checkSynapses()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
Metamaps.Mappings.on('add remove', function (mapping) {
|
|
||||||
Metamaps.Map.InfoBox.updateNumbers()
|
|
||||||
Metamaps.Filter.checkSynapses()
|
|
||||||
Metamaps.Filter.checkMetacodes()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.attachCollectionEvents()
|
|
||||||
}; // end Metamaps.Backbone.init
|
|
|
@ -1,451 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Control.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Control
|
|
||||||
* - Metamaps.Filter
|
|
||||||
* - Metamaps.GlobalUI
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Mappings
|
|
||||||
* - Metamaps.Metacodes
|
|
||||||
* - Metamaps.Mouse
|
|
||||||
* - Metamaps.Selected
|
|
||||||
* - Metamaps.Settings
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.Topics
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Control = {
|
|
||||||
init: function () {},
|
|
||||||
selectNode: function (node, e) {
|
|
||||||
var filtered = node.getData('alpha') === 0
|
|
||||||
|
|
||||||
if (filtered || Metamaps.Selected.Nodes.indexOf(node) != -1) return
|
|
||||||
node.selected = true
|
|
||||||
node.setData('dim', 30, 'current')
|
|
||||||
Metamaps.Selected.Nodes.push(node)
|
|
||||||
},
|
|
||||||
deselectAllNodes: function () {
|
|
||||||
var l = Metamaps.Selected.Nodes.length
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
var node = Metamaps.Selected.Nodes[i]
|
|
||||||
Metamaps.Control.deselectNode(node)
|
|
||||||
}
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
},
|
|
||||||
deselectNode: function (node) {
|
|
||||||
delete node.selected
|
|
||||||
node.setData('dim', 25, 'current')
|
|
||||||
|
|
||||||
// remove the node
|
|
||||||
Metamaps.Selected.Nodes.splice(
|
|
||||||
Metamaps.Selected.Nodes.indexOf(node), 1)
|
|
||||||
},
|
|
||||||
deleteSelected: function () {
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var n = Metamaps.Selected.Nodes.length
|
|
||||||
var e = Metamaps.Selected.Edges.length
|
|
||||||
var ntext = n == 1 ? '1 topic' : n + ' topics'
|
|
||||||
var etext = e == 1 ? '1 synapse' : e + ' synapses'
|
|
||||||
var text = 'You have ' + ntext + ' and ' + etext + ' selected. '
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = confirm(text + 'Are you sure you want to permanently delete them all? This will remove them from all maps they appear on.')
|
|
||||||
if (r == true) {
|
|
||||||
Metamaps.Control.deleteSelectedEdges()
|
|
||||||
Metamaps.Control.deleteSelectedNodes()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteSelectedNodes: function () { // refers to deleting topics permanently
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var l = Metamaps.Selected.Nodes.length
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
var node = Metamaps.Selected.Nodes[i]
|
|
||||||
Metamaps.Control.deleteNode(node.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteNode: function (nodeid) { // refers to deleting topics permanently
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid)
|
|
||||||
var topic = node.getData('topic')
|
|
||||||
|
|
||||||
var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin')
|
|
||||||
if (permToDelete) {
|
|
||||||
var mappableid = topic.id
|
|
||||||
var mapping = node.getData('mapping')
|
|
||||||
topic.destroy()
|
|
||||||
Metamaps.Mappings.remove(mapping)
|
|
||||||
$(document).trigger(Metamaps.JIT.events.deleteTopic, [{
|
|
||||||
mappableid: mappableid
|
|
||||||
}])
|
|
||||||
Metamaps.Control.hideNode(nodeid)
|
|
||||||
} else {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Only topics you created can be deleted')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeSelectedNodes: function () { // refers to removing topics permanently from a map
|
|
||||||
if (Metamaps.Active.Topic) {
|
|
||||||
// hideNode will handle synapses as well
|
|
||||||
var nodeids = _.map(Metamaps.Selected.Nodes, function(node) {
|
|
||||||
return node.id
|
|
||||||
})
|
|
||||||
_.each(nodeids, function(nodeid) {
|
|
||||||
if (Metamaps.Active.Topic.id !== nodeid) {
|
|
||||||
Metamaps.Topics.remove(nodeid)
|
|
||||||
Metamaps.Control.hideNode(nodeid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var l = Metamaps.Selected.Nodes.length,
|
|
||||||
i,
|
|
||||||
node,
|
|
||||||
authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = l - 1; i >= 0; i -= 1) {
|
|
||||||
node = Metamaps.Selected.Nodes[i]
|
|
||||||
Metamaps.Control.removeNode(node.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeNode: function (nodeid) { // refers to removing topics permanently from a map
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var topic = node.getData('topic')
|
|
||||||
var mappableid = topic.id
|
|
||||||
var mapping = node.getData('mapping')
|
|
||||||
mapping.destroy()
|
|
||||||
Metamaps.Topics.remove(topic)
|
|
||||||
$(document).trigger(Metamaps.JIT.events.removeTopic, [{
|
|
||||||
mappableid: mappableid
|
|
||||||
}])
|
|
||||||
Metamaps.Control.hideNode(nodeid)
|
|
||||||
},
|
|
||||||
hideSelectedNodes: function () {
|
|
||||||
var l = Metamaps.Selected.Nodes.length,
|
|
||||||
i,
|
|
||||||
node
|
|
||||||
|
|
||||||
for (i = l - 1; i >= 0; i -= 1) {
|
|
||||||
node = Metamaps.Selected.Nodes[i]
|
|
||||||
Metamaps.Control.hideNode(node.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hideNode: function (nodeid) {
|
|
||||||
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid)
|
|
||||||
var graph = Metamaps.Visualize.mGraph
|
|
||||||
|
|
||||||
Metamaps.Control.deselectNode(node)
|
|
||||||
|
|
||||||
node.setData('alpha', 0, 'end')
|
|
||||||
node.eachAdjacency(function (adj) {
|
|
||||||
adj.setData('alpha', 0, 'end')
|
|
||||||
})
|
|
||||||
Metamaps.Visualize.mGraph.fx.animate({
|
|
||||||
modes: ['node-property:alpha',
|
|
||||||
'edge-property:alpha'
|
|
||||||
],
|
|
||||||
duration: 500
|
|
||||||
})
|
|
||||||
setTimeout(function () {
|
|
||||||
if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph"
|
|
||||||
var newroot = _.find(graph.graph.nodes, function (n) { return n.id !== nodeid; })
|
|
||||||
graph.root = newroot ? newroot.id : null
|
|
||||||
}
|
|
||||||
Metamaps.Visualize.mGraph.graph.removeNode(nodeid)
|
|
||||||
}, 500)
|
|
||||||
Metamaps.Filter.checkMetacodes()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
},
|
|
||||||
selectEdge: function (edge) {
|
|
||||||
var filtered = edge.getData('alpha') === 0; // don't select if the edge is filtered
|
|
||||||
|
|
||||||
if (filtered || Metamaps.Selected.Edges.indexOf(edge) != -1) return
|
|
||||||
|
|
||||||
var width = Metamaps.Mouse.edgeHoveringOver === edge ? 4 : 2
|
|
||||||
edge.setDataset('current', {
|
|
||||||
showDesc: true,
|
|
||||||
lineWidth: width,
|
|
||||||
color: Metamaps.Settings.colors.synapses.selected
|
|
||||||
})
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
|
|
||||||
Metamaps.Selected.Edges.push(edge)
|
|
||||||
},
|
|
||||||
deselectAllEdges: function () {
|
|
||||||
var l = Metamaps.Selected.Edges.length
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
var edge = Metamaps.Selected.Edges[i]
|
|
||||||
Metamaps.Control.deselectEdge(edge)
|
|
||||||
}
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
},
|
|
||||||
deselectEdge: function (edge) {
|
|
||||||
edge.setData('showDesc', false, 'current')
|
|
||||||
|
|
||||||
edge.setDataset('current', {
|
|
||||||
lineWidth: 2,
|
|
||||||
color: Metamaps.Settings.colors.synapses.normal
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Metamaps.Mouse.edgeHoveringOver == edge) {
|
|
||||||
edge.setDataset('current', {
|
|
||||||
showDesc: true,
|
|
||||||
lineWidth: 4
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
|
|
||||||
// remove the edge
|
|
||||||
Metamaps.Selected.Edges.splice(
|
|
||||||
Metamaps.Selected.Edges.indexOf(edge), 1)
|
|
||||||
},
|
|
||||||
deleteSelectedEdges: function () { // refers to deleting topics permanently
|
|
||||||
var edge,
|
|
||||||
l = Metamaps.Selected.Edges.length
|
|
||||||
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
edge = Metamaps.Selected.Edges[i]
|
|
||||||
Metamaps.Control.deleteEdge(edge)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteEdge: function (edge) {
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0
|
|
||||||
|
|
||||||
var synapse = edge.getData('synapses')[index]
|
|
||||||
var mapping = edge.getData('mappings')[index]
|
|
||||||
|
|
||||||
var permToDelete = Metamaps.Active.Mapper.id === synapse.get('user_id') || Metamaps.Active.Mapper.get('admin')
|
|
||||||
if (permToDelete) {
|
|
||||||
if (edge.getData('synapses').length - 1 === 0) {
|
|
||||||
Metamaps.Control.hideEdge(edge)
|
|
||||||
}
|
|
||||||
var mappableid = synapse.id
|
|
||||||
synapse.destroy()
|
|
||||||
|
|
||||||
// the server will destroy the mapping, we just need to remove it here
|
|
||||||
Metamaps.Mappings.remove(mapping)
|
|
||||||
edge.getData('mappings').splice(index, 1)
|
|
||||||
edge.getData('synapses').splice(index, 1)
|
|
||||||
if (edge.getData('displayIndex')) {
|
|
||||||
delete edge.data.$displayIndex
|
|
||||||
}
|
|
||||||
$(document).trigger(Metamaps.JIT.events.deleteSynapse, [{
|
|
||||||
mappableid: mappableid
|
|
||||||
}])
|
|
||||||
} else {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeSelectedEdges: function () {
|
|
||||||
// Topic view is handled by removeSelectedNodes
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var l = Metamaps.Selected.Edges.length,
|
|
||||||
i,
|
|
||||||
edge
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = l - 1; i >= 0; i -= 1) {
|
|
||||||
edge = Metamaps.Selected.Edges[i]
|
|
||||||
Metamaps.Control.removeEdge(edge)
|
|
||||||
}
|
|
||||||
Metamaps.Selected.Edges = [ ]
|
|
||||||
},
|
|
||||||
removeEdge: function (edge) {
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edge.getData('mappings').length - 1 === 0) {
|
|
||||||
Metamaps.Control.hideEdge(edge)
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0
|
|
||||||
|
|
||||||
var synapse = edge.getData('synapses')[index]
|
|
||||||
var mapping = edge.getData('mappings')[index]
|
|
||||||
var mappableid = synapse.id
|
|
||||||
mapping.destroy()
|
|
||||||
|
|
||||||
Metamaps.Synapses.remove(synapse)
|
|
||||||
|
|
||||||
edge.getData('mappings').splice(index, 1)
|
|
||||||
edge.getData('synapses').splice(index, 1)
|
|
||||||
if (edge.getData('displayIndex')) {
|
|
||||||
delete edge.data.$displayIndex
|
|
||||||
}
|
|
||||||
$(document).trigger(Metamaps.JIT.events.removeSynapse, [{
|
|
||||||
mappableid: mappableid
|
|
||||||
}])
|
|
||||||
},
|
|
||||||
hideSelectedEdges: function () {
|
|
||||||
var edge,
|
|
||||||
l = Metamaps.Selected.Edges.length,
|
|
||||||
i
|
|
||||||
for (i = l - 1; i >= 0; i -= 1) {
|
|
||||||
edge = Metamaps.Selected.Edges[i]
|
|
||||||
Metamaps.Control.hideEdge(edge)
|
|
||||||
}
|
|
||||||
Metamaps.Selected.Edges = [ ]
|
|
||||||
},
|
|
||||||
hideEdge: function (edge) {
|
|
||||||
var from = edge.nodeFrom.id
|
|
||||||
var to = edge.nodeTo.id
|
|
||||||
edge.setData('alpha', 0, 'end')
|
|
||||||
Metamaps.Control.deselectEdge(edge)
|
|
||||||
Metamaps.Visualize.mGraph.fx.animate({
|
|
||||||
modes: ['edge-property:alpha'],
|
|
||||||
duration: 500
|
|
||||||
})
|
|
||||||
setTimeout(function () {
|
|
||||||
Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to)
|
|
||||||
}, 500)
|
|
||||||
Metamaps.Filter.checkSynapses()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
},
|
|
||||||
updateSelectedPermissions: function (permission) {
|
|
||||||
var edge, synapse, node, topic
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.notifyUser('Working...')
|
|
||||||
|
|
||||||
// variables to keep track of how many nodes and synapses you had the ability to change the permission of
|
|
||||||
var nCount = 0,
|
|
||||||
sCount = 0
|
|
||||||
|
|
||||||
// change the permission of the selected synapses, if logged in user is the original creator
|
|
||||||
var l = Metamaps.Selected.Edges.length
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
edge = Metamaps.Selected.Edges[i]
|
|
||||||
synapse = edge.getData('synapses')[0]
|
|
||||||
|
|
||||||
if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
|
|
||||||
synapse.save({
|
|
||||||
permission: permission
|
|
||||||
})
|
|
||||||
sCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// change the permission of the selected topics, if logged in user is the original creator
|
|
||||||
var l = Metamaps.Selected.Nodes.length
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
node = Metamaps.Selected.Nodes[i]
|
|
||||||
topic = node.getData('topic')
|
|
||||||
|
|
||||||
if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) {
|
|
||||||
topic.save({
|
|
||||||
permission: permission
|
|
||||||
})
|
|
||||||
nCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and ')
|
|
||||||
var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses')
|
|
||||||
|
|
||||||
var message = nString + sString + ' you created updated to ' + permission
|
|
||||||
Metamaps.GlobalUI.notifyUser(message)
|
|
||||||
},
|
|
||||||
updateSelectedMetacodes: function (metacode_id) {
|
|
||||||
var node, topic
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.notifyUser('Working...')
|
|
||||||
|
|
||||||
var metacode = Metamaps.Metacodes.get(metacode_id)
|
|
||||||
|
|
||||||
// variables to keep track of how many nodes and synapses you had the ability to change the permission of
|
|
||||||
var nCount = 0
|
|
||||||
|
|
||||||
// change the permission of the selected topics, if logged in user is the original creator
|
|
||||||
var l = Metamaps.Selected.Nodes.length
|
|
||||||
for (var i = l - 1; i >= 0; i -= 1) {
|
|
||||||
node = Metamaps.Selected.Nodes[i]
|
|
||||||
topic = node.getData('topic')
|
|
||||||
|
|
||||||
if (topic.authorizeToEdit(Metamaps.Active.Mapper)) {
|
|
||||||
topic.save({
|
|
||||||
'metacode_id': metacode_id
|
|
||||||
})
|
|
||||||
nCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var nString = nCount == 1 ? (nCount.toString() + ' topic') : (nCount.toString() + ' topics')
|
|
||||||
|
|
||||||
var message = nString + ' you can edit updated to ' + metacode.get('name')
|
|
||||||
Metamaps.GlobalUI.notifyUser(message)
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
},
|
|
||||||
}; // end Metamaps.Control
|
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Metamaps.Debug.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies: none!
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Debug = function () {
|
|
||||||
console.debug(Metamaps)
|
|
||||||
console.debug('Metamaps Version: ' + Metamaps.VERSION)
|
|
||||||
}
|
|
||||||
Metamaps.debug = function () {
|
|
||||||
Metamaps.Debug()
|
|
||||||
}
|
|
|
@ -1,717 +0,0 @@
|
||||||
var Metamaps = window.Metamaps || {}; // this variable declaration defines a Javascript object that will contain all the variables and functions used by us, broken down into 'sub-modules' that look something like this
|
|
||||||
/*
|
|
||||||
|
|
||||||
* unless you are on a page with the Javascript InfoVis Toolkit (Topic or Map) the only section in the metamaps
|
|
||||||
* object will be these
|
|
||||||
GlobalUI
|
|
||||||
Active
|
|
||||||
Maps
|
|
||||||
Mappers
|
|
||||||
Backbone
|
|
||||||
|
|
||||||
* all these get added when you are on a page with the Javascript Infovis Toolkit
|
|
||||||
Settings
|
|
||||||
Touch
|
|
||||||
Mouse
|
|
||||||
Selected
|
|
||||||
Metacodes
|
|
||||||
Topics
|
|
||||||
Synapses
|
|
||||||
Mappings
|
|
||||||
Create
|
|
||||||
TopicCard
|
|
||||||
SynapseCard
|
|
||||||
Visualize
|
|
||||||
Util
|
|
||||||
Realtime
|
|
||||||
Control
|
|
||||||
Filter
|
|
||||||
Listeners
|
|
||||||
Organize
|
|
||||||
Map
|
|
||||||
Mapper
|
|
||||||
Topic
|
|
||||||
Synapse
|
|
||||||
JIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Active = {
|
|
||||||
Map: null,
|
|
||||||
Topic: null,
|
|
||||||
Mapper: null
|
|
||||||
};
|
|
||||||
Metamaps.Maps = {};
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
// initialize all the modules
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// load whichever page you are on
|
|
||||||
if (Metamaps.currentSection === "explore") {
|
|
||||||
var capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1)
|
|
||||||
|
|
||||||
Metamaps.Views.exploreMaps.setCollection( Metamaps.Maps[capitalize] )
|
|
||||||
if (Metamaps.currentPage === "mapper") {
|
|
||||||
Metamaps.Views.exploreMaps.fetchUserThenRender()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Metamaps.Views.exploreMaps.render()
|
|
||||||
}
|
|
||||||
Metamaps.GlobalUI.showDiv('#explore')
|
|
||||||
}
|
|
||||||
else if (Metamaps.currentSection === "" && Metamaps.Active.Mapper) {
|
|
||||||
Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps.Active)
|
|
||||||
Metamaps.Views.exploreMaps.render()
|
|
||||||
Metamaps.GlobalUI.showDiv('#explore')
|
|
||||||
}
|
|
||||||
else if (Metamaps.Active.Map || Metamaps.Active.Topic) {
|
|
||||||
Metamaps.Loading.show()
|
|
||||||
Metamaps.JIT.prepareVizData()
|
|
||||||
Metamaps.GlobalUI.showDiv('#infovis')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Metamaps.GlobalUI = {
|
|
||||||
notifyTimeout: null,
|
|
||||||
lightbox: null,
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.GlobalUI;
|
|
||||||
|
|
||||||
self.Search.init();
|
|
||||||
self.CreateMap.init();
|
|
||||||
self.Account.init();
|
|
||||||
|
|
||||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
|
||||||
|
|
||||||
//bind lightbox clicks
|
|
||||||
$('.openLightbox').click(function (event) {
|
|
||||||
self.openLightbox($(this).attr('data-open'));
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#lightbox_screen, #lightbox_close').click(self.closeLightbox);
|
|
||||||
|
|
||||||
// initialize global backbone models and collections
|
|
||||||
if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper);
|
|
||||||
|
|
||||||
var myCollection = Metamaps.Maps.Mine ? Metamaps.Maps.Mine : [];
|
|
||||||
var sharedCollection = Metamaps.Maps.Shared ? Metamaps.Maps.Shared : [];
|
|
||||||
var starredCollection = Metamaps.Maps.Starred ? Metamaps.Maps.Starred : [];
|
|
||||||
var mapperCollection = [];
|
|
||||||
var mapperOptionsObj = {id: 'mapper', sortBy: 'updated_at' };
|
|
||||||
if (Metamaps.Maps.Mapper) {
|
|
||||||
mapperCollection = Metamaps.Maps.Mapper.models;
|
|
||||||
mapperOptionsObj.mapperId = Metamaps.Maps.Mapper.id;
|
|
||||||
}
|
|
||||||
var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : [];
|
|
||||||
var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : [];
|
|
||||||
Metamaps.Maps.Mine = new Metamaps.Backbone.MapsCollection(myCollection, {id: 'mine', sortBy: 'updated_at' });
|
|
||||||
Metamaps.Maps.Shared = new Metamaps.Backbone.MapsCollection(sharedCollection, {id: 'shared', sortBy: 'updated_at' });
|
|
||||||
Metamaps.Maps.Starred = new Metamaps.Backbone.MapsCollection(starredCollection, {id: 'starred', sortBy: 'updated_at' });
|
|
||||||
// 'Mapper' refers to another mapper
|
|
||||||
Metamaps.Maps.Mapper = new Metamaps.Backbone.MapsCollection(mapperCollection, mapperOptionsObj);
|
|
||||||
Metamaps.Maps.Featured = new Metamaps.Backbone.MapsCollection(featuredCollection, {id: 'featured', sortBy: 'updated_at' });
|
|
||||||
Metamaps.Maps.Active = new Metamaps.Backbone.MapsCollection(activeCollection, {id: 'active', sortBy: 'updated_at' });
|
|
||||||
},
|
|
||||||
showDiv: function (selector) {
|
|
||||||
$(selector).show()
|
|
||||||
$(selector).animate({
|
|
||||||
opacity: 1
|
|
||||||
}, 200, 'easeOutCubic')
|
|
||||||
},
|
|
||||||
hideDiv: function (selector) {
|
|
||||||
$(selector).animate({
|
|
||||||
opacity: 0
|
|
||||||
}, 200, 'easeInCubic', function () { $(this).hide() })
|
|
||||||
},
|
|
||||||
openLightbox: function (which) {
|
|
||||||
var self = Metamaps.GlobalUI;
|
|
||||||
|
|
||||||
$('.lightboxContent').hide();
|
|
||||||
$('#' + which).show();
|
|
||||||
|
|
||||||
self.lightbox = which;
|
|
||||||
|
|
||||||
$('#lightbox_overlay').show();
|
|
||||||
|
|
||||||
var heightOfContent = '-' + ($('#lightbox_main').height() / 2) + 'px';
|
|
||||||
// animate the content in from the bottom
|
|
||||||
$('#lightbox_main').animate({
|
|
||||||
'top': '50%',
|
|
||||||
'margin-top': heightOfContent
|
|
||||||
}, 200, 'easeOutCubic');
|
|
||||||
|
|
||||||
// fade the black overlay in
|
|
||||||
$('#lightbox_screen').animate({
|
|
||||||
'opacity': '0.42'
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
if (which == "switchMetacodes") {
|
|
||||||
Metamaps.Create.isSwitchingSet = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
closeLightbox: function (event) {
|
|
||||||
var self = Metamaps.GlobalUI;
|
|
||||||
|
|
||||||
if (event) event.preventDefault();
|
|
||||||
|
|
||||||
// animate the lightbox content offscreen
|
|
||||||
$('#lightbox_main').animate({
|
|
||||||
'top': '100%',
|
|
||||||
'margin-top': '0'
|
|
||||||
}, 200, 'easeInCubic');
|
|
||||||
|
|
||||||
// fade the black overlay out
|
|
||||||
$('#lightbox_screen').animate({
|
|
||||||
'opacity': '0.0'
|
|
||||||
}, 200, function () {
|
|
||||||
$('#lightbox_overlay').hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (self.lightbox === 'forkmap') Metamaps.GlobalUI.CreateMap.reset('fork_map');
|
|
||||||
if (self.lightbox === 'newmap') Metamaps.GlobalUI.CreateMap.reset('new_map');
|
|
||||||
if (Metamaps.Create && Metamaps.Create.isSwitchingSet) {
|
|
||||||
Metamaps.Create.cancelMetacodeSetSwitch();
|
|
||||||
}
|
|
||||||
self.lightbox = null;
|
|
||||||
},
|
|
||||||
notifyUser: function (message, leaveOpen) {
|
|
||||||
var self = Metamaps.GlobalUI;
|
|
||||||
|
|
||||||
$('#toast').html(message)
|
|
||||||
self.showDiv('#toast')
|
|
||||||
clearTimeout(self.notifyTimeOut);
|
|
||||||
if (!leaveOpen) {
|
|
||||||
self.notifyTimeOut = setTimeout(function () {
|
|
||||||
self.hideDiv('#toast')
|
|
||||||
}, 8000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearNotify: function() {
|
|
||||||
var self = Metamaps.GlobalUI;
|
|
||||||
|
|
||||||
clearTimeout(self.notifyTimeOut);
|
|
||||||
self.hideDiv('#toast')
|
|
||||||
},
|
|
||||||
shareInvite: function(inviteLink) {
|
|
||||||
window.prompt("To copy the invite link, press: Ctrl+C, Enter", inviteLink);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.CreateMap = {
|
|
||||||
newMap: null,
|
|
||||||
emptyMapForm: "",
|
|
||||||
emptyForkMapForm: "",
|
|
||||||
topicsToMap: [],
|
|
||||||
synapsesToMap: [],
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' });
|
|
||||||
|
|
||||||
self.bindFormEvents();
|
|
||||||
|
|
||||||
self.emptyMapForm = $('#new_map').html();
|
|
||||||
|
|
||||||
},
|
|
||||||
bindFormEvents: function () {
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
$('.new_map input, .new_map div').unbind('keypress').bind('keypress', function(event) {
|
|
||||||
if (event.keyCode === 13) self.submit()
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.new_map button.cancel').unbind().bind('click', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
Metamaps.GlobalUI.closeLightbox();
|
|
||||||
});
|
|
||||||
$('.new_map button.submitMap').unbind().bind('click', self.submit);
|
|
||||||
|
|
||||||
// bind permission changer events on the createMap form
|
|
||||||
$('.permIcon').unbind().bind('click', self.switchPermission);
|
|
||||||
},
|
|
||||||
closeSuccess: function () {
|
|
||||||
$('#mapCreatedSuccess').fadeOut(300, function(){
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
generateSuccessMessage: function (id) {
|
|
||||||
var stringStart = "<div id='mapCreatedSuccess'><h6>SUCCESS!</h6>Your map has been created. Do you want to: <a id='mapGo' href='/maps/";
|
|
||||||
stringStart += id;
|
|
||||||
stringStart += "' onclick='Metamaps.GlobalUI.CreateMap.closeSuccess();'>Go to your new map</a>";
|
|
||||||
stringStart += "<span>OR</span><a id='mapStay' href='#' onclick='Metamaps.GlobalUI.CreateMap.closeSuccess(); return false;'>Stay on this ";
|
|
||||||
var page = Metamaps.Active.Map ? 'map' : 'page';
|
|
||||||
var stringEnd = "</a></div>";
|
|
||||||
return stringStart + page + stringEnd;
|
|
||||||
},
|
|
||||||
switchPermission: function () {
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
self.newMap.set('permission', $(this).attr('data-permission'));
|
|
||||||
$(this).siblings('.permIcon').find('.mapPermIcon').removeClass('selected');
|
|
||||||
$(this).find('.mapPermIcon').addClass('selected');
|
|
||||||
|
|
||||||
var permText = $(this).find('.tip').html();
|
|
||||||
$(this).parents('.new_map').find('.permText').html(permText);
|
|
||||||
},
|
|
||||||
submit: function (event) {
|
|
||||||
if (event) event.preventDefault();
|
|
||||||
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
if (Metamaps.GlobalUI.lightbox === 'forkmap') {
|
|
||||||
self.newMap.set('topicsToMap', self.topicsToMap);
|
|
||||||
self.newMap.set('synapsesToMap', self.synapsesToMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
|
||||||
var $form = $(formId);
|
|
||||||
|
|
||||||
self.newMap.set('name', $form.find('#map_name').val());
|
|
||||||
self.newMap.set('desc', $form.find('#map_desc').val());
|
|
||||||
|
|
||||||
if (self.newMap.get('name').length===0){
|
|
||||||
self.throwMapNameError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.newMap.save(null, {
|
|
||||||
success: self.success
|
|
||||||
// TODO add error message
|
|
||||||
});
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.closeLightbox();
|
|
||||||
Metamaps.GlobalUI.notifyUser('Working...');
|
|
||||||
},
|
|
||||||
throwMapNameError: function () {
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
|
||||||
var $form = $(formId);
|
|
||||||
|
|
||||||
var message = $("<div class='feedback_message'>Please enter a map name...</div>");
|
|
||||||
|
|
||||||
$form.find('#map_name').after(message);
|
|
||||||
setTimeout(function(){
|
|
||||||
message.fadeOut('fast', function(){
|
|
||||||
message.remove();
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
},
|
|
||||||
success: function (model) {
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
//push the new map onto the collection of 'my maps'
|
|
||||||
Metamaps.Maps.Mine.add(model);
|
|
||||||
|
|
||||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
|
||||||
var form = $(formId);
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.clearNotify();
|
|
||||||
$('#wrapper').append(self.generateSuccessMessage(model.id));
|
|
||||||
|
|
||||||
},
|
|
||||||
reset: function (id) {
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
|
||||||
|
|
||||||
var form = $('#' + id);
|
|
||||||
|
|
||||||
if (id === "fork_map") {
|
|
||||||
self.topicsToMap = [];
|
|
||||||
self.synapsesToMap = [];
|
|
||||||
form.html(self.emptyForkMapForm);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
form.html(self.emptyMapForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.bindFormEvents();
|
|
||||||
self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' });
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.Account = {
|
|
||||||
isOpen: false,
|
|
||||||
changing: false,
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.GlobalUI.Account;
|
|
||||||
|
|
||||||
$('.sidebarAccountIcon').click(self.toggleBox);
|
|
||||||
$('.sidebarAccountBox').click(function(event){
|
|
||||||
event.stopPropagation();
|
|
||||||
});
|
|
||||||
$('body').click(self.close);
|
|
||||||
},
|
|
||||||
toggleBox: function (event) {
|
|
||||||
var self = Metamaps.GlobalUI.Account;
|
|
||||||
|
|
||||||
if (self.isOpen) self.close();
|
|
||||||
else self.open();
|
|
||||||
|
|
||||||
event.stopPropagation();
|
|
||||||
},
|
|
||||||
open: function () {
|
|
||||||
var self = Metamaps.GlobalUI.Account;
|
|
||||||
|
|
||||||
Metamaps.Filter.close();
|
|
||||||
$('.sidebarAccountIcon .tooltipsUnder').addClass('hide');
|
|
||||||
|
|
||||||
|
|
||||||
if (!self.isOpen && !self.changing) {
|
|
||||||
self.changing = true;
|
|
||||||
$('.sidebarAccountBox').fadeIn(200, function () {
|
|
||||||
self.changing = false;
|
|
||||||
self.isOpen = true;
|
|
||||||
$('.sidebarAccountBox #user_email').focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
close: function () {
|
|
||||||
var self = Metamaps.GlobalUI.Account;
|
|
||||||
|
|
||||||
$('.sidebarAccountIcon .tooltipsUnder').removeClass('hide');
|
|
||||||
if (!self.changing) {
|
|
||||||
self.changing = true;
|
|
||||||
$('.sidebarAccountBox #user_email').blur();
|
|
||||||
$('.sidebarAccountBox').fadeOut(200, function () {
|
|
||||||
self.changing = false;
|
|
||||||
self.isOpen = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.Search = {
|
|
||||||
locked: false,
|
|
||||||
isOpen: false,
|
|
||||||
limitTopicsToMe: false,
|
|
||||||
limitMapsToMe: false,
|
|
||||||
timeOut: null,
|
|
||||||
changing: false,
|
|
||||||
optionsInitialized: false,
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
|
|
||||||
var loader = new CanvasLoader('searchLoading');
|
|
||||||
loader.setColor('#4fb5c0'); // default is '#000000'
|
|
||||||
loader.setDiameter(24); // default is 40
|
|
||||||
loader.setDensity(41); // default is 40
|
|
||||||
loader.setRange(0.9); // default is 1.3
|
|
||||||
loader.show(); // Hidden by default
|
|
||||||
|
|
||||||
// bind the hover events
|
|
||||||
$(".sidebarSearch").hover(function () {
|
|
||||||
self.open()
|
|
||||||
}, function () {
|
|
||||||
self.close(800, false)
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.sidebarSearchIcon').click(function (e) {
|
|
||||||
$('.sidebarSearchField').focus();
|
|
||||||
});
|
|
||||||
$('.sidebarSearch').click(function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
$('body').click(function (e) {
|
|
||||||
self.close(0, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// open if the search is closed and user hits ctrl+/
|
|
||||||
// close if they hit ESC
|
|
||||||
$('body').bind('keyup', function (e) {
|
|
||||||
switch (e.which) {
|
|
||||||
case 191:
|
|
||||||
if ((e.ctrlKey && !self.isOpen) || (e.ctrlKey && self.locked)) {
|
|
||||||
self.open(true); // true for focus
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 27:
|
|
||||||
if (self.isOpen) {
|
|
||||||
self.close(0, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break; //console.log(e.which);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.startTypeahead();
|
|
||||||
},
|
|
||||||
lock: function() {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
self.locked = true;
|
|
||||||
},
|
|
||||||
unlock: function() {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
self.locked = false;
|
|
||||||
},
|
|
||||||
open: function (focus) {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
|
|
||||||
clearTimeout(self.timeOut);
|
|
||||||
if (!self.isOpen && !self.changing && !self.locked) {
|
|
||||||
self.changing = true;
|
|
||||||
$('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({
|
|
||||||
width: '400px'
|
|
||||||
}, 300, function () {
|
|
||||||
if (focus) $('.sidebarSearchField').focus();
|
|
||||||
$('.sidebarSearchField, .sidebarSearch .tt-hint').css({
|
|
||||||
padding: '7px 10px 3px 10px',
|
|
||||||
width: '380px'
|
|
||||||
});
|
|
||||||
self.changing = false;
|
|
||||||
self.isOpen = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
close: function (closeAfter, bypass) {
|
|
||||||
// for now
|
|
||||||
return
|
|
||||||
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
|
|
||||||
self.timeOut = setTimeout(function () {
|
|
||||||
if (!self.locked && !self.changing && self.isOpen && (bypass || $('.sidebarSearchField.tt-input').val() == '')) {
|
|
||||||
self.changing = true;
|
|
||||||
$('.sidebarSearchField, .sidebarSearch .tt-hint').css({
|
|
||||||
padding: '7px 0 3px 0',
|
|
||||||
width: '400px'
|
|
||||||
});
|
|
||||||
$('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({
|
|
||||||
width: '0'
|
|
||||||
}, 300, function () {
|
|
||||||
$('.sidebarSearchField').typeahead('val', '');
|
|
||||||
$('.sidebarSearchField').blur();
|
|
||||||
self.changing = false;
|
|
||||||
self.isOpen = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, closeAfter);
|
|
||||||
},
|
|
||||||
startTypeahead: function () {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
|
|
||||||
var mapheader = Metamaps.Active.Mapper ? '<div class="searchMapsHeader searchHeader"><h3 class="search-heading">Maps</h3><input type="checkbox" class="limitToMe" id="limitMapsToMe"></input><label for="limitMapsToMe" class="limitToMeLabel">added by me</label><div class="minimizeResults minimizeMapResults"></div><div class="clearfloat"></div></div>' : '<div class="searchMapsHeader searchHeader"><h3 class="search-heading">Maps</h3><div class="minimizeResults minimizeMapResults"></div><div class="clearfloat"></div></div>';
|
|
||||||
var topicheader = Metamaps.Active.Mapper ? '<div class="searchTopicsHeader searchHeader"><h3 class="search-heading">Topics</h3><input type="checkbox" class="limitToMe" id="limitTopicsToMe"></input><label for="limitTopicsToMe" class="limitToMeLabel">added by me</label><div class="minimizeResults minimizeTopicResults"></div><div class="clearfloat"></div></div>' : '<div class="searchTopicsHeader searchHeader"><h3 class="search-heading">Topics</h3><div class="minimizeResults minimizeTopicResults"></div><div class="clearfloat"></div></div>';
|
|
||||||
var mapperheader = '<div class="searchMappersHeader searchHeader"><h3 class="search-heading">Mappers</h3><div class="minimizeResults minimizeMapperResults"></div><div class="clearfloat"></div></div>';
|
|
||||||
|
|
||||||
var topics = {
|
|
||||||
name: 'topics',
|
|
||||||
limit: 9999,
|
|
||||||
|
|
||||||
display: function(s) { return s.label; },
|
|
||||||
templates: {
|
|
||||||
notFound: function(s) {
|
|
||||||
return Hogan.compile(topicheader + $('#topicSearchTemplate').html()).render({
|
|
||||||
value: "No results",
|
|
||||||
label: "No results",
|
|
||||||
typeImageURL: Metamaps.Erb['icons/wildcard.png'],
|
|
||||||
rtype: "noresult"
|
|
||||||
});
|
|
||||||
},
|
|
||||||
header: topicheader,
|
|
||||||
suggestion: function(s) {
|
|
||||||
return Hogan.compile($('#topicSearchTemplate').html()).render(s);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
source: new Bloodhound({
|
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
remote: {
|
|
||||||
url: '/search/topics',
|
|
||||||
prepare: function(query, settings) {
|
|
||||||
settings.url += '?term=' + query;
|
|
||||||
if (Metamaps.Active.Mapper && self.limitTopicsToMe) {
|
|
||||||
settings.url += "&user=" + Metamaps.Active.Mapper.id.toString();
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
var maps = {
|
|
||||||
name: 'maps',
|
|
||||||
limit: 9999,
|
|
||||||
display: function(s) { return s.label; },
|
|
||||||
templates: {
|
|
||||||
notFound: function(s) {
|
|
||||||
return Hogan.compile(mapheader + $('#mapSearchTemplate').html()).render({
|
|
||||||
value: "No results",
|
|
||||||
label: "No results",
|
|
||||||
rtype: "noresult"
|
|
||||||
});
|
|
||||||
},
|
|
||||||
header: mapheader,
|
|
||||||
suggestion: function(s) {
|
|
||||||
return Hogan.compile($('#mapSearchTemplate').html()).render(s);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
source: new Bloodhound({
|
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
remote: {
|
|
||||||
url: '/search/maps',
|
|
||||||
prepare: function(query, settings) {
|
|
||||||
settings.url += '?term=' + query;
|
|
||||||
if (Metamaps.Active.Mapper && self.limitMapsToMe) {
|
|
||||||
settings.url += "&user=" + Metamaps.Active.Mapper.id.toString();
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
var mappers = {
|
|
||||||
name: 'mappers',
|
|
||||||
limit: 9999,
|
|
||||||
display: function(s) { return s.label; },
|
|
||||||
templates: {
|
|
||||||
notFound: function(s) {
|
|
||||||
return Hogan.compile(mapperheader + $('#mapperSearchTemplate').html()).render({
|
|
||||||
value: "No results",
|
|
||||||
label: "No results",
|
|
||||||
rtype: "noresult",
|
|
||||||
profile: Metamaps.Erb['user.png']
|
|
||||||
});
|
|
||||||
},
|
|
||||||
header: mapperheader,
|
|
||||||
suggestion: function(s) {
|
|
||||||
return Hogan.compile($('#mapperSearchTemplate').html()).render(s);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
source: new Bloodhound({
|
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
remote: {
|
|
||||||
url: '/search/mappers?term=%QUERY',
|
|
||||||
wildcard: '%QUERY',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Take all that crazy setup data and put it together into one beautiful typeahead call!
|
|
||||||
$('.sidebarSearchField').typeahead(
|
|
||||||
{
|
|
||||||
highlight: true,
|
|
||||||
},
|
|
||||||
[topics, maps, mappers]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Set max height of the search results box to prevent it from covering bottom left footer
|
|
||||||
$('.sidebarSearchField').bind('typeahead:render', function (event) {
|
|
||||||
self.initSearchOptions();
|
|
||||||
self.hideLoader();
|
|
||||||
var h = $(window).height();
|
|
||||||
$(".tt-dropdown-menu").css('max-height', h - 100);
|
|
||||||
if (self.limitTopicsToMe) {
|
|
||||||
$('#limitTopicsToMe').prop('checked', true);
|
|
||||||
}
|
|
||||||
if (self.limitMapsToMe) {
|
|
||||||
$('#limitMapsToMe').prop('checked', true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$(window).resize(function () {
|
|
||||||
var h = $(window).height();
|
|
||||||
$(".tt-dropdown-menu").css('max-height', h - 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
// tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on
|
|
||||||
$('.sidebarSearchField').bind('typeahead:select', self.handleResultClick);
|
|
||||||
|
|
||||||
// don't do it, if they clicked on a 'addToMap' button
|
|
||||||
$('.sidebarSearch button.addToMap').click(function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
});
|
|
||||||
|
|
||||||
// make sure that when you click on 'limit to me' or 'toggle section' it works
|
|
||||||
$('.sidebarSearchField.tt-input').keyup(function(){
|
|
||||||
if ($('.sidebarSearchField.tt-input').val() === '') {
|
|
||||||
self.hideLoader();
|
|
||||||
} else {
|
|
||||||
self.showLoader();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
handleResultClick: function (event, datum, dataset) {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
|
|
||||||
self.hideLoader();
|
|
||||||
|
|
||||||
if (["topic", "map", "mapper"].indexOf(datum.rtype) !== -1) {
|
|
||||||
self.close(0, true);
|
|
||||||
var win;
|
|
||||||
if (datum.rtype == "topic") {
|
|
||||||
Metamaps.Router.topics(datum.id);
|
|
||||||
} else if (datum.rtype == "map") {
|
|
||||||
Metamaps.Router.maps(datum.id);
|
|
||||||
} else if (datum.rtype == "mapper") {
|
|
||||||
Metamaps.Router.explore("mapper", datum.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initSearchOptions: function () {
|
|
||||||
var self = Metamaps.GlobalUI.Search;
|
|
||||||
|
|
||||||
function toggleResultSet(set) {
|
|
||||||
var s = $('.tt-dataset-' + set + ' .tt-suggestion, .tt-dataset-' + set + ' .resultnoresult');
|
|
||||||
if (s.is(':visible')) {
|
|
||||||
s.hide();
|
|
||||||
$(this).removeClass('minimizeResults').addClass('maximizeResults');
|
|
||||||
} else {
|
|
||||||
s.show();
|
|
||||||
$(this).removeClass('maximizeResults').addClass('minimizeResults');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.limitToMe').unbind().bind("change", function (e) {
|
|
||||||
if ($(this).attr('id') == 'limitTopicsToMe') {
|
|
||||||
self.limitTopicsToMe = !self.limitTopicsToMe;
|
|
||||||
}
|
|
||||||
if ($(this).attr('id') == 'limitMapsToMe') {
|
|
||||||
self.limitMapsToMe = !self.limitMapsToMe;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the value of the search equal to itself to retrigger the
|
|
||||||
// autocomplete event
|
|
||||||
var searchQuery = $('.sidebarSearchField.tt-input').val();
|
|
||||||
$(".sidebarSearchField").typeahead('val', '')
|
|
||||||
.typeahead('val', searchQuery);
|
|
||||||
});
|
|
||||||
|
|
||||||
// when the user clicks minimize section, hide the results for that section
|
|
||||||
$('.minimizeMapperResults').unbind().click(function (e) {
|
|
||||||
toggleResultSet.call(this, 'mappers');
|
|
||||||
});
|
|
||||||
$('.minimizeTopicResults').unbind().click(function (e) {
|
|
||||||
toggleResultSet.call(this, 'topics');
|
|
||||||
});
|
|
||||||
$('.minimizeMapResults').unbind().click(function (e) {
|
|
||||||
toggleResultSet.call(this, 'maps');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
hideLoader: function () {
|
|
||||||
$('#searchLoading').hide();
|
|
||||||
},
|
|
||||||
showLoader: function () {
|
|
||||||
$('#searchLoading').show();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,326 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Import.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Backbone
|
|
||||||
* - Metamaps.Famous // TODO remove dependency
|
|
||||||
* - Metamaps.Map
|
|
||||||
* - Metamaps.Mappings
|
|
||||||
* - Metamaps.Metacodes
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.Topics
|
|
||||||
*/
|
|
||||||
|
|
||||||
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
|
|
||||||
if (e.target.tagName === 'TEXTAREA') return
|
|
||||||
|
|
||||||
var text = e.originalEvent.clipboardData.getData('text/plain')
|
|
||||||
|
|
||||||
var results
|
|
||||||
if (text.trimLeft()[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 (window.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: // eslint-disable-line
|
|
||||||
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[synapse.topic1])
|
|
||||||
var topic2 = Metamaps.Topics.get(self.cidMappings[synapse.topic2])
|
|
||||||
if (!topic1 || !topic2) {
|
|
||||||
console.error("One of the two topics doesn't exist!")
|
|
||||||
console.error(synapse)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var synapse_created = false
|
|
||||||
topic1.once('sync', function () {
|
|
||||||
if (topic1.id && topic2.id && !synapse_created) {
|
|
||||||
synapse_created = true
|
|
||||||
self.createSynapseWithParameters(
|
|
||||||
synapse.desc, synapse.category, synapse.permission,
|
|
||||||
topic1, topic2
|
|
||||||
)
|
|
||||||
} // if
|
|
||||||
})
|
|
||||||
topic2.once('sync', function () {
|
|
||||||
if (topic1.id && topic2.id && !synapse_created) {
|
|
||||||
synapse_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) {
|
|
||||||
metacode = Metamaps.Metacodes.where({ name: 'Wildcard' })[0]
|
|
||||||
console.warn("Couldn't find metacode " + metacode_name + ' so used Wildcard instead.')
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (desc, category, permission,
|
|
||||||
topic1, topic2) {
|
|
||||||
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: desc || "",
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Listeners.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Control
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
Metamaps.Listeners = {
|
|
||||||
init: function () {
|
|
||||||
var self = this
|
|
||||||
$(document).on('keydown', function (e) {
|
|
||||||
if (!(Metamaps.Active.Map || Metamaps.Active.Topic)) return
|
|
||||||
|
|
||||||
switch (e.which) {
|
|
||||||
case 13: // if enter key is pressed
|
|
||||||
Metamaps.JIT.enterKeyHandler()
|
|
||||||
e.preventDefault()
|
|
||||||
break
|
|
||||||
case 27: // if esc key is pressed
|
|
||||||
Metamaps.JIT.escKeyHandler()
|
|
||||||
break
|
|
||||||
case 65: // if a or A is pressed
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
Metamaps.Control.deselectAllNodes()
|
|
||||||
Metamaps.Control.deselectAllEdges()
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
|
||||||
Metamaps.Control.selectNode(n, e)
|
|
||||||
})
|
|
||||||
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
case 68: // if d or D is pressed
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
Metamaps.Control.deleteSelected()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 69: // if e or E is pressed
|
|
||||||
if (e.ctrlKey && Metamaps.Active.Map) {
|
|
||||||
e.preventDefault()
|
|
||||||
Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (e.altKey && Metamaps.Active.Topic) {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
if (Metamaps.Active.Topic) {
|
|
||||||
self.centerAndReveal(Metamaps.Selected.Nodes, {
|
|
||||||
center: true,
|
|
||||||
reveal: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 72: // if h or H is pressed
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
Metamaps.Control.hideSelectedNodes()
|
|
||||||
Metamaps.Control.hideSelectedEdges()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 77: // if m or M is pressed
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
Metamaps.Control.removeSelectedNodes()
|
|
||||||
Metamaps.Control.removeSelectedEdges()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 82: // if r or R is pressed
|
|
||||||
if (e.altKey && Metamaps.Active.Topic) {
|
|
||||||
e.preventDefault()
|
|
||||||
self.centerAndReveal(Metamaps.Selected.Nodes, {
|
|
||||||
center: false,
|
|
||||||
reveal: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 84: // if t or T is pressed
|
|
||||||
if (e.altKey && Metamaps.Active.Topic) {
|
|
||||||
e.preventDefault()
|
|
||||||
self.centerAndReveal(Metamaps.Selected.Nodes, {
|
|
||||||
center: true,
|
|
||||||
reveal: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
// console.log(e.which)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$(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()
|
|
||||||
Metamaps.Mobile.resizeTitle()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
centerAndReveal: function(nodes, opts) {
|
|
||||||
if (nodes.length < 1) return
|
|
||||||
var node = nodes[nodes.length - 1]
|
|
||||||
if (opts.center && opts.reveal) {
|
|
||||||
Metamaps.Topic.centerOn(node.id, function() {
|
|
||||||
Metamaps.Topic.fetchRelatives(nodes)
|
|
||||||
})
|
|
||||||
} else if (opts.center) {
|
|
||||||
Metamaps.Topic.centerOn(node.id)
|
|
||||||
} else if (opts.reveal) {
|
|
||||||
Metamaps.Topic.fetchRelatives(nodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Listeners
|
|
|
@ -1,822 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Map.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Create
|
|
||||||
* - Metamaps.Erb
|
|
||||||
* - Metamaps.Filter
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Loading
|
|
||||||
* - Metamaps.Maps
|
|
||||||
* - Metamaps.Realtime
|
|
||||||
* - Metamaps.Router
|
|
||||||
* - Metamaps.Selected
|
|
||||||
* - Metamaps.SynapseCard
|
|
||||||
* - Metamaps.TopicCard
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Backbone
|
|
||||||
* - Metamaps.GlobalUI
|
|
||||||
* - Metamaps.Mappers
|
|
||||||
* - Metamaps.Mappings
|
|
||||||
* - Metamaps.Messages
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.Topics
|
|
||||||
*
|
|
||||||
* Major sub-modules:
|
|
||||||
* - Metamaps.Map.CheatSheet
|
|
||||||
* - Metamaps.Map.InfoBox
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Map = {
|
|
||||||
events: {
|
|
||||||
editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper'
|
|
||||||
},
|
|
||||||
nextX: 0,
|
|
||||||
nextY: 0,
|
|
||||||
sideLength: 1,
|
|
||||||
turnCount: 0,
|
|
||||||
nextXshift: 1,
|
|
||||||
nextYshift: 0,
|
|
||||||
timeToTurn: 0,
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.Map
|
|
||||||
|
|
||||||
// prevent right clicks on the main canvas, so as to not get in the way of our right clicks
|
|
||||||
$('#center-container').bind('contextmenu', function (e) {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.starMap').click(function () {
|
|
||||||
if ($(this).is('.starred')) self.unstar()
|
|
||||||
else self.star()
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.sidebarFork').click(function () {
|
|
||||||
self.fork()
|
|
||||||
})
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
|
|
||||||
|
|
||||||
self.updateStar()
|
|
||||||
self.InfoBox.init()
|
|
||||||
self.CheatSheet.init()
|
|
||||||
|
|
||||||
$(document).on(Metamaps.Map.events.editedByActiveMapper, self.editedByActiveMapper)
|
|
||||||
},
|
|
||||||
launch: function (id) {
|
|
||||||
var bb = Metamaps.Backbone
|
|
||||||
var start = function (data) {
|
|
||||||
Metamaps.Active.Map = new bb.Map(data.map)
|
|
||||||
Metamaps.Mappers = new bb.MapperCollection(data.mappers)
|
|
||||||
Metamaps.Collaborators = new bb.MapperCollection(data.collaborators)
|
|
||||||
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.Stars = data.stars
|
|
||||||
Metamaps.Backbone.attachCollectionEvents()
|
|
||||||
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
var mapper = Metamaps.Active.Mapper
|
|
||||||
|
|
||||||
// add class to .wrapper for specifying whether you can edit the map
|
|
||||||
if (map.authorizeToEdit(mapper)) {
|
|
||||||
$('.wrapper').addClass('canEditMap')
|
|
||||||
}
|
|
||||||
|
|
||||||
// add class to .wrapper for specifying if the map can
|
|
||||||
// be collaborated on
|
|
||||||
if (map.get('permission') === 'commons') {
|
|
||||||
$('.wrapper').addClass('commonsMap')
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Map.updateStar()
|
|
||||||
|
|
||||||
// set filter mapper H3 text
|
|
||||||
$('#filter_by_mapper h3').html('MAPPERS')
|
|
||||||
|
|
||||||
// build and render the visualization
|
|
||||||
Metamaps.Visualize.type = 'ForceDirected'
|
|
||||||
Metamaps.JIT.prepareVizData()
|
|
||||||
|
|
||||||
// update filters
|
|
||||||
Metamaps.Filter.reset()
|
|
||||||
|
|
||||||
// reset selected arrays
|
|
||||||
Metamaps.Selected.reset()
|
|
||||||
|
|
||||||
// set the proper mapinfobox content
|
|
||||||
Metamaps.Map.InfoBox.load()
|
|
||||||
|
|
||||||
// these three update the actual filter box with the right list items
|
|
||||||
Metamaps.Filter.checkMetacodes()
|
|
||||||
Metamaps.Filter.checkSynapses()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
|
|
||||||
Metamaps.Realtime.startActiveMap()
|
|
||||||
Metamaps.Loading.hide()
|
|
||||||
|
|
||||||
// for mobile
|
|
||||||
$('#header_content').html(map.get('name'))
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/maps/' + id + '/contains.json',
|
|
||||||
success: start
|
|
||||||
})
|
|
||||||
},
|
|
||||||
end: function () {
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
$('.wrapper').removeClass('canEditMap commonsMap')
|
|
||||||
Metamaps.Map.resetSpiral()
|
|
||||||
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Metamaps.TopicCard.hideCard()
|
|
||||||
Metamaps.SynapseCard.hideCard()
|
|
||||||
Metamaps.Create.newTopic.hide(true) // true means force (and override pinned)
|
|
||||||
Metamaps.Create.newSynapse.hide()
|
|
||||||
Metamaps.Filter.close()
|
|
||||||
Metamaps.Map.InfoBox.close()
|
|
||||||
Metamaps.Realtime.endActiveMap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateStar: function () {
|
|
||||||
if (!Metamaps.Active.Mapper || !Metamaps.Stars) return
|
|
||||||
// update the star/unstar icon
|
|
||||||
if (Metamaps.Stars.find(function (s) { return s.user_id === Metamaps.Active.Mapper.id })) {
|
|
||||||
$('.starMap').addClass('starred')
|
|
||||||
$('.starMap .tooltipsAbove').html('Unstar')
|
|
||||||
} else {
|
|
||||||
$('.starMap').removeClass('starred')
|
|
||||||
$('.starMap .tooltipsAbove').html('Star')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
star: function () {
|
|
||||||
var self = Metamaps.Map
|
|
||||||
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
$.post('/maps/' + Metamaps.Active.Map.id + '/star')
|
|
||||||
Metamaps.Stars.push({ user_id: Metamaps.Active.Mapper.id, map_id: Metamaps.Active.Map.id })
|
|
||||||
Metamaps.Maps.Starred.add(Metamaps.Active.Map)
|
|
||||||
Metamaps.GlobalUI.notifyUser('Map is now starred')
|
|
||||||
self.updateStar()
|
|
||||||
},
|
|
||||||
unstar: function () {
|
|
||||||
var self = Metamaps.Map
|
|
||||||
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
$.post('/maps/' + Metamaps.Active.Map.id + '/unstar')
|
|
||||||
Metamaps.Stars = Metamaps.Stars.filter(function (s) { return s.user_id != Metamaps.Active.Mapper.id })
|
|
||||||
Metamaps.Maps.Starred.remove(Metamaps.Active.Map)
|
|
||||||
self.updateStar()
|
|
||||||
},
|
|
||||||
fork: function () {
|
|
||||||
Metamaps.GlobalUI.openLightbox('forkmap')
|
|
||||||
|
|
||||||
var nodes_data = '',
|
|
||||||
synapses_data = ''
|
|
||||||
var nodes_array = []
|
|
||||||
var synapses_array = []
|
|
||||||
// collect the unfiltered topics
|
|
||||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
|
||||||
// if the opacity is less than 1 then it's filtered
|
|
||||||
if (n.getData('alpha') === 1) {
|
|
||||||
var id = n.getData('topic').id
|
|
||||||
nodes_array.push(id)
|
|
||||||
var x, y
|
|
||||||
if (n.pos.x && n.pos.y) {
|
|
||||||
x = n.pos.x
|
|
||||||
y = n.pos.y
|
|
||||||
} else {
|
|
||||||
var x = Math.cos(n.pos.theta) * n.pos.rho
|
|
||||||
var y = Math.sin(n.pos.theta) * n.pos.rho
|
|
||||||
}
|
|
||||||
nodes_data += id + '/' + x + '/' + y + ','
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// collect the unfiltered synapses
|
|
||||||
Metamaps.Synapses.each(function (synapse) {
|
|
||||||
var desc = synapse.get('desc')
|
|
||||||
|
|
||||||
var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1
|
|
||||||
// make sure that both topics are being added, otherwise, it
|
|
||||||
// doesn't make sense to add the synapse
|
|
||||||
var topicsNotFiltered = nodes_array.indexOf(synapse.get('node1_id')) > -1
|
|
||||||
topicsNotFiltered = topicsNotFiltered && nodes_array.indexOf(synapse.get('node2_id')) > -1
|
|
||||||
if (descNotFiltered && topicsNotFiltered) {
|
|
||||||
synapses_array.push(synapse.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
synapses_data = synapses_array.join()
|
|
||||||
nodes_data = nodes_data.slice(0, -1)
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data
|
|
||||||
Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data
|
|
||||||
},
|
|
||||||
leavePrivateMap: function () {
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
Metamaps.Maps.Active.remove(map)
|
|
||||||
Metamaps.Maps.Featured.remove(map)
|
|
||||||
Metamaps.Router.home()
|
|
||||||
Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
|
|
||||||
},
|
|
||||||
cantEditNow: function () {
|
|
||||||
Metamaps.Realtime.turnOff(true); // true is for 'silence'
|
|
||||||
Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.')
|
|
||||||
Metamaps.Active.Map.trigger('changeByOther')
|
|
||||||
},
|
|
||||||
canEditNow: function () {
|
|
||||||
var confirmString = "You've been granted permission to edit this map. "
|
|
||||||
confirmString += 'Do you want to reload and enable realtime collaboration?'
|
|
||||||
var c = confirm(confirmString)
|
|
||||||
if (c) {
|
|
||||||
Metamaps.Router.maps(Metamaps.Active.Map.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editedByActiveMapper: function () {
|
|
||||||
if (Metamaps.Active.Mapper) {
|
|
||||||
Metamaps.Mappers.add(Metamaps.Active.Mapper)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getNextCoord: function () {
|
|
||||||
var self = Metamaps.Map
|
|
||||||
var nextX = self.nextX
|
|
||||||
var nextY = self.nextY
|
|
||||||
|
|
||||||
var DISTANCE_BETWEEN = 250
|
|
||||||
|
|
||||||
self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift
|
|
||||||
self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift
|
|
||||||
|
|
||||||
self.timeToTurn += 1
|
|
||||||
// if true, it's time to turn
|
|
||||||
if (self.timeToTurn === self.sideLength) {
|
|
||||||
self.turnCount += 1
|
|
||||||
// if true, it's time to increase side length
|
|
||||||
if (self.turnCount % 2 === 0) {
|
|
||||||
self.sideLength += 1
|
|
||||||
}
|
|
||||||
self.timeToTurn = 0
|
|
||||||
|
|
||||||
// going right? turn down
|
|
||||||
if (self.nextXshift == 1 && self.nextYshift == 0) {
|
|
||||||
self.nextXshift = 0
|
|
||||||
self.nextYshift = 1
|
|
||||||
}
|
|
||||||
// going down? turn left
|
|
||||||
else if (self.nextXshift == 0 && self.nextYshift == 1) {
|
|
||||||
self.nextXshift = -1
|
|
||||||
self.nextYshift = 0
|
|
||||||
}
|
|
||||||
// going left? turn up
|
|
||||||
else if (self.nextXshift == -1 && self.nextYshift == 0) {
|
|
||||||
self.nextXshift = 0
|
|
||||||
self.nextYshift = -1
|
|
||||||
}
|
|
||||||
// going up? turn right
|
|
||||||
else if (self.nextXshift == 0 && self.nextYshift == -1) {
|
|
||||||
self.nextXshift = 1
|
|
||||||
self.nextYshift = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is so that if someone has relied on the auto-placement feature on this map,
|
|
||||||
// it will at least start placing nodes at the first empty spot
|
|
||||||
// this will only work up to the point in the spiral at which someone manually moved a node
|
|
||||||
if (Metamaps.Mappings.findWhere({ xloc: nextX, yloc: nextY })) {
|
|
||||||
return self.getNextCoord()
|
|
||||||
}
|
|
||||||
else return {
|
|
||||||
x: nextX,
|
|
||||||
y: nextY
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetSpiral: function () {
|
|
||||||
Metamaps.Map.nextX = 0
|
|
||||||
Metamaps.Map.nextY = 0
|
|
||||||
Metamaps.Map.nextXshift = 1
|
|
||||||
Metamaps.Map.nextYshift = 0
|
|
||||||
Metamaps.Map.sideLength = 1
|
|
||||||
Metamaps.Map.timeToTurn = 0
|
|
||||||
Metamaps.Map.turnCount = 0
|
|
||||||
},
|
|
||||||
exportImage: function () {
|
|
||||||
var canvas = {}
|
|
||||||
|
|
||||||
canvas.canvas = document.createElement('canvas')
|
|
||||||
canvas.canvas.width = 1880 // 960
|
|
||||||
canvas.canvas.height = 1260 // 630
|
|
||||||
|
|
||||||
canvas.scaleOffsetX = 1
|
|
||||||
canvas.scaleOffsetY = 1
|
|
||||||
canvas.translateOffsetY = 0
|
|
||||||
canvas.translateOffsetX = 0
|
|
||||||
canvas.denySelected = true
|
|
||||||
|
|
||||||
canvas.getSize = function () {
|
|
||||||
if (this.size) return this.size
|
|
||||||
var canvas = this.canvas
|
|
||||||
return this.size = {
|
|
||||||
width: canvas.width,
|
|
||||||
height: canvas.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canvas.scale = function (x, y) {
|
|
||||||
var px = this.scaleOffsetX * x,
|
|
||||||
py = this.scaleOffsetY * y
|
|
||||||
var dx = this.translateOffsetX * (x - 1) / px,
|
|
||||||
dy = this.translateOffsetY * (y - 1) / py
|
|
||||||
this.scaleOffsetX = px
|
|
||||||
this.scaleOffsetY = py
|
|
||||||
this.getCtx().scale(x, y)
|
|
||||||
this.translate(dx, dy)
|
|
||||||
}
|
|
||||||
canvas.translate = function (x, y) {
|
|
||||||
var sx = this.scaleOffsetX,
|
|
||||||
sy = this.scaleOffsetY
|
|
||||||
this.translateOffsetX += x * sx
|
|
||||||
this.translateOffsetY += y * sy
|
|
||||||
this.getCtx().translate(x, y)
|
|
||||||
}
|
|
||||||
canvas.getCtx = function () {
|
|
||||||
return this.canvas.getContext('2d')
|
|
||||||
}
|
|
||||||
// center it
|
|
||||||
canvas.getCtx().translate(1880 / 2, 1260 / 2)
|
|
||||||
|
|
||||||
var mGraph = Metamaps.Visualize.mGraph
|
|
||||||
|
|
||||||
var id = mGraph.root
|
|
||||||
var root = mGraph.graph.getNode(id)
|
|
||||||
var T = !!root.visited
|
|
||||||
|
|
||||||
// pass true to avoid basing it on a selection
|
|
||||||
Metamaps.JIT.zoomExtents(null, canvas, true)
|
|
||||||
|
|
||||||
var c = canvas.canvas,
|
|
||||||
ctx = canvas.getCtx(),
|
|
||||||
scale = canvas.scaleOffsetX
|
|
||||||
|
|
||||||
// draw a grey background
|
|
||||||
ctx.fillStyle = '#d8d9da'
|
|
||||||
var xPoint = (-(c.width / scale) / 2) - (canvas.translateOffsetX / scale),
|
|
||||||
yPoint = (-(c.height / scale) / 2) - (canvas.translateOffsetY / scale)
|
|
||||||
ctx.fillRect(xPoint, yPoint, c.width / scale, c.height / scale)
|
|
||||||
|
|
||||||
// draw the graph
|
|
||||||
mGraph.graph.eachNode(function (node) {
|
|
||||||
var nodeAlpha = node.getData('alpha')
|
|
||||||
node.eachAdjacency(function (adj) {
|
|
||||||
var nodeTo = adj.nodeTo
|
|
||||||
if (!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
|
|
||||||
mGraph.fx.plotLine(adj, canvas)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (node.drawn) {
|
|
||||||
mGraph.fx.plotNode(node, canvas)
|
|
||||||
}
|
|
||||||
if (!mGraph.labelsHidden) {
|
|
||||||
if (node.drawn && nodeAlpha >= 0.95) {
|
|
||||||
mGraph.labels.plotLabel(canvas, node)
|
|
||||||
} else {
|
|
||||||
mGraph.labels.hideLabel(node, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.visited = !T
|
|
||||||
})
|
|
||||||
|
|
||||||
var imageData = {
|
|
||||||
encoded_image: canvas.canvas.toDataURL()
|
|
||||||
}
|
|
||||||
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
|
|
||||||
var today = new Date()
|
|
||||||
var dd = today.getDate()
|
|
||||||
var mm = today.getMonth() + 1; // January is 0!
|
|
||||||
var yyyy = today.getFullYear()
|
|
||||||
if (dd < 10) {
|
|
||||||
dd = '0' + dd
|
|
||||||
}
|
|
||||||
if (mm < 10) {
|
|
||||||
mm = '0' + mm
|
|
||||||
}
|
|
||||||
today = mm + '/' + dd + '/' + yyyy
|
|
||||||
|
|
||||||
var mapName = map.get('name').split(' ').join([separator = '-'])
|
|
||||||
var downloadMessage = ''
|
|
||||||
downloadMessage += 'Captured map screenshot! '
|
|
||||||
downloadMessage += "<a href='" + imageData.encoded_image + "' "
|
|
||||||
downloadMessage += "download='metamap-" + map.id + '-' + mapName + '-' + today + ".png'>DOWNLOAD</a>"
|
|
||||||
Metamaps.GlobalUI.notifyUser(downloadMessage)
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
url: '/maps/' + Metamaps.Active.Map.id + '/upload_screenshot',
|
|
||||||
data: imageData,
|
|
||||||
success: function (data) {
|
|
||||||
console.log('successfully uploaded map screenshot')
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
console.log('failed to save map screenshot')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* CHEATSHEET
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
Metamaps.Map.CheatSheet = {
|
|
||||||
init: function () {
|
|
||||||
// tab the cheatsheet
|
|
||||||
$('#cheatSheet').tabs()
|
|
||||||
$('#quickReference').tabs().addClass('ui-tabs-vertical ui-helper-clearfix')
|
|
||||||
$('#quickReference .ui-tabs-nav li').removeClass('ui-corner-top').addClass('ui-corner-left')
|
|
||||||
|
|
||||||
// id = the id of a vimeo video
|
|
||||||
var switchVideo = function (element, id) {
|
|
||||||
$('.tutorialItem').removeClass('active')
|
|
||||||
$(element).addClass('active')
|
|
||||||
$('#tutorialVideo').attr('src', '//player.vimeo.com/video/' + id)
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#gettingStarted').click(function () {
|
|
||||||
// switchVideo(this,'88334167')
|
|
||||||
})
|
|
||||||
$('#upYourSkillz').click(function () {
|
|
||||||
// switchVideo(this,'100118167')
|
|
||||||
})
|
|
||||||
$('#advancedMapping').click(function () {
|
|
||||||
// switchVideo(this,'88334167')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Map.CheatSheet
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* INFOBOX
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
Metamaps.Map.InfoBox = {
|
|
||||||
isOpen: false,
|
|
||||||
changing: false,
|
|
||||||
selectingPermission: false,
|
|
||||||
changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.</div>",
|
|
||||||
nameHTML: '<span class="best_in_place best_in_place_name" id="best_in_place_map_{{id}}_name" data-url="/maps/{{id}}" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName">{{name}}</span>',
|
|
||||||
descHTML: '<span class="best_in_place best_in_place_desc" id="best_in_place_map_{{id}}_desc" data-url="/maps/{{id}}" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc">{{desc}}</span>',
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
$('.mapInfoIcon').click(self.toggleBox)
|
|
||||||
$('.mapInfoBox').click(function (event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
})
|
|
||||||
$('body').click(self.close)
|
|
||||||
|
|
||||||
self.attachEventListeners()
|
|
||||||
|
|
||||||
self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html())
|
|
||||||
|
|
||||||
var querystring = window.location.search.replace(/^\?/, '')
|
|
||||||
if (querystring == 'new') {
|
|
||||||
self.open()
|
|
||||||
$('.mapInfoBox').addClass('mapRequestTitle')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleBox: function (event) {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
if (self.isOpen) self.close()
|
|
||||||
else self.open()
|
|
||||||
|
|
||||||
event.stopPropagation()
|
|
||||||
},
|
|
||||||
open: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
$('.mapInfoIcon div').addClass('hide')
|
|
||||||
if (!self.isOpen && !self.changing) {
|
|
||||||
self.changing = true
|
|
||||||
$('.mapInfoBox').fadeIn(200, function () {
|
|
||||||
self.changing = false
|
|
||||||
self.isOpen = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
close: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
$('.mapInfoIcon div').removeClass('hide')
|
|
||||||
if (!self.changing) {
|
|
||||||
self.changing = true
|
|
||||||
$('.mapInfoBox').fadeOut(200, function () {
|
|
||||||
self.changing = false
|
|
||||||
self.isOpen = false
|
|
||||||
self.hidePermissionSelect()
|
|
||||||
$('.mapContributors .tip').hide()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
load: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
|
|
||||||
var obj = map.pick('permission', 'topic_count', 'synapse_count')
|
|
||||||
|
|
||||||
var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper)
|
|
||||||
var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper)
|
|
||||||
var relevantPeople = map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
|
|
||||||
var shareable = map.get('permission') !== 'private'
|
|
||||||
|
|
||||||
obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name')
|
|
||||||
obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc')
|
|
||||||
obj['map_creator_tip'] = isCreator ? self.changePermissionText : ''
|
|
||||||
|
|
||||||
obj['contributor_count'] = relevantPeople.length
|
|
||||||
obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : ''
|
|
||||||
obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : ''
|
|
||||||
obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : Metamaps.Erb['user.png']
|
|
||||||
obj['contributor_list'] = self.createContributorList()
|
|
||||||
|
|
||||||
obj['user_name'] = isCreator ? 'You' : map.get('user_name')
|
|
||||||
obj['created_at'] = map.get('created_at_clean')
|
|
||||||
obj['updated_at'] = map.get('updated_at_clean')
|
|
||||||
|
|
||||||
var classes = isCreator ? 'yourMap' : ''
|
|
||||||
classes += canEdit ? ' canEdit' : ''
|
|
||||||
classes += shareable ? ' shareable' : ''
|
|
||||||
$('.mapInfoBox').removeClass('shareable yourMap canEdit')
|
|
||||||
.addClass(classes)
|
|
||||||
.html(self.generateBoxHTML.render(obj))
|
|
||||||
|
|
||||||
self.attachEventListeners()
|
|
||||||
},
|
|
||||||
attachEventListeners: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
$('.mapInfoBox.canEdit .best_in_place').best_in_place()
|
|
||||||
|
|
||||||
// because anyone who can edit the map can change the map title
|
|
||||||
var bipName = $('.mapInfoBox .best_in_place_name')
|
|
||||||
bipName.unbind('best_in_place:activate').bind('best_in_place:activate', function () {
|
|
||||||
var $el = bipName.find('textarea')
|
|
||||||
var el = $el[0]
|
|
||||||
|
|
||||||
$el.attr('maxlength', '140')
|
|
||||||
|
|
||||||
$('.mapInfoName').append('<div class="nameCounter forMap"></div>')
|
|
||||||
|
|
||||||
var callback = function (data) {
|
|
||||||
$('.nameCounter.forMap').html(data.all + '/140')
|
|
||||||
}
|
|
||||||
Countable.live(el, callback)
|
|
||||||
})
|
|
||||||
bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function () {
|
|
||||||
$('.nameCounter.forMap').remove()
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function () {
|
|
||||||
var name = $(this).html()
|
|
||||||
Metamaps.Active.Map.set('name', name)
|
|
||||||
Metamaps.Active.Map.trigger('saved')
|
|
||||||
// mobile menu
|
|
||||||
$('#header_content').html(name)
|
|
||||||
$('.mapInfoBox').removeClass('mapRequestTitle')
|
|
||||||
document.title = name + ' | Metamaps'
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function () {
|
|
||||||
var desc = $(this).html()
|
|
||||||
Metamaps.Active.Map.set('desc', desc)
|
|
||||||
Metamaps.Active.Map.trigger('saved')
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.yourMap .mapPermission').unbind().click(self.onPermissionClick)
|
|
||||||
// .yourMap in the unbind/bind is just a namespace for the events
|
|
||||||
// not a reference to the class .yourMap on the .mapInfoBox
|
|
||||||
$('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect)
|
|
||||||
|
|
||||||
$('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap)
|
|
||||||
|
|
||||||
$('.mapContributors span, #mapContribs').unbind().click(function (event) {
|
|
||||||
$('.mapContributors .tip').toggle()
|
|
||||||
event.stopPropagation()
|
|
||||||
})
|
|
||||||
$('.mapContributors .tip').unbind().click(function (event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
})
|
|
||||||
$('.mapContributors .tip li a').click(Metamaps.Router.intercept)
|
|
||||||
|
|
||||||
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () {
|
|
||||||
$('.mapContributors .tip').hide()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addTypeahead()
|
|
||||||
},
|
|
||||||
addTypeahead: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
// for autocomplete
|
|
||||||
var collaborators = {
|
|
||||||
name: 'collaborators',
|
|
||||||
limit: 9999,
|
|
||||||
display: function(s) { return s.label; },
|
|
||||||
templates: {
|
|
||||||
notFound: function(s) {
|
|
||||||
return Hogan.compile($('#collaboratorSearchTemplate').html()).render({
|
|
||||||
value: "No results",
|
|
||||||
label: "No results",
|
|
||||||
rtype: "noresult",
|
|
||||||
profile: Metamaps.Erb['user.png'],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
suggestion: function(s) {
|
|
||||||
return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
source: new Bloodhound({
|
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
remote: {
|
|
||||||
url: '/search/mappers?term=%QUERY',
|
|
||||||
wildcard: '%QUERY',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// for adding map collaborators, who will have edit rights
|
|
||||||
if (Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')) {
|
|
||||||
$('.collaboratorSearchField').typeahead(
|
|
||||||
{
|
|
||||||
highlight: false,
|
|
||||||
},
|
|
||||||
[collaborators]
|
|
||||||
)
|
|
||||||
$('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick)
|
|
||||||
$('.mapContributors .removeCollaborator').click(function () {
|
|
||||||
self.removeCollaborator(parseInt($(this).data('id')))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeCollaborator: function (collaboratorId) {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
Metamaps.Collaborators.remove(Metamaps.Collaborators.get(collaboratorId))
|
|
||||||
var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id })
|
|
||||||
$.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds })
|
|
||||||
self.updateNumbers()
|
|
||||||
},
|
|
||||||
addCollaborator: function (newCollaboratorId) {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
if (Metamaps.Collaborators.get(newCollaboratorId)) {
|
|
||||||
Metamaps.GlobalUI.notifyUser('That user already has access')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
function callback(mapper) {
|
|
||||||
Metamaps.Collaborators.add(mapper)
|
|
||||||
var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id })
|
|
||||||
$.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds })
|
|
||||||
var name = Metamaps.Collaborators.get(newCollaboratorId).get('name')
|
|
||||||
Metamaps.GlobalUI.notifyUser(name + ' will be notified by email')
|
|
||||||
self.updateNumbers()
|
|
||||||
}
|
|
||||||
|
|
||||||
$.getJSON('/users/' + newCollaboratorId + '.json', callback)
|
|
||||||
},
|
|
||||||
handleResultClick: function (event, item) {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
self.addCollaborator(item.id)
|
|
||||||
$('.collaboratorSearchField').typeahead('val', '')
|
|
||||||
},
|
|
||||||
updateNameDescPerm: function (name, desc, perm) {
|
|
||||||
$('.mapInfoBox').removeClass('mapRequestTitle')
|
|
||||||
$('.mapInfoName .best_in_place_name').html(name)
|
|
||||||
$('.mapInfoDesc .best_in_place_desc').html(desc)
|
|
||||||
$('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm)
|
|
||||||
},
|
|
||||||
createContributorList: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
|
|
||||||
var activeMapperIsCreator = Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')
|
|
||||||
var string = ''
|
|
||||||
string += '<ul>'
|
|
||||||
|
|
||||||
relevantPeople.each(function (m) {
|
|
||||||
var isCreator = Metamaps.Active.Map.get('user_id') === m.get('id')
|
|
||||||
string += '<li><a href="/explore/mapper/' + m.get('id') + '">' + '<img class="rtUserImage" width="25" height="25" src="' + m.get('image') + '" />' + m.get('name')
|
|
||||||
if (isCreator) string += ' (creator)'
|
|
||||||
string += '</a>'
|
|
||||||
if (activeMapperIsCreator && !isCreator) string += '<span class="removeCollaborator" data-id="' + m.get('id') + '"></span>'
|
|
||||||
string += '</li>'
|
|
||||||
})
|
|
||||||
|
|
||||||
string += '</ul>'
|
|
||||||
|
|
||||||
if (activeMapperIsCreator) {
|
|
||||||
string += '<div class="collabSearchField"><span class="addCollab"></span><input class="collaboratorSearchField" placeholder="Add a collaborator!"></input></div>'
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
},
|
|
||||||
updateNumbers: function () {
|
|
||||||
if (!Metamaps.Active.Map) return
|
|
||||||
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
var mapper = Metamaps.Active.Mapper
|
|
||||||
var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
|
|
||||||
|
|
||||||
var contributors_class = ''
|
|
||||||
if (relevantPeople.length === 2) contributors_class = 'multiple mTwo'
|
|
||||||
else if (relevantPeople.length > 2) contributors_class = 'multiple'
|
|
||||||
|
|
||||||
var contributors_image = Metamaps.Erb['user.png']
|
|
||||||
if (relevantPeople.length > 0) {
|
|
||||||
// get the first contributor and use their image
|
|
||||||
contributors_image = relevantPeople.models[0].get('image')
|
|
||||||
}
|
|
||||||
$('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class)
|
|
||||||
$('.mapContributors span').text(relevantPeople.length)
|
|
||||||
$('.mapContributors .tip').html(self.createContributorList())
|
|
||||||
self.addTypeahead()
|
|
||||||
$('.mapContributors .tip').unbind().click(function (event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
})
|
|
||||||
$('.mapTopics').text(Metamaps.Topics.length)
|
|
||||||
$('.mapSynapses').text(Metamaps.Synapses.length)
|
|
||||||
|
|
||||||
$('.mapEditedAt').html('<span>Last edited: </span>' + Metamaps.Util.nowDateFormatted())
|
|
||||||
},
|
|
||||||
onPermissionClick: function (event) {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
if (!self.selectingPermission) {
|
|
||||||
self.selectingPermission = true
|
|
||||||
$(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow
|
|
||||||
if ($(this).hasClass('commons')) {
|
|
||||||
$(this).append('<ul class="permissionSelect"><li class="public"></li><li class="private"></li></ul>')
|
|
||||||
} else if ($(this).hasClass('public')) {
|
|
||||||
$(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="private"></li></ul>')
|
|
||||||
} else if ($(this).hasClass('private')) {
|
|
||||||
$(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="public"></li></ul>')
|
|
||||||
}
|
|
||||||
$('.mapPermission .permissionSelect li').click(self.selectPermission)
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hidePermissionSelect: function () {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
self.selectingPermission = false
|
|
||||||
$('.mapPermission').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow
|
|
||||||
$('.mapPermission .permissionSelect').remove()
|
|
||||||
},
|
|
||||||
selectPermission: function (event) {
|
|
||||||
var self = Metamaps.Map.InfoBox
|
|
||||||
|
|
||||||
self.selectingPermission = false
|
|
||||||
var permission = $(this).attr('class')
|
|
||||||
Metamaps.Active.Map.save({
|
|
||||||
permission: permission
|
|
||||||
})
|
|
||||||
Metamaps.Active.Map.updateMapWrapper()
|
|
||||||
shareable = permission === 'private' ? '' : 'shareable'
|
|
||||||
$('.mapPermission').removeClass('commons public private minimize').addClass(permission)
|
|
||||||
$('.mapPermission .permissionSelect').remove()
|
|
||||||
$('.mapInfoBox').removeClass('shareable').addClass(shareable)
|
|
||||||
event.stopPropagation()
|
|
||||||
},
|
|
||||||
deleteActiveMap: function () {
|
|
||||||
var confirmString = 'Are you sure you want to delete this map? '
|
|
||||||
confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.'
|
|
||||||
|
|
||||||
var doIt = confirm(confirmString)
|
|
||||||
var map = Metamaps.Active.Map
|
|
||||||
var mapper = Metamaps.Active.Mapper
|
|
||||||
var authorized = map.authorizePermissionChange(mapper)
|
|
||||||
|
|
||||||
if (doIt && authorized) {
|
|
||||||
Metamaps.Map.InfoBox.close()
|
|
||||||
Metamaps.Maps.Active.remove(map)
|
|
||||||
Metamaps.Maps.Featured.remove(map)
|
|
||||||
Metamaps.Maps.Mine.remove(map)
|
|
||||||
Metamaps.Maps.Shared.remove(map)
|
|
||||||
map.destroy()
|
|
||||||
Metamaps.Router.home()
|
|
||||||
Metamaps.GlobalUI.notifyUser('Map eliminated!')
|
|
||||||
}
|
|
||||||
else if (!authorized) {
|
|
||||||
alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Map.InfoBox
|
|
|
@ -1,20 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Mapper.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies: none!
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Mapper = {
|
|
||||||
// this function is to retrieve a mapper JSON object from the database
|
|
||||||
// @param id = the id of the mapper to retrieve
|
|
||||||
get: function (id, callback) {
|
|
||||||
return $.ajax({
|
|
||||||
url: '/users/' + id + '.json',
|
|
||||||
success: function (data) {
|
|
||||||
callback(new Metamaps.Backbone.Mapper(data))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Mapper
|
|
|
@ -1,37 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Mobile.js
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Map
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Mobile = {
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.Mobile
|
|
||||||
|
|
||||||
$('#menu_icon').click(self.toggleMenu)
|
|
||||||
$('#mobile_menu li a').click(self.liClick)
|
|
||||||
$('#header_content').click(self.titleClick)
|
|
||||||
self.resizeTitle()
|
|
||||||
},
|
|
||||||
resizeTitle: function () {
|
|
||||||
// the 70 relates to padding
|
|
||||||
$('#header_content').width($(document).width() - 70)
|
|
||||||
},
|
|
||||||
liClick: function () {
|
|
||||||
var self = Metamaps.Mobile
|
|
||||||
$('#header_content').html($(this).text())
|
|
||||||
self.toggleMenu()
|
|
||||||
},
|
|
||||||
toggleMenu: function () {
|
|
||||||
$('#mobile_menu').toggle()
|
|
||||||
},
|
|
||||||
titleClick: function () {
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
Metamaps.Map.InfoBox.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Organize.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
Metamaps.Organize = {
|
|
||||||
init: function () {},
|
|
||||||
arrange: function (layout, centerNode) {
|
|
||||||
// first option for layout to implement is 'grid', will do an evenly spaced grid with its center at the 0,0 origin
|
|
||||||
if (layout == 'grid') {
|
|
||||||
var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization
|
|
||||||
var numColumns = Math.floor(Math.sqrt(numNodes)) // the number of columns to make an even grid
|
|
||||||
var GRIDSPACE = 400
|
|
||||||
var row = 0
|
|
||||||
var column = 0
|
|
||||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
|
||||||
if (column == numColumns) {
|
|
||||||
column = 0
|
|
||||||
row += 1
|
|
||||||
}
|
|
||||||
var newPos = new $jit.Complex()
|
|
||||||
newPos.x = column * GRIDSPACE
|
|
||||||
newPos.y = row * GRIDSPACE
|
|
||||||
n.setPos(newPos, 'end')
|
|
||||||
column += 1
|
|
||||||
})
|
|
||||||
Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout)
|
|
||||||
} else if (layout == 'grid_full') {
|
|
||||||
// this will always be an integer, the # of nodes on your graph visualization
|
|
||||||
var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes)
|
|
||||||
// var numColumns = Math.floor(Math.sqrt(numNodes)) // the number of columns to make an even grid
|
|
||||||
// var GRIDSPACE = 400
|
|
||||||
var height = Metamaps.Visualize.mGraph.canvas.getSize(0).height
|
|
||||||
var width = Metamaps.Visualize.mGraph.canvas.getSize(0).width
|
|
||||||
var totalArea = height * width
|
|
||||||
var cellArea = totalArea / numNodes
|
|
||||||
var ratio = height / width
|
|
||||||
var cellWidth = sqrt(cellArea / ratio)
|
|
||||||
var cellHeight = cellArea / cellWidth
|
|
||||||
var row = floor(height / cellHeight)
|
|
||||||
var column = floor(width / cellWidth)
|
|
||||||
var totalCells = row * column
|
|
||||||
|
|
||||||
if (totalCells)
|
|
||||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
|
||||||
if (column == numColumns) {
|
|
||||||
column = 0
|
|
||||||
row += 1
|
|
||||||
}
|
|
||||||
var newPos = new $jit.Complex()
|
|
||||||
newPos.x = column * GRIDSPACE
|
|
||||||
newPos.y = row * GRIDSPACE
|
|
||||||
n.setPos(newPos, 'end')
|
|
||||||
column += 1
|
|
||||||
})
|
|
||||||
Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout)
|
|
||||||
} else if (layout == 'radial') {
|
|
||||||
var centerX = centerNode.getPos().x
|
|
||||||
var centerY = centerNode.getPos().y
|
|
||||||
centerNode.setPos(centerNode.getPos(), 'end')
|
|
||||||
|
|
||||||
console.log(centerNode.adjacencies)
|
|
||||||
var lineLength = 200
|
|
||||||
var usedNodes = {}
|
|
||||||
usedNodes[centerNode.id] = centerNode
|
|
||||||
var radial = function (node, level, degree) {
|
|
||||||
if (level == 1) {
|
|
||||||
var numLinksTemp = _.size(node.adjacencies)
|
|
||||||
var angleTemp = 2 * Math.PI / numLinksTemp
|
|
||||||
} else {
|
|
||||||
angleTemp = 2 * Math.PI / 20
|
|
||||||
}
|
|
||||||
node.eachAdjacency(function (a) {
|
|
||||||
var isSecondLevelNode = (centerNode.adjacencies[a.nodeTo.id] != undefined && level > 1)
|
|
||||||
if (usedNodes[a.nodeTo.id] == undefined && !isSecondLevelNode) {
|
|
||||||
var newPos = new $jit.Complex()
|
|
||||||
newPos.x = level * lineLength * Math.sin(degree) + centerX
|
|
||||||
newPos.y = level * lineLength * Math.cos(degree) + centerY
|
|
||||||
a.nodeTo.setPos(newPos, 'end')
|
|
||||||
usedNodes[a.nodeTo.id] = a.nodeTo
|
|
||||||
|
|
||||||
radial(a.nodeTo, level + 1, degree)
|
|
||||||
degree += angleTemp
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
radial(centerNode, 1, 0)
|
|
||||||
Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout)
|
|
||||||
} else if (layout == 'center_viewport') {
|
|
||||||
var lowX = 0,
|
|
||||||
lowY = 0,
|
|
||||||
highX = 0,
|
|
||||||
highY = 0
|
|
||||||
var oldOriginX = Metamaps.Visualize.mGraph.canvas.translateOffsetX
|
|
||||||
var oldOriginY = Metamaps.Visualize.mGraph.canvas.translateOffsetY
|
|
||||||
|
|
||||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
|
||||||
if (n.id === 1) {
|
|
||||||
lowX = n.getPos().x
|
|
||||||
lowY = n.getPos().y
|
|
||||||
highX = n.getPos().x
|
|
||||||
highY = n.getPos().y
|
|
||||||
}
|
|
||||||
if (n.getPos().x < lowX) lowX = n.getPos().x
|
|
||||||
if (n.getPos().y < lowY) lowY = n.getPos().y
|
|
||||||
if (n.getPos().x > highX) highX = n.getPos().x
|
|
||||||
if (n.getPos().y > highY) highY = n.getPos().y
|
|
||||||
})
|
|
||||||
console.log(lowX, lowY, highX, highY)
|
|
||||||
var newOriginX = (lowX + highX) / 2
|
|
||||||
var newOriginY = (lowY + highY) / 2
|
|
||||||
} else alert('please call function with a valid layout dammit!')
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Organize
|
|
|
@ -1,245 +0,0 @@
|
||||||
/* global Metamaps, Backbone, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Router.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.GlobalUI
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Loading
|
|
||||||
* - Metamaps.Map
|
|
||||||
* - Metamaps.Maps
|
|
||||||
* - Metamaps.Topic
|
|
||||||
* - Metamaps.Views
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
|
|
||||||
;(function () {
|
|
||||||
var Router = Backbone.Router.extend({
|
|
||||||
routes: {
|
|
||||||
'': 'home', // #home
|
|
||||||
'explore/:section': 'explore', // #explore/active
|
|
||||||
'explore/:section/:id': 'explore', // #explore/mapper/1234
|
|
||||||
'maps/:id': 'maps' // #maps/7
|
|
||||||
},
|
|
||||||
home: function () {
|
|
||||||
clearTimeout(Metamaps.Router.timeoutId)
|
|
||||||
|
|
||||||
if (Metamaps.Active.Mapper) document.title = 'Explore Active Maps | Metamaps'
|
|
||||||
else document.title = 'Home | Metamaps'
|
|
||||||
|
|
||||||
Metamaps.Router.currentSection = ''
|
|
||||||
Metamaps.Router.currentPage = ''
|
|
||||||
$('.wrapper').removeClass('mapPage topicPage')
|
|
||||||
|
|
||||||
var classes = Metamaps.Active.Mapper ? 'homePage explorePage' : 'homePage'
|
|
||||||
$('.wrapper').addClass(classes)
|
|
||||||
|
|
||||||
var navigate = function () {
|
|
||||||
Metamaps.Router.timeoutId = setTimeout(function () {
|
|
||||||
Metamaps.Router.navigate('')
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
// all this only for the logged in home page
|
|
||||||
if (Metamaps.Active.Mapper) {
|
|
||||||
$('.homeButton a').attr('href', '/')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#yield')
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.showDiv('#explore')
|
|
||||||
|
|
||||||
Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps.Active)
|
|
||||||
if (Metamaps.Maps.Active.length === 0) {
|
|
||||||
Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render
|
|
||||||
} else {
|
|
||||||
Metamaps.Views.exploreMaps.render(navigate)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// logged out home page
|
|
||||||
Metamaps.GlobalUI.hideDiv('#explore')
|
|
||||||
Metamaps.GlobalUI.showDiv('#yield')
|
|
||||||
Metamaps.Router.timeoutId = setTimeout(navigate, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.hideDiv('#infovis')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#instructions')
|
|
||||||
Metamaps.Map.end()
|
|
||||||
Metamaps.Topic.end()
|
|
||||||
Metamaps.Active.Map = null
|
|
||||||
Metamaps.Active.Topic = null
|
|
||||||
},
|
|
||||||
explore: function (section, id) {
|
|
||||||
clearTimeout(Metamaps.Router.timeoutId)
|
|
||||||
|
|
||||||
// just capitalize the variable section
|
|
||||||
// either 'featured', 'mapper', or 'active'
|
|
||||||
var capitalize = section.charAt(0).toUpperCase() + section.slice(1)
|
|
||||||
|
|
||||||
if (section === 'shared' || section === 'featured' || section === 'active' || section === 'starred') {
|
|
||||||
document.title = 'Explore ' + capitalize + ' Maps | Metamaps'
|
|
||||||
} else if (section === 'mapper') {
|
|
||||||
$.ajax({
|
|
||||||
url: '/users/' + id + '.json',
|
|
||||||
success: function (response) {
|
|
||||||
document.title = response.name + ' | Metamaps'
|
|
||||||
},
|
|
||||||
error: function () {}
|
|
||||||
})
|
|
||||||
} else if (section === 'mine') {
|
|
||||||
document.title = 'Explore My Maps | Metamaps'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Metamaps.Active.Mapper && section != 'mapper') $('.homeButton a').attr('href', '/explore/' + section)
|
|
||||||
$('.wrapper').removeClass('homePage mapPage topicPage')
|
|
||||||
$('.wrapper').addClass('explorePage')
|
|
||||||
|
|
||||||
Metamaps.Router.currentSection = 'explore'
|
|
||||||
Metamaps.Router.currentPage = section
|
|
||||||
|
|
||||||
// this will mean it's a mapper page being loaded
|
|
||||||
if (id) {
|
|
||||||
if (Metamaps.Maps.Mapper.mapperId !== id) {
|
|
||||||
// empty the collection if we are trying to load the maps
|
|
||||||
// collection of a different mapper than we had previously
|
|
||||||
Metamaps.Maps.Mapper.reset()
|
|
||||||
Metamaps.Maps.Mapper.page = 1
|
|
||||||
}
|
|
||||||
Metamaps.Maps.Mapper.mapperId = id
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps[capitalize])
|
|
||||||
|
|
||||||
var navigate = function () {
|
|
||||||
var path = '/explore/' + Metamaps.Router.currentPage
|
|
||||||
|
|
||||||
// alter url if for mapper profile page
|
|
||||||
if (Metamaps.Router.currentPage === 'mapper') {
|
|
||||||
path += '/' + Metamaps.Maps.Mapper.mapperId
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Router.navigate(path)
|
|
||||||
}
|
|
||||||
var navigateTimeout = function () {
|
|
||||||
Metamaps.Router.timeoutId = setTimeout(navigate, 300)
|
|
||||||
}
|
|
||||||
if (Metamaps.Maps[capitalize].length === 0) {
|
|
||||||
Metamaps.Loading.show()
|
|
||||||
setTimeout(function () {
|
|
||||||
Metamaps.Maps[capitalize].getMaps(navigate) // this will trigger an explore maps render
|
|
||||||
}, 300) // wait 300 milliseconds till the other animations are done to do the fetch
|
|
||||||
} else {
|
|
||||||
if (id) {
|
|
||||||
Metamaps.Views.exploreMaps.fetchUserThenRender(navigateTimeout)
|
|
||||||
} else {
|
|
||||||
Metamaps.Views.exploreMaps.render(navigateTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.showDiv('#explore')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#yield')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#infovis')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#instructions')
|
|
||||||
Metamaps.Map.end()
|
|
||||||
Metamaps.Topic.end()
|
|
||||||
Metamaps.Active.Map = null
|
|
||||||
Metamaps.Active.Topic = null
|
|
||||||
},
|
|
||||||
maps: function (id) {
|
|
||||||
clearTimeout(Metamaps.Router.timeoutId)
|
|
||||||
|
|
||||||
document.title = 'Map ' + id + ' | Metamaps'
|
|
||||||
|
|
||||||
Metamaps.Router.currentSection = 'map'
|
|
||||||
Metamaps.Router.currentPage = id
|
|
||||||
|
|
||||||
$('.wrapper').removeClass('homePage explorePage topicPage')
|
|
||||||
$('.wrapper').addClass('mapPage')
|
|
||||||
// another class will be added to wrapper if you
|
|
||||||
// can edit this map '.canEditMap'
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.hideDiv('#yield')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#explore')
|
|
||||||
|
|
||||||
// clear the visualization, if there was one, before showing its div again
|
|
||||||
if (Metamaps.Visualize.mGraph) {
|
|
||||||
Metamaps.Visualize.mGraph.graph.empty()
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas)
|
|
||||||
}
|
|
||||||
Metamaps.GlobalUI.showDiv('#infovis')
|
|
||||||
Metamaps.Topic.end()
|
|
||||||
Metamaps.Active.Topic = null
|
|
||||||
|
|
||||||
Metamaps.Loading.show()
|
|
||||||
Metamaps.Map.end()
|
|
||||||
Metamaps.Map.launch(id)
|
|
||||||
},
|
|
||||||
topics: function (id) {
|
|
||||||
clearTimeout(Metamaps.Router.timeoutId)
|
|
||||||
|
|
||||||
document.title = 'Topic ' + id + ' | Metamaps'
|
|
||||||
|
|
||||||
Metamaps.Router.currentSection = 'topic'
|
|
||||||
Metamaps.Router.currentPage = id
|
|
||||||
|
|
||||||
$('.wrapper').removeClass('homePage explorePage mapPage')
|
|
||||||
$('.wrapper').addClass('topicPage')
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.hideDiv('#yield')
|
|
||||||
Metamaps.GlobalUI.hideDiv('#explore')
|
|
||||||
|
|
||||||
// clear the visualization, if there was one, before showing its div again
|
|
||||||
if (Metamaps.Visualize.mGraph) {
|
|
||||||
Metamaps.Visualize.mGraph.graph.empty()
|
|
||||||
Metamaps.Visualize.mGraph.plot()
|
|
||||||
Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas)
|
|
||||||
}
|
|
||||||
Metamaps.GlobalUI.showDiv('#infovis')
|
|
||||||
Metamaps.Map.end()
|
|
||||||
Metamaps.Active.Map = null
|
|
||||||
|
|
||||||
Metamaps.Topic.end()
|
|
||||||
Metamaps.Topic.launch(id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Metamaps.Router = new Router()
|
|
||||||
Metamaps.Router.currentPage = ''
|
|
||||||
Metamaps.Router.currentSection = undefined
|
|
||||||
Metamaps.Router.timeoutId = undefined
|
|
||||||
|
|
||||||
Metamaps.Router.intercept = function (evt) {
|
|
||||||
var segments
|
|
||||||
|
|
||||||
var href = {
|
|
||||||
prop: $(this).prop('href'),
|
|
||||||
attr: $(this).attr('href')
|
|
||||||
}
|
|
||||||
var root = window.location.protocol + '//' + window.location.host + Backbone.history.options.root
|
|
||||||
|
|
||||||
if (href.prop && href.prop === root) href.attr = ''
|
|
||||||
|
|
||||||
if (href.prop && href.prop.slice(0, root.length) === root) {
|
|
||||||
evt.preventDefault()
|
|
||||||
|
|
||||||
segments = href.attr.split('/')
|
|
||||||
segments.splice(0, 1) // pop off the element created by the first /
|
|
||||||
|
|
||||||
if (href.attr === '') {
|
|
||||||
Metamaps.Router.home()
|
|
||||||
} else {
|
|
||||||
Metamaps.Router[segments[0]](segments[1], segments[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Router.init = function () {
|
|
||||||
Backbone.history.start({
|
|
||||||
silent: true,
|
|
||||||
pushState: true,
|
|
||||||
root: '/'
|
|
||||||
})
|
|
||||||
$(document).on('click', 'a[data-router="true"]', Metamaps.Router.intercept)
|
|
||||||
}
|
|
||||||
})()
|
|
|
@ -1,169 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Synapse.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Backbone
|
|
||||||
* - Metamaps.Control
|
|
||||||
* - Metamaps.Create
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Map
|
|
||||||
* - Metamaps.Mappings
|
|
||||||
* - Metamaps.Selected
|
|
||||||
* - Metamaps.Settings
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.Topics
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Synapse = {
|
|
||||||
// this function is to retrieve a synapse JSON object from the database
|
|
||||||
// @param id = the id of the synapse to retrieve
|
|
||||||
get: function (id, callback) {
|
|
||||||
// if the desired topic is not yet in the local topic repository, fetch it
|
|
||||||
if (Metamaps.Synapses.get(id) == undefined) {
|
|
||||||
if (!callback) {
|
|
||||||
var e = $.ajax({
|
|
||||||
url: '/synapses/' + id + '.json',
|
|
||||||
async: false
|
|
||||||
})
|
|
||||||
Metamaps.Synapses.add($.parseJSON(e.responseText))
|
|
||||||
return Metamaps.Synapses.get(id)
|
|
||||||
} else {
|
|
||||||
return $.ajax({
|
|
||||||
url: '/synapses/' + id + '.json',
|
|
||||||
success: function (data) {
|
|
||||||
Metamaps.Synapses.add(data)
|
|
||||||
callback(Metamaps.Synapses.get(id))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!callback) {
|
|
||||||
return Metamaps.Synapses.get(id)
|
|
||||||
} else {
|
|
||||||
return callback(Metamaps.Synapses.get(id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
renderSynapse: function (mapping, synapse, node1, node2, createNewInDB) {
|
|
||||||
var self = Metamaps.Synapse
|
|
||||||
|
|
||||||
var edgeOnViz
|
|
||||||
|
|
||||||
var newedge = synapse.createEdge(mapping)
|
|
||||||
|
|
||||||
Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data)
|
|
||||||
edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id)
|
|
||||||
synapse.set('edge', edgeOnViz)
|
|
||||||
synapse.updateEdge() // links the synapse and the mapping to the edge
|
|
||||||
|
|
||||||
Metamaps.Control.selectEdge(edgeOnViz)
|
|
||||||
|
|
||||||
var mappingSuccessCallback = function (mappingModel, response) {
|
|
||||||
var newSynapseData = {
|
|
||||||
mappingid: mappingModel.id,
|
|
||||||
mappableid: mappingModel.get('mappable_id')
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData])
|
|
||||||
}
|
|
||||||
var synapseSuccessCallback = function (synapseModel, response) {
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
mapping.save({ mappable_id: synapseModel.id }, {
|
|
||||||
success: mappingSuccessCallback
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Metamaps.Settings.sandbox && createNewInDB) {
|
|
||||||
if (synapse.isNew()) {
|
|
||||||
synapse.save(null, {
|
|
||||||
success: synapseSuccessCallback,
|
|
||||||
error: function (model, response) {
|
|
||||||
console.log('error saving synapse to database')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (!synapse.isNew() && Metamaps.Active.Map) {
|
|
||||||
mapping.save(null, {
|
|
||||||
success: mappingSuccessCallback
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createSynapseLocally: function () {
|
|
||||||
var self = Metamaps.Synapse,
|
|
||||||
topic1,
|
|
||||||
topic2,
|
|
||||||
node1,
|
|
||||||
node2,
|
|
||||||
synapse,
|
|
||||||
mapping
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.Map.events.editedByActiveMapper)
|
|
||||||
|
|
||||||
// for each node in this array we will create a synapse going to the position2 node.
|
|
||||||
var synapsesToCreate = []
|
|
||||||
|
|
||||||
topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id)
|
|
||||||
node2 = topic2.get('node')
|
|
||||||
|
|
||||||
var len = Metamaps.Selected.Nodes.length
|
|
||||||
if (len == 0) {
|
|
||||||
topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id)
|
|
||||||
synapsesToCreate[0] = topic1.get('node')
|
|
||||||
} else if (len > 0) {
|
|
||||||
synapsesToCreate = Metamaps.Selected.Nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < synapsesToCreate.length; i++) {
|
|
||||||
node1 = synapsesToCreate[i]
|
|
||||||
topic1 = node1.getData('topic')
|
|
||||||
synapse = new Metamaps.Backbone.Synapse({
|
|
||||||
desc: Metamaps.Create.newSynapse.description,
|
|
||||||
node1_id: topic1.isNew() ? topic1.cid : topic1.id,
|
|
||||||
node2_id: topic2.isNew() ? topic2.cid : topic2.id,
|
|
||||||
})
|
|
||||||
Metamaps.Synapses.add(synapse)
|
|
||||||
|
|
||||||
mapping = new Metamaps.Backbone.Mapping({
|
|
||||||
mappable_type: 'Synapse',
|
|
||||||
mappable_id: synapse.cid,
|
|
||||||
})
|
|
||||||
Metamaps.Mappings.add(mapping)
|
|
||||||
|
|
||||||
// this function also includes the creation of the synapse in the database
|
|
||||||
self.renderSynapse(mapping, synapse, node1, node2, true)
|
|
||||||
} // for each in synapsesToCreate
|
|
||||||
|
|
||||||
Metamaps.Create.newSynapse.hide()
|
|
||||||
},
|
|
||||||
getSynapseFromAutocomplete: function (id) {
|
|
||||||
var self = Metamaps.Synapse,
|
|
||||||
topic1,
|
|
||||||
topic2,
|
|
||||||
node1,
|
|
||||||
node2
|
|
||||||
|
|
||||||
var synapse = self.get(id)
|
|
||||||
|
|
||||||
var mapping = new Metamaps.Backbone.Mapping({
|
|
||||||
mappable_type: 'Synapse',
|
|
||||||
mappable_id: synapse.id,
|
|
||||||
})
|
|
||||||
Metamaps.Mappings.add(mapping)
|
|
||||||
|
|
||||||
topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id)
|
|
||||||
node1 = topic1.get('node')
|
|
||||||
topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id)
|
|
||||||
node2 = topic2.get('node')
|
|
||||||
Metamaps.Create.newSynapse.hide()
|
|
||||||
|
|
||||||
self.renderSynapse(mapping, synapse, node1, node2, true)
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Synapse
|
|
|
@ -1,390 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Topic.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.Backbone
|
|
||||||
* - Metamaps.Backbone
|
|
||||||
* - Metamaps.Create
|
|
||||||
* - Metamaps.Creators
|
|
||||||
* - Metamaps.Famous
|
|
||||||
* - Metamaps.Filter
|
|
||||||
* - Metamaps.GlobalUI
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Mappings
|
|
||||||
* - Metamaps.Selected
|
|
||||||
* - Metamaps.Settings
|
|
||||||
* - Metamaps.SynapseCard
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.TopicCard
|
|
||||||
* - Metamaps.Topics
|
|
||||||
* - Metamaps.Util
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
* - Metamaps.tempInit
|
|
||||||
* - Metamaps.tempNode
|
|
||||||
* - Metamaps.tempNode2
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Topic = {
|
|
||||||
// this function is to retrieve a topic JSON object from the database
|
|
||||||
// @param id = the id of the topic to retrieve
|
|
||||||
get: function (id, callback) {
|
|
||||||
// if the desired topic is not yet in the local topic repository, fetch it
|
|
||||||
if (Metamaps.Topics.get(id) == undefined) {
|
|
||||||
// console.log("Ajax call!")
|
|
||||||
if (!callback) {
|
|
||||||
var e = $.ajax({
|
|
||||||
url: '/topics/' + id + '.json',
|
|
||||||
async: false
|
|
||||||
})
|
|
||||||
Metamaps.Topics.add($.parseJSON(e.responseText))
|
|
||||||
return Metamaps.Topics.get(id)
|
|
||||||
} else {
|
|
||||||
return $.ajax({
|
|
||||||
url: '/topics/' + id + '.json',
|
|
||||||
success: function (data) {
|
|
||||||
Metamaps.Topics.add(data)
|
|
||||||
callback(Metamaps.Topics.get(id))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!callback) {
|
|
||||||
return Metamaps.Topics.get(id)
|
|
||||||
} else {
|
|
||||||
return callback(Metamaps.Topics.get(id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
launch: function (id) {
|
|
||||||
var bb = Metamaps.Backbone
|
|
||||||
var start = function (data) {
|
|
||||||
Metamaps.Active.Topic = new bb.Topic(data.topic)
|
|
||||||
Metamaps.Creators = new bb.MapperCollection(data.creators)
|
|
||||||
Metamaps.Topics = new bb.TopicCollection([data.topic].concat(data.relatives))
|
|
||||||
Metamaps.Synapses = new bb.SynapseCollection(data.synapses)
|
|
||||||
Metamaps.Backbone.attachCollectionEvents()
|
|
||||||
|
|
||||||
// set filter mapper H3 text
|
|
||||||
$('#filter_by_mapper h3').html('CREATORS')
|
|
||||||
|
|
||||||
// build and render the visualization
|
|
||||||
Metamaps.Visualize.type = 'RGraph'
|
|
||||||
Metamaps.JIT.prepareVizData()
|
|
||||||
|
|
||||||
// update filters
|
|
||||||
Metamaps.Filter.reset()
|
|
||||||
|
|
||||||
// reset selected arrays
|
|
||||||
Metamaps.Selected.reset()
|
|
||||||
|
|
||||||
// these three update the actual filter box with the right list items
|
|
||||||
Metamaps.Filter.checkMetacodes()
|
|
||||||
Metamaps.Filter.checkSynapses()
|
|
||||||
Metamaps.Filter.checkMappers()
|
|
||||||
|
|
||||||
// for mobile
|
|
||||||
$('#header_content').html(Metamaps.Active.Topic.get('name'))
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/topics/' + id + '/network.json',
|
|
||||||
success: start
|
|
||||||
})
|
|
||||||
},
|
|
||||||
end: function () {
|
|
||||||
if (Metamaps.Active.Topic) {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Metamaps.TopicCard.hideCard()
|
|
||||||
Metamaps.SynapseCard.hideCard()
|
|
||||||
Metamaps.Filter.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
centerOn: function (nodeid, callback) {
|
|
||||||
// don't clash with fetchRelatives
|
|
||||||
if (!Metamaps.Visualize.mGraph.busy) {
|
|
||||||
Metamaps.Visualize.mGraph.onClick(nodeid, {
|
|
||||||
hideLabels: false,
|
|
||||||
duration: 1000,
|
|
||||||
onComplete: function () {
|
|
||||||
if (callback) callback()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Metamaps.Router.navigate('/topics/' + nodeid)
|
|
||||||
Metamaps.Active.Topic = Metamaps.Topics.get(nodeid)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchRelatives: function (nodes, metacode_id) {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
var node = $.isArray(nodes) ? nodes[0] : nodes
|
|
||||||
|
|
||||||
var topics = Metamaps.Topics.map(function (t) { return t.id })
|
|
||||||
var topics_string = topics.join()
|
|
||||||
|
|
||||||
var creators = Metamaps.Creators.map(function (t) { return t.id })
|
|
||||||
var creators_string = creators.join()
|
|
||||||
|
|
||||||
var topic = node.getData('topic')
|
|
||||||
|
|
||||||
var successCallback;
|
|
||||||
successCallback = function (data) {
|
|
||||||
if (Metamaps.Visualize.mGraph.busy) {
|
|
||||||
// don't clash with centerOn
|
|
||||||
window.setTimeout(function() { successCallback(data) }, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (data.creators.length > 0) Metamaps.Creators.add(data.creators)
|
|
||||||
if (data.topics.length > 0) Metamaps.Topics.add(data.topics)
|
|
||||||
if (data.synapses.length > 0) Metamaps.Synapses.add(data.synapses)
|
|
||||||
|
|
||||||
var topicColl = new Metamaps.Backbone.TopicCollection(data.topics)
|
|
||||||
topicColl.add(topic)
|
|
||||||
var synapseColl = new Metamaps.Backbone.SynapseCollection(data.synapses)
|
|
||||||
|
|
||||||
var graph = Metamaps.JIT.convertModelsToJIT(topicColl, synapseColl)[0]
|
|
||||||
Metamaps.Visualize.mGraph.op.sum(graph, {
|
|
||||||
type: 'fade',
|
|
||||||
duration: 500,
|
|
||||||
hideLabels: false
|
|
||||||
})
|
|
||||||
|
|
||||||
var i, l, t, s
|
|
||||||
|
|
||||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
|
||||||
t = Metamaps.Topics.get(n.id)
|
|
||||||
t.set({ node: n }, { silent: true })
|
|
||||||
t.updateNode()
|
|
||||||
|
|
||||||
n.eachAdjacency(function (edge) {
|
|
||||||
if (!edge.getData('init')) {
|
|
||||||
edge.setData('init', true)
|
|
||||||
|
|
||||||
l = edge.getData('synapseIDs').length
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
s = Metamaps.Synapses.get(edge.getData('synapseIDs')[i])
|
|
||||||
s.set({ edge: edge }, { silent: true })
|
|
||||||
s.updateEdge()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if ($.isArray(nodes) && nodes.length > 1) {
|
|
||||||
self.fetchRelatives(nodes.slice(1), metacode_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var paramsString = metacode_id ? 'metacode=' + metacode_id + '&' : ''
|
|
||||||
paramsString += 'network=' + topics_string + '&creators=' + creators_string
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: '/topics/' + topic.id + '/relatives.json?' + paramsString,
|
|
||||||
success: successCallback,
|
|
||||||
error: function () {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter) {
|
|
||||||
var self = Metamaps.Topic
|
|
||||||
|
|
||||||
var nodeOnViz, tempPos
|
|
||||||
|
|
||||||
var newnode = topic.createNode()
|
|
||||||
|
|
||||||
var midpoint = {}, pixelPos
|
|
||||||
|
|
||||||
if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) {
|
|
||||||
Metamaps.Visualize.mGraph.graph.addNode(newnode)
|
|
||||||
nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id)
|
|
||||||
topic.set('node', nodeOnViz, {silent: true})
|
|
||||||
topic.updateNode() // links the topic and the mapping to the node
|
|
||||||
|
|
||||||
nodeOnViz.setData('dim', 1, 'start')
|
|
||||||
nodeOnViz.setData('dim', 25, 'end')
|
|
||||||
if (Metamaps.Visualize.type === 'RGraph') {
|
|
||||||
tempPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'))
|
|
||||||
tempPos = tempPos.toPolar()
|
|
||||||
nodeOnViz.setPos(tempPos, 'current')
|
|
||||||
nodeOnViz.setPos(tempPos, 'start')
|
|
||||||
nodeOnViz.setPos(tempPos, 'end')
|
|
||||||
} else if (Metamaps.Visualize.type === 'ForceDirected') {
|
|
||||||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current')
|
|
||||||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start')
|
|
||||||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end')
|
|
||||||
}
|
|
||||||
if (Metamaps.Create.newTopic.addSynapse && permitCreateSynapseAfter) {
|
|
||||||
Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id
|
|
||||||
|
|
||||||
// position the form
|
|
||||||
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')
|
|
||||||
// show the form
|
|
||||||
Metamaps.Create.newSynapse.open()
|
|
||||||
Metamaps.Visualize.mGraph.fx.animate({
|
|
||||||
modes: ['node-property:dim'],
|
|
||||||
duration: 500,
|
|
||||||
onComplete: function () {
|
|
||||||
Metamaps.tempNode = null
|
|
||||||
Metamaps.tempNode2 = null
|
|
||||||
Metamaps.tempInit = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas)
|
|
||||||
Metamaps.Visualize.mGraph.fx.animate({
|
|
||||||
modes: ['node-property:dim'],
|
|
||||||
duration: 500,
|
|
||||||
onComplete: function () {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Metamaps.Visualize.mGraph.loadJSON(newnode)
|
|
||||||
nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id)
|
|
||||||
topic.set('node', nodeOnViz, {silent: true})
|
|
||||||
topic.updateNode() // links the topic and the mapping to the node
|
|
||||||
|
|
||||||
nodeOnViz.setData('dim', 1, 'start')
|
|
||||||
nodeOnViz.setData('dim', 25, 'end')
|
|
||||||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current')
|
|
||||||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start')
|
|
||||||
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end')
|
|
||||||
Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas)
|
|
||||||
Metamaps.Visualize.mGraph.fx.animate({
|
|
||||||
modes: ['node-property:dim'],
|
|
||||||
duration: 500,
|
|
||||||
onComplete: function () {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var mappingSuccessCallback = function (mappingModel, response) {
|
|
||||||
var newTopicData = {
|
|
||||||
mappingid: mappingModel.id,
|
|
||||||
mappableid: mappingModel.get('mappable_id')
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData])
|
|
||||||
}
|
|
||||||
var topicSuccessCallback = function (topicModel, response) {
|
|
||||||
if (Metamaps.Active.Map) {
|
|
||||||
mapping.save({ mappable_id: topicModel.id }, {
|
|
||||||
success: mappingSuccessCallback,
|
|
||||||
error: function (model, response) {
|
|
||||||
console.log('error saving mapping to database')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Metamaps.Create.newTopic.addSynapse) {
|
|
||||||
Metamaps.Create.newSynapse.topic2id = topicModel.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Metamaps.Settings.sandbox && createNewInDB) {
|
|
||||||
if (topic.isNew()) {
|
|
||||||
topic.save(null, {
|
|
||||||
success: topicSuccessCallback,
|
|
||||||
error: function (model, response) {
|
|
||||||
console.log('error saving topic to database')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (!topic.isNew() && Metamaps.Active.Map) {
|
|
||||||
mapping.save(null, {
|
|
||||||
success: mappingSuccessCallback
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createTopicLocally: function () {
|
|
||||||
var self = Metamaps.Topic
|
|
||||||
|
|
||||||
if (Metamaps.Create.newTopic.name === '') {
|
|
||||||
Metamaps.GlobalUI.notifyUser('Please enter a topic title...')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide the 'double-click to add a topic' message
|
|
||||||
Metamaps.GlobalUI.hideDiv('#instructions')
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.Map.events.editedByActiveMapper)
|
|
||||||
|
|
||||||
var metacode = Metamaps.Metacodes.get(Metamaps.Create.newTopic.metacode)
|
|
||||||
|
|
||||||
var topic = new Metamaps.Backbone.Topic({
|
|
||||||
name: Metamaps.Create.newTopic.name,
|
|
||||||
metacode_id: metacode.id,
|
|
||||||
defer_to_map_id: Metamaps.Active.Map.id
|
|
||||||
})
|
|
||||||
Metamaps.Topics.add(topic)
|
|
||||||
|
|
||||||
if (Metamaps.Create.newTopic.pinned) {
|
|
||||||
var nextCoords = Metamaps.Map.getNextCoord()
|
|
||||||
}
|
|
||||||
var mapping = new Metamaps.Backbone.Mapping({
|
|
||||||
xloc: nextCoords ? nextCoords.x : Metamaps.Create.newTopic.x,
|
|
||||||
yloc: nextCoords ? nextCoords.y : Metamaps.Create.newTopic.y,
|
|
||||||
mappable_id: topic.cid,
|
|
||||||
mappable_type: 'Topic',
|
|
||||||
})
|
|
||||||
Metamaps.Mappings.add(mapping)
|
|
||||||
|
|
||||||
// these can't happen until the value is retrieved, which happens in the line above
|
|
||||||
Metamaps.Create.newTopic.hide()
|
|
||||||
|
|
||||||
self.renderTopic(mapping, topic, true, true) // this function also includes the creation of the topic in the database
|
|
||||||
},
|
|
||||||
getTopicFromAutocomplete: function (id) {
|
|
||||||
var self = Metamaps.Topic
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.Map.events.editedByActiveMapper)
|
|
||||||
|
|
||||||
Metamaps.Create.newTopic.hide()
|
|
||||||
|
|
||||||
var topic = self.get(id)
|
|
||||||
|
|
||||||
if (Metamaps.Create.newTopic.pinned) {
|
|
||||||
var nextCoords = Metamaps.Map.getNextCoord()
|
|
||||||
}
|
|
||||||
var mapping = new Metamaps.Backbone.Mapping({
|
|
||||||
xloc: nextCoords ? nextCoords.x : Metamaps.Create.newTopic.x,
|
|
||||||
yloc: nextCoords ? nextCoords.y : Metamaps.Create.newTopic.y,
|
|
||||||
mappable_type: 'Topic',
|
|
||||||
mappable_id: topic.id,
|
|
||||||
})
|
|
||||||
Metamaps.Mappings.add(mapping)
|
|
||||||
|
|
||||||
self.renderTopic(mapping, topic, true, true)
|
|
||||||
},
|
|
||||||
getTopicFromSearch: function (event, id) {
|
|
||||||
var self = Metamaps.Topic
|
|
||||||
|
|
||||||
$(document).trigger(Metamaps.Map.events.editedByActiveMapper)
|
|
||||||
|
|
||||||
var topic = self.get(id)
|
|
||||||
|
|
||||||
var nextCoords = Metamaps.Map.getNextCoord()
|
|
||||||
var mapping = new Metamaps.Backbone.Mapping({
|
|
||||||
xloc: nextCoords.x,
|
|
||||||
yloc: nextCoords.y,
|
|
||||||
mappable_type: 'Topic',
|
|
||||||
mappable_id: topic.id,
|
|
||||||
})
|
|
||||||
Metamaps.Mappings.add(mapping)
|
|
||||||
|
|
||||||
self.renderTopic(mapping, topic, true, true)
|
|
||||||
|
|
||||||
Metamaps.GlobalUI.notifyUser('Topic was added to your map!')
|
|
||||||
|
|
||||||
event.stopPropagation()
|
|
||||||
event.preventDefault()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Topic
|
|
|
@ -1,130 +0,0 @@
|
||||||
/* global Metamaps */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Util.js
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Util = {
|
|
||||||
// helper function to determine how many lines are needed
|
|
||||||
// Line Splitter Function
|
|
||||||
// copyright Stephen Chapman, 19th April 2006
|
|
||||||
// you may copy this code but please keep the copyright notice as well
|
|
||||||
splitLine: function (st, n) {
|
|
||||||
var b = ''
|
|
||||||
var s = st ? st : ''
|
|
||||||
while (s.length > n) {
|
|
||||||
var c = s.substring(0, n)
|
|
||||||
var d = c.lastIndexOf(' ')
|
|
||||||
var e = c.lastIndexOf('\n')
|
|
||||||
if (e != -1) d = e
|
|
||||||
if (d == -1) d = n
|
|
||||||
b += c.substring(0, d) + '\n'
|
|
||||||
s = s.substring(d + 1)
|
|
||||||
}
|
|
||||||
return b + s
|
|
||||||
},
|
|
||||||
nowDateFormatted: function () {
|
|
||||||
var date = new Date(Date.now())
|
|
||||||
var month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1)
|
|
||||||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
|
||||||
var year = date.getFullYear()
|
|
||||||
|
|
||||||
return month + '/' + day + '/' + year
|
|
||||||
},
|
|
||||||
decodeEntities: function (desc) {
|
|
||||||
var str, temp = document.createElement('p')
|
|
||||||
temp.innerHTML = desc // browser handles the topics
|
|
||||||
str = temp.textContent || temp.innerText
|
|
||||||
temp = null // delete the element
|
|
||||||
return str
|
|
||||||
}, // decodeEntities
|
|
||||||
getDistance: function (p1, p2) {
|
|
||||||
return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2))
|
|
||||||
},
|
|
||||||
coordsToPixels: function (coords) {
|
|
||||||
if (Metamaps.Visualize.mGraph) {
|
|
||||||
var canvas = Metamaps.Visualize.mGraph.canvas,
|
|
||||||
s = canvas.getSize(),
|
|
||||||
p = canvas.getPos(),
|
|
||||||
ox = canvas.translateOffsetX,
|
|
||||||
oy = canvas.translateOffsetY,
|
|
||||||
sx = canvas.scaleOffsetX,
|
|
||||||
sy = canvas.scaleOffsetY
|
|
||||||
var pixels = {
|
|
||||||
x: (coords.x / (1 / sx)) + p.x + s.width / 2 + ox,
|
|
||||||
y: (coords.y / (1 / sy)) + p.y + s.height / 2 + oy
|
|
||||||
}
|
|
||||||
return pixels
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pixelsToCoords: function (pixels) {
|
|
||||||
var coords
|
|
||||||
if (Metamaps.Visualize.mGraph) {
|
|
||||||
var canvas = Metamaps.Visualize.mGraph.canvas,
|
|
||||||
s = canvas.getSize(),
|
|
||||||
p = canvas.getPos(),
|
|
||||||
ox = canvas.translateOffsetX,
|
|
||||||
oy = canvas.translateOffsetY,
|
|
||||||
sx = canvas.scaleOffsetX,
|
|
||||||
sy = canvas.scaleOffsetY
|
|
||||||
coords = {
|
|
||||||
x: (pixels.x - p.x - s.width / 2 - ox) * (1 / sx),
|
|
||||||
y: (pixels.y - p.y - s.height / 2 - oy) * (1 / sy),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
coords = {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return coords
|
|
||||||
},
|
|
||||||
getPastelColor: function () {
|
|
||||||
var r = (Math.round(Math.random() * 127) + 127).toString(16)
|
|
||||||
var g = (Math.round(Math.random() * 127) + 127).toString(16)
|
|
||||||
var b = (Math.round(Math.random() * 127) + 127).toString(16)
|
|
||||||
return Metamaps.Util.colorLuminance('#' + r + g + b, -0.4)
|
|
||||||
},
|
|
||||||
// darkens a hex value by 'lum' percentage
|
|
||||||
colorLuminance: function (hex, lum) {
|
|
||||||
// validate hex string
|
|
||||||
hex = String(hex).replace(/[^0-9a-f]/gi, '')
|
|
||||||
if (hex.length < 6) {
|
|
||||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
|
|
||||||
}
|
|
||||||
lum = lum || 0
|
|
||||||
|
|
||||||
// convert to decimal and change luminosity
|
|
||||||
var rgb = '#', c, i
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
c = parseInt(hex.substr(i * 2, 2), 16)
|
|
||||||
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16)
|
|
||||||
rgb += ('00' + c).substr(c.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rgb
|
|
||||||
},
|
|
||||||
generateOptionsList: function (data) {
|
|
||||||
var newlist = ''
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
newlist = newlist + '<option value="' + data[i]['id'] + '">' + data[i]['1'][1] + '</option>'
|
|
||||||
}
|
|
||||||
return newlist
|
|
||||||
},
|
|
||||||
checkURLisImage: function (url) {
|
|
||||||
// when the page reloads the following regular expression will be screwed up
|
|
||||||
// please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/
|
|
||||||
return (url.match(/\.(jpeg|jpg|gif|png)$/) != null)
|
|
||||||
},
|
|
||||||
checkURLisYoutubeVideo: function (url) {
|
|
||||||
return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null)
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Util
|
|
|
@ -1,87 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.Views.js.erb
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Loading
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.ReactComponents
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Views = {
|
|
||||||
exploreMaps: {
|
|
||||||
setCollection: function (collection) {
|
|
||||||
var self = Metamaps.Views.exploreMaps
|
|
||||||
|
|
||||||
if (self.collection) {
|
|
||||||
self.collection.off('add', self.render)
|
|
||||||
self.collection.off('successOnFetch', self.handleSuccess)
|
|
||||||
self.collection.off('errorOnFetch', self.handleError)
|
|
||||||
}
|
|
||||||
self.collection = collection
|
|
||||||
self.collection.on('add', self.render)
|
|
||||||
self.collection.on('successOnFetch', self.handleSuccess)
|
|
||||||
self.collection.on('errorOnFetch', self.handleError)
|
|
||||||
},
|
|
||||||
render: function (mapperObj, cb) {
|
|
||||||
var self = Metamaps.Views.exploreMaps
|
|
||||||
|
|
||||||
if (typeof mapperObj === 'function') {
|
|
||||||
cb = mapperObj
|
|
||||||
mapperObj = null
|
|
||||||
}
|
|
||||||
|
|
||||||
var exploreObj = {
|
|
||||||
currentUser: Metamaps.Active.Mapper,
|
|
||||||
section: self.collection.id,
|
|
||||||
displayStyle: 'grid',
|
|
||||||
maps: self.collection,
|
|
||||||
moreToLoad: self.collection.page != 'loadedAll',
|
|
||||||
user: mapperObj,
|
|
||||||
loadMore: self.loadMore
|
|
||||||
}
|
|
||||||
ReactDOM.render(
|
|
||||||
React.createElement(Metamaps.ReactComponents.Maps, exploreObj),
|
|
||||||
document.getElementById('explore')
|
|
||||||
)
|
|
||||||
|
|
||||||
if (cb) cb()
|
|
||||||
Metamaps.Loading.hide()
|
|
||||||
},
|
|
||||||
loadMore: function () {
|
|
||||||
var self = Metamaps.Views.exploreMaps
|
|
||||||
|
|
||||||
if (self.collection.page != "loadedAll") {
|
|
||||||
self.collection.getMaps()
|
|
||||||
}
|
|
||||||
else self.render()
|
|
||||||
},
|
|
||||||
handleSuccess: function (cb) {
|
|
||||||
var self = Metamaps.Views.exploreMaps
|
|
||||||
|
|
||||||
if (self.collection && self.collection.id === 'mapper') {
|
|
||||||
self.fetchUserThenRender(cb)
|
|
||||||
} else {
|
|
||||||
self.render(cb)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleError: function () {
|
|
||||||
console.log('error loading maps!') // TODO
|
|
||||||
},
|
|
||||||
fetchUserThenRender: function (cb) {
|
|
||||||
var self = Metamaps.Views.exploreMaps
|
|
||||||
|
|
||||||
// first load the mapper object and then call the render function
|
|
||||||
$.ajax({
|
|
||||||
url: '/users/' + self.collection.mapperId + '/details.json',
|
|
||||||
success: function (response) {
|
|
||||||
self.render(response, cb)
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
self.render(cb)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,219 +0,0 @@
|
||||||
/* global Metamaps, $ */
|
|
||||||
/*
|
|
||||||
* Metamaps.Visualize
|
|
||||||
*
|
|
||||||
* Dependencies:
|
|
||||||
* - Metamaps.Active
|
|
||||||
* - Metamaps.JIT
|
|
||||||
* - Metamaps.Loading
|
|
||||||
* - Metamaps.Metacodes
|
|
||||||
* - Metamaps.Router
|
|
||||||
* - Metamaps.Synapses
|
|
||||||
* - Metamaps.TopicCard
|
|
||||||
* - Metamaps.Topics
|
|
||||||
* - Metamaps.Touch
|
|
||||||
* - Metamaps.Visualize
|
|
||||||
*/
|
|
||||||
|
|
||||||
Metamaps.Visualize = {
|
|
||||||
mGraph: null, // a reference to the graph object.
|
|
||||||
cameraPosition: null, // stores the camera position when using a 3D visualization
|
|
||||||
type: 'ForceDirected', // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D"
|
|
||||||
loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created
|
|
||||||
init: function () {
|
|
||||||
var self = Metamaps.Visualize
|
|
||||||
// disable awkward dragging of the canvas element that would sometimes happen
|
|
||||||
$('#infovis-canvas').on('dragstart', function (event) {
|
|
||||||
event.preventDefault()
|
|
||||||
})
|
|
||||||
|
|
||||||
// prevent touch events on the canvas from default behaviour
|
|
||||||
$('#infovis-canvas').bind('touchstart', function (event) {
|
|
||||||
event.preventDefault()
|
|
||||||
self.mGraph.events.touched = true
|
|
||||||
})
|
|
||||||
|
|
||||||
// prevent touch events on the canvas from default behaviour
|
|
||||||
$('#infovis-canvas').bind('touchmove', function (event) {
|
|
||||||
// Metamaps.JIT.touchPanZoomHandler(event)
|
|
||||||
})
|
|
||||||
|
|
||||||
// prevent touch events on the canvas from default behaviour
|
|
||||||
$('#infovis-canvas').bind('touchend touchcancel', function (event) {
|
|
||||||
lastDist = 0
|
|
||||||
if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard()
|
|
||||||
self.mGraph.events.touched = self.mGraph.events.touchMoved = false
|
|
||||||
Metamaps.Touch.touchDragNode = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
computePositions: function () {
|
|
||||||
var self = Metamaps.Visualize,
|
|
||||||
mapping
|
|
||||||
|
|
||||||
if (self.type == 'RGraph') {
|
|
||||||
var i, l, startPos, endPos, topic, synapse
|
|
||||||
|
|
||||||
self.mGraph.graph.eachNode(function (n) {
|
|
||||||
topic = Metamaps.Topics.get(n.id)
|
|
||||||
topic.set({ node: n }, { silent: true })
|
|
||||||
topic.updateNode()
|
|
||||||
|
|
||||||
n.eachAdjacency(function (edge) {
|
|
||||||
if (!edge.getData('init')) {
|
|
||||||
edge.setData('init', true)
|
|
||||||
|
|
||||||
l = edge.getData('synapseIDs').length
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i])
|
|
||||||
synapse.set({ edge: edge }, { silent: true })
|
|
||||||
synapse.updateEdge()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var pos = n.getPos()
|
|
||||||
pos.setc(-200, -200)
|
|
||||||
})
|
|
||||||
self.mGraph.compute('end')
|
|
||||||
} else if (self.type == 'ForceDirected') {
|
|
||||||
var i, l, startPos, endPos, topic, synapse
|
|
||||||
|
|
||||||
self.mGraph.graph.eachNode(function (n) {
|
|
||||||
topic = Metamaps.Topics.get(n.id)
|
|
||||||
topic.set({ node: n }, { silent: true })
|
|
||||||
topic.updateNode()
|
|
||||||
mapping = topic.getMapping()
|
|
||||||
|
|
||||||
n.eachAdjacency(function (edge) {
|
|
||||||
if (!edge.getData('init')) {
|
|
||||||
edge.setData('init', true)
|
|
||||||
|
|
||||||
l = edge.getData('synapseIDs').length
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i])
|
|
||||||
synapse.set({ edge: edge }, { silent: true })
|
|
||||||
synapse.updateEdge()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
startPos = new $jit.Complex(0, 0)
|
|
||||||
endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'))
|
|
||||||
n.setPos(startPos, 'start')
|
|
||||||
n.setPos(endPos, 'end')
|
|
||||||
})
|
|
||||||
} else if (self.type == 'ForceDirected3D') {
|
|
||||||
self.mGraph.compute()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* render does the heavy lifting of creating the engine that renders the graph with the properties we desire
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
render: function () {
|
|
||||||
var self = Metamaps.Visualize, RGraphSettings, FDSettings
|
|
||||||
|
|
||||||
if (self.type == 'RGraph' && (!self.mGraph || self.mGraph instanceof $jit.ForceDirected)) {
|
|
||||||
// clear the previous canvas from #infovis
|
|
||||||
$('#infovis').empty()
|
|
||||||
|
|
||||||
RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings)
|
|
||||||
|
|
||||||
$jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings)
|
|
||||||
$jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings)
|
|
||||||
|
|
||||||
RGraphSettings.width = $(document).width()
|
|
||||||
RGraphSettings.height = $(document).height()
|
|
||||||
RGraphSettings.background = Metamaps.JIT.RGraph.background
|
|
||||||
RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance
|
|
||||||
|
|
||||||
self.mGraph = new $jit.RGraph(RGraphSettings)
|
|
||||||
} else if (self.type == 'ForceDirected' && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) {
|
|
||||||
// clear the previous canvas from #infovis
|
|
||||||
$('#infovis').empty()
|
|
||||||
|
|
||||||
FDSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings)
|
|
||||||
|
|
||||||
$jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings)
|
|
||||||
$jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings)
|
|
||||||
|
|
||||||
FDSettings.width = $('body').width()
|
|
||||||
FDSettings.height = $('body').height()
|
|
||||||
|
|
||||||
self.mGraph = new $jit.ForceDirected(FDSettings)
|
|
||||||
} else if (self.type == 'ForceDirected3D' && !self.mGraph) {
|
|
||||||
// clear the previous canvas from #infovis
|
|
||||||
$('#infovis').empty()
|
|
||||||
|
|
||||||
// init ForceDirected3D
|
|
||||||
self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings)
|
|
||||||
self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position
|
|
||||||
} else {
|
|
||||||
self.mGraph.graph.empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (self.type == 'ForceDirected' && Metamaps.Active.Mapper) $.post('/maps/' + Metamaps.Active.Map.id + '/events/user_presence')
|
|
||||||
|
|
||||||
function runAnimation () {
|
|
||||||
Metamaps.Loading.hide()
|
|
||||||
// load JSON data, if it's not empty
|
|
||||||
if (!self.loadLater) {
|
|
||||||
// load JSON data.
|
|
||||||
var rootIndex = 0
|
|
||||||
if (Metamaps.Active.Topic) {
|
|
||||||
var node = _.find(Metamaps.JIT.vizData, function (node) {
|
|
||||||
return node.id === Metamaps.Active.Topic.id
|
|
||||||
})
|
|
||||||
rootIndex = _.indexOf(Metamaps.JIT.vizData, node)
|
|
||||||
}
|
|
||||||
self.mGraph.loadJSON(Metamaps.JIT.vizData, rootIndex)
|
|
||||||
// compute positions and plot.
|
|
||||||
self.computePositions()
|
|
||||||
self.mGraph.busy = true
|
|
||||||
if (self.type == 'RGraph') {
|
|
||||||
self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate)
|
|
||||||
} else if (self.type == 'ForceDirected') {
|
|
||||||
self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout)
|
|
||||||
} else if (self.type == 'ForceDirected3D') {
|
|
||||||
self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// hold until all the needed metacode images are loaded
|
|
||||||
// hold for a maximum of 80 passes, or 4 seconds of waiting time
|
|
||||||
var tries = 0
|
|
||||||
function hold () {
|
|
||||||
var unique = _.uniq(Metamaps.Topics.models, function (metacode) { return metacode.get('metacode_id'); }),
|
|
||||||
requiredMetacodes = _.map(unique, function (metacode) { return metacode.get('metacode_id'); }),
|
|
||||||
loadedCount = 0
|
|
||||||
|
|
||||||
_.each(requiredMetacodes, function (metacode_id) {
|
|
||||||
var metacode = Metamaps.Metacodes.get(metacode_id),
|
|
||||||
img = metacode ? metacode.get('image') : false
|
|
||||||
|
|
||||||
if (img && (img.complete || (typeof img.naturalWidth !== 'undefined' && img.naturalWidth !== 0))) {
|
|
||||||
loadedCount += 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (loadedCount === requiredMetacodes.length || tries > 80) runAnimation()
|
|
||||||
else setTimeout(function () { tries++; hold() }, 50)
|
|
||||||
}
|
|
||||||
hold()
|
|
||||||
|
|
||||||
// update the url now that the map is ready
|
|
||||||
clearTimeout(Metamaps.Router.timeoutId)
|
|
||||||
Metamaps.Router.timeoutId = setTimeout(function () {
|
|
||||||
var m = Metamaps.Active.Map
|
|
||||||
var t = Metamaps.Active.Topic
|
|
||||||
|
|
||||||
if (m && window.location.pathname !== '/maps/' + m.id) {
|
|
||||||
Metamaps.Router.navigate('/maps/' + m.id)
|
|
||||||
}
|
|
||||||
else if (t && window.location.pathname !== '/topics/' + t.id) {
|
|
||||||
Metamaps.Router.navigate('/topics/' + t.id)
|
|
||||||
}
|
|
||||||
}, 800)
|
|
||||||
}
|
|
||||||
}; // end Metamaps.Visualize
|
|
|
@ -1,74 +0,0 @@
|
||||||
/* global Metamaps */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metamaps.js.erb
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO eliminate these 5 top-level variables
|
|
||||||
Metamaps.panningInt = null
|
|
||||||
Metamaps.tempNode = null
|
|
||||||
Metamaps.tempInit = false
|
|
||||||
Metamaps.tempNode2 = null
|
|
||||||
Metamaps.VERSION = '<%= METAMAPS_VERSION %>'
|
|
||||||
|
|
||||||
/* erb variables from rails */
|
|
||||||
Metamaps.Erb = {}
|
|
||||||
Metamaps.Erb['REALTIME_SERVER'] = '<%= ENV['REALTIME_SERVER'] %>'
|
|
||||||
Metamaps.Erb['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>'
|
|
||||||
Metamaps.Erb['user.png'] = '<%= asset_path('user.png') %>'
|
|
||||||
Metamaps.Erb['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>'
|
|
||||||
Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
|
|
||||||
Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
|
||||||
Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>'
|
|
||||||
Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
|
||||||
|
|
||||||
Metamaps.Settings = {
|
|
||||||
embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages
|
|
||||||
sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database
|
|
||||||
colors: {
|
|
||||||
background: '#344A58',
|
|
||||||
synapses: {
|
|
||||||
normal: '#888888',
|
|
||||||
hover: '#888888',
|
|
||||||
selected: '#FFFFFF'
|
|
||||||
},
|
|
||||||
topics: {
|
|
||||||
selected: '#FFFFFF'
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
background: '#18202E',
|
|
||||||
text: '#DDD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Touch = {
|
|
||||||
touchPos: null, // this stores the x and y values of a current touch event
|
|
||||||
touchDragNode: null // this stores a reference to a JIT node that is being dragged
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Mouse = {
|
|
||||||
didPan: false,
|
|
||||||
didBoxZoom: false,
|
|
||||||
changeInX: 0,
|
|
||||||
changeInY: 0,
|
|
||||||
edgeHoveringOver: false,
|
|
||||||
boxStartCoordinates: false,
|
|
||||||
boxEndCoordinates: false,
|
|
||||||
synapseStartCoordinates: [],
|
|
||||||
synapseEndCoordinates: null,
|
|
||||||
lastNodeClick: 0,
|
|
||||||
lastCanvasClick: 0,
|
|
||||||
DOUBLE_CLICK_TOLERANCE: 300
|
|
||||||
}
|
|
||||||
|
|
||||||
Metamaps.Selected = {
|
|
||||||
reset: function () {
|
|
||||||
var self = Metamaps.Selected
|
|
||||||
|
|
||||||
self.Nodes = []
|
|
||||||
self.Edges = []
|
|
||||||
},
|
|
||||||
Nodes: [],
|
|
||||||
Edges: []
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// TODO document this user agent function
|
|
||||||
var labelType, useGradients, nativeTextSupport, animate
|
|
||||||
;(function () {
|
|
||||||
var ua = navigator.userAgent,
|
|
||||||
iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
|
|
||||||
typeOfCanvas = typeof HTMLCanvasElement,
|
|
||||||
nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
|
|
||||||
textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function')
|
|
||||||
// I'm setting this based on the fact that ExCanvas provides text support for IE
|
|
||||||
// and that as of today iPhone/iPad current text support is lame
|
|
||||||
labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native' : 'HTML'
|
|
||||||
nativeTextSupport = labelType == 'Native'
|
|
||||||
useGradients = nativeCanvasSupport
|
|
||||||
animate = !(iStuff || !nativeCanvasSupport)
|
|
||||||
})()
|
|
|
@ -1,343 +0,0 @@
|
||||||
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"></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/MM_sounds.mp3' %>", "<%= asset_path 'sounds/MM_sounds.ogg' %>"],
|
|
||||||
sprite: {
|
|
||||||
joinmap: [0, 561],
|
|
||||||
leavemap: [1000, 592],
|
|
||||||
receivechat: [2000, 318],
|
|
||||||
sendchat: [3000, 296],
|
|
||||||
sessioninvite: [4000, 5393, true]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
incrementUnread: function() {
|
|
||||||
this.unreadMessages++;
|
|
||||||
this.$unread.html(this.unreadMessages);
|
|
||||||
this.$unread.show();
|
|
||||||
},
|
|
||||||
addMessage: function(message, isInitial, wasMe) {
|
|
||||||
|
|
||||||
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 (!wasMe && !isInitial && this.alertSound) this.sound.play('receivechat');
|
|
||||||
},
|
|
||||||
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 = true; // 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, wasMe) {
|
|
||||||
this.messages.add(message);
|
|
||||||
Private.addMessage.call(this, message, isInitial, wasMe);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,195 +0,0 @@
|
||||||
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);
|
|
||||||
if (self.chat.alertSound) self.chat.sound.play('sendchat');
|
|
||||||
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), false, true);
|
|
||||||
$(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, wasMe) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
messages.models.forEach(function (message) {
|
|
||||||
self.chat.addMessage(message, isInitial, wasMe);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
room.events = {
|
|
||||||
newMessage: "Room:newMessage"
|
|
||||||
};
|
|
||||||
|
|
||||||
return room;
|
|
||||||
})();
|
|
|
@ -1,207 +0,0 @@
|
||||||
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;
|
|
||||||
})();
|
|
|
@ -78,11 +78,18 @@ html {
|
||||||
|
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background: #d8d9da url(<%= asset_data_uri('shattered_@2X.png') %>);
|
background: #d8d9da url(<%= asset_path('shattered_@2X.png') %>);
|
||||||
font-family: 'din-medium', helvetica, sans-serif;
|
font-family: 'din-medium', helvetica, sans-serif;
|
||||||
color: #424242;
|
color: #424242;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&.controller-main,
|
||||||
|
&.controller-maps,
|
||||||
|
&.controller-topics,
|
||||||
|
&.controller-explore {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
|
@ -142,6 +149,7 @@ button.button.btn-no:hover {
|
||||||
.toast .toast-button {
|
.toast .toast-button {
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
margin-bottom: -10px;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Utility
|
* Utility
|
||||||
|
@ -855,7 +863,7 @@ label {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
pointer-events:none;
|
pointer-events:none;
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
background-image: url(<%= asset_data_uri('user_sprite.png') %>);
|
background-image: url(<%= asset_path('user_sprite.png') %>);
|
||||||
}
|
}
|
||||||
.accountSettings .accountIcon {
|
.accountSettings .accountIcon {
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
|
@ -863,6 +871,9 @@ label {
|
||||||
.accountAdmin .accountIcon {
|
.accountAdmin .accountIcon {
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
|
.accountApps .accountIcon {
|
||||||
|
background-position: 0 -32px;
|
||||||
|
}
|
||||||
.accountInvite .accountIcon {
|
.accountInvite .accountIcon {
|
||||||
background-position: 0 -64px;
|
background-position: 0 -64px;
|
||||||
}
|
}
|
||||||
|
@ -1562,9 +1573,8 @@ h3.filterBox {
|
||||||
background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>);
|
background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>);
|
||||||
}
|
}
|
||||||
/* map info box */
|
/* map info box */
|
||||||
/* map info box */
|
|
||||||
|
|
||||||
.wrapper div.mapInfoBox {
|
.wrapper .mapInfoBox {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 40px;
|
bottom: 40px;
|
||||||
|
@ -1572,12 +1582,40 @@ h3.filterBox {
|
||||||
background-color: #424242;
|
background-color: #424242;
|
||||||
color: #F5F5F5;
|
color: #F5F5F5;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0px 3px 3px rgba(0,0,0,0.16);
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
.import-dialog{
|
||||||
|
button {
|
||||||
|
margin: 1em 0.5em;
|
||||||
|
}
|
||||||
|
.import-blue-button {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0.75em;
|
||||||
|
padding: 0.75em;
|
||||||
|
height: 3em;
|
||||||
|
background-color: #AAB0FB;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.fileupload {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0.75em;
|
||||||
|
padding: 0.75em;
|
||||||
|
height: 3em;
|
||||||
|
border: 3px dashed #AAB0FB;
|
||||||
|
width: 75%;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wrapper .mapInfoBox {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-style: normal;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0px 3px 3px rgba(0,0,0,0.16);
|
|
||||||
}
|
}
|
||||||
.requestTitle {
|
.requestTitle {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -2064,17 +2102,17 @@ and it won't be important on password protected instances */
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
position: absolute;
|
||||||
z-index: 1000000;
|
z-index: 1000000;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#lightbox_main {
|
#lightbox_main {
|
||||||
width: 800px;
|
width: 800px;
|
||||||
height: auto;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 50%;
|
top: 5vh;
|
||||||
|
height: 90vh;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
@ -2113,8 +2151,10 @@ and it won't be important on password protected instances */
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
}
|
}
|
||||||
#lightbox_content {
|
#lightbox_content {
|
||||||
width: 552px;
|
width: 800px;
|
||||||
height: 434px;
|
max-height: 90vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
background-color: #e0e0e0;
|
background-color: #e0e0e0;
|
||||||
padding: 64px 124px 64px 124px;
|
padding: 64px 124px 64px 124px;
|
||||||
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);
|
||||||
|
@ -2196,42 +2236,35 @@ and it won't be important on password protected instances */
|
||||||
color: #00bcd4;
|
color: #00bcd4;
|
||||||
}
|
}
|
||||||
.lightbox_links .lightboxAboutIcon {
|
.lightbox_links .lightboxAboutIcon {
|
||||||
background-image: url(<%= asset_data_uri('about_sprite.png') %>);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
width:32px;
|
width:32px;
|
||||||
height:32px;
|
height:32px;
|
||||||
margin:10px auto;
|
margin:10px auto;
|
||||||
}
|
}
|
||||||
#lightbox_metamapps .lightboxAboutIcon {
|
.icon_twitter .lightboxAboutIcon,
|
||||||
|
.icon_source_code .lightboxAboutIcon,
|
||||||
|
.icon_terms .lightboxAboutIcon {
|
||||||
|
background-image: url(<%= asset_data_uri('about_sprite.png') %>);
|
||||||
|
background-repeat: no-repeat;
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
#lightbox_community .lightboxAboutIcon {
|
.icon_twitter .lightboxAboutIcon {
|
||||||
background-position: -32px 0;
|
background-position: 0 0;
|
||||||
}
|
&:hover {
|
||||||
#lightbox_source .lightboxAboutIcon {
|
|
||||||
background-position: -64px 0;
|
|
||||||
}
|
|
||||||
#lightbox_blog .lightboxAboutIcon {
|
|
||||||
background-position: -96px 0;
|
|
||||||
}
|
|
||||||
#lightbox_term .lightboxAboutIcon {
|
|
||||||
background-position: -128px 0;
|
|
||||||
}
|
|
||||||
#lightbox_metamapps:hover .lightboxAboutIcon {
|
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
#lightbox_community:hover .lightboxAboutIcon {
|
|
||||||
background-position: -32px -32px;
|
|
||||||
}
|
}
|
||||||
#lightbox_source:hover .lightboxAboutIcon {
|
.icon_source_code .lightboxAboutIcon {
|
||||||
|
background-position: -64px 0;
|
||||||
|
&:hover {
|
||||||
background-position: -64px -32px;
|
background-position: -64px -32px;
|
||||||
}
|
}
|
||||||
#lightbox_blog:hover .lightboxAboutIcon {
|
|
||||||
background-position: -96px -32px;
|
|
||||||
}
|
}
|
||||||
#lightbox_term:hover .lightboxAboutIcon {
|
.icon_terms .lightboxAboutIcon {
|
||||||
|
background-position: -128px 0;
|
||||||
|
&:hover {
|
||||||
background-position: -128px -32px;
|
background-position: -128px -32px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* jquery ui tabs */
|
/* jquery ui tabs */
|
||||||
|
|
||||||
|
@ -3080,3 +3113,7 @@ script.data-gratipay-username {
|
||||||
display: inline;
|
display: inline;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
|
@ -1,17 +1,14 @@
|
||||||
.centerContent {
|
.centerContent {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 92px auto 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px 0 60px 20px;
|
width: auto;
|
||||||
width: 760px;
|
max-width: 800px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);
|
box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
-webkit-border-radius: 3px;
|
box-sizing: border-box;
|
||||||
-moz-border-radius: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #dcdcdc;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centerContent .page-header {
|
.centerContent .page-header {
|
||||||
|
@ -129,3 +126,9 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.centerContent.withPadding {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#center-container {
|
#center-container {
|
||||||
position:relative;
|
position:relative;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
@ -70,6 +69,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.CardOnGraph .title {
|
.CardOnGraph .title {
|
||||||
|
word-break: break-word;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
display: table;
|
display: table;
|
||||||
|
@ -138,11 +138,35 @@
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CardOnGraph .desc h3 {
|
/*
|
||||||
font-style:normal;
|
* Styling for Markdown in topic cards
|
||||||
margin-top:5px;
|
*/
|
||||||
|
|
||||||
|
.CardOnGraph .desc {
|
||||||
|
p, ol, ul {
|
||||||
|
padding: 0.15em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End Markdown styling
|
||||||
|
*/
|
||||||
|
|
||||||
.CardOnGraph .best_in_place_desc {
|
.CardOnGraph .best_in_place_desc {
|
||||||
display:block;
|
display:block;
|
||||||
margin-top:2px;
|
margin-top:2px;
|
||||||
|
@ -582,10 +606,10 @@ background-color: #E0E0E0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CardOnGraph .hoverForTip:hover .tip, .mapCard .hoverForTip:hover .tip, #mapContribs:hover .tip {
|
.CardOnGraph .hoverForTip:hover .tip, #mapContribs:hover .tip {
|
||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
.CardOnGraph .tip, .mapCard .tip {
|
.CardOnGraph .tip {
|
||||||
display:none;
|
display:none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: black;
|
background: black;
|
||||||
|
@ -942,160 +966,14 @@ font-family: 'din-regular', helvetica, sans-serif;
|
||||||
background-position: 0 -24px;
|
background-position: 0 -24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Map Cards */
|
|
||||||
|
|
||||||
.map {
|
|
||||||
display:inline-block;
|
|
||||||
width:220px;
|
|
||||||
height:340px;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: left;
|
|
||||||
overflow: visible;
|
|
||||||
background: #e8e8e8;
|
|
||||||
border-radius:2px;
|
|
||||||
margin:16px 16px 16px 19px;
|
|
||||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
|
||||||
}
|
|
||||||
.map:hover {
|
|
||||||
background: #dcdcdc;
|
|
||||||
}
|
|
||||||
.map.newMap {
|
|
||||||
float: left;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.map.newMap a {
|
|
||||||
height: 340px;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.newMap .newMapImage {
|
|
||||||
display: block;
|
|
||||||
width: 72px;
|
|
||||||
height: 72px;
|
|
||||||
background-image: url("<%= asset_data_uri('newmap_sprite.png') %>");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 0;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -36px;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -36px;
|
|
||||||
}
|
|
||||||
.map:hover .newMapImage {
|
|
||||||
background-position: 0 -72px;
|
|
||||||
}
|
|
||||||
.newMap span {
|
|
||||||
font-family: 'din-regular', sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 22px;
|
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
padding-top: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard {
|
|
||||||
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
|
|
||||||
display: -ms-flexbox; /* TWEENER - IE 10 */
|
|
||||||
display: -webkit-flex; /* NEW - Chrome */
|
|
||||||
display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-moz-box-direction: normal;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:308px;
|
|
||||||
padding: 16px 0;
|
|
||||||
color: #424242;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .title {
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-size:18px;
|
|
||||||
line-height:22px;
|
|
||||||
height: 44px;
|
|
||||||
display:block;
|
|
||||||
padding: 0 16px;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-box-flex: none; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
-moz-box-flex: none; /* OLD - Firefox 19- */
|
|
||||||
-webkit-flex: none; /* Chrome */
|
|
||||||
-ms-flex: none; /* IE 10 */
|
|
||||||
flex: none; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
font-family: 'din-regular', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .mapScreenshot {
|
|
||||||
width: 188px;
|
|
||||||
height: 126px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
.mapCard .mapScreenshot img {
|
|
||||||
width: 188px;
|
|
||||||
height: 126px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .scroll {
|
|
||||||
display:block;
|
|
||||||
-webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
-moz-box-flex: 1; /* OLD - Firefox 19- */
|
|
||||||
-webkit-flex: 1; /* Chrome */
|
|
||||||
-ms-flex: 1; /* IE 10 */
|
|
||||||
flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
padding:0 16px 8px;
|
|
||||||
font-family: helvetica, sans-serif;
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 12px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.mCS_no_scrollbar {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .mapMetadata {
|
|
||||||
font-family: 'din-regular', sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
position:relative;
|
|
||||||
border-top: 1px solid #BDBDBD;
|
|
||||||
-webkit-box-flex: none; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
-moz-box-flex: none; /* OLD - Firefox 19- */
|
|
||||||
-webkit-flex: none; /* Chrome */
|
|
||||||
-ms-flex: none; /* IE 10 */
|
|
||||||
flex: none; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .metadataSection {
|
|
||||||
padding: 8px 16px 0 16px;
|
|
||||||
width: 78px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapPermission {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
}
|
|
||||||
.cCountColor {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
color: #DB5D5D;
|
|
||||||
}
|
|
||||||
.tCountColor {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
color: #4FC059;
|
|
||||||
}
|
|
||||||
.sCountColor {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
color: #DAB539;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* mapper card */
|
/* mapper card */
|
||||||
|
|
||||||
.mapper {
|
.mapper {
|
||||||
float: left;
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
width:220px;
|
width:220px;
|
||||||
height:340px;
|
height:340px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -1103,7 +981,7 @@ font-family: 'din-regular', helvetica, sans-serif;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: #E0E0E0;
|
background: #E0E0E0;
|
||||||
border-radius:2px;
|
border-radius:2px;
|
||||||
margin:16px 16px 16px 19px;
|
margin:16px;
|
||||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1127,10 +1005,10 @@ font-family: 'din-regular', helvetica, sans-serif;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
padding: 0 16px;
|
padding: 0 5%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 189px;
|
width: 90%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 92px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.animations {
|
/*.animations {
|
||||||
|
@ -47,10 +49,13 @@
|
||||||
.mapElement {
|
.mapElement {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.mapPage .mapElement, .topicPage .mapElement {
|
.mapPage .mapElement,
|
||||||
|
.topicPage .mapElement {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.mapPage .mapElementHidden {
|
.mapPage .mapElementHidden,
|
||||||
|
.topicPage .mapElement.mapInfoBox,
|
||||||
|
.topicPage .mapElement.importDialog {
|
||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
.topicPage .starMap {
|
.topicPage .starMap {
|
||||||
|
@ -188,29 +193,44 @@
|
||||||
.upperRightIcon {
|
.upperRightIcon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background-image: url(<%= asset_data_uri('topright_sprite.png') %>);
|
background-image: url(<%= asset_path('topright_sprite.png') %>);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.mapPage .mapElement .importDialog {
|
||||||
|
display: none;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
.mapPage.canEditMap .mapElement .importDialog {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
.sidebarFilterIcon {
|
.sidebarFilterIcon {
|
||||||
background-position: -64px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
.sidebarForkIcon {
|
.sidebarForkIcon {
|
||||||
background-position: -96px 0;
|
background-position: -64px 0;
|
||||||
}
|
}
|
||||||
.addMap {
|
.addMap {
|
||||||
|
background-position: -96px 0;
|
||||||
|
}
|
||||||
|
.notificationsIcon {
|
||||||
background-position: -128px 0;
|
background-position: -128px 0;
|
||||||
margin-right:10px;
|
margin-right: 10px; // make it look more natural next to the account menu icon
|
||||||
|
}
|
||||||
|
.notificationsIcon:hover {
|
||||||
|
background-position: -128px -32px;
|
||||||
|
}
|
||||||
|
.importDialog:hover {
|
||||||
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
.sidebarFilterIcon:hover {
|
.sidebarFilterIcon:hover {
|
||||||
background-position: -64px -32px;
|
background-position: -32px -32px;
|
||||||
}
|
}
|
||||||
.sidebarForkIcon:hover {
|
.sidebarForkIcon:hover {
|
||||||
background-position: -96px -32px;
|
background-position: -64px -32px;
|
||||||
}
|
}
|
||||||
.addMap:hover {
|
.addMap:hover {
|
||||||
background-position: -128px -32px;
|
background-position: -96px -32px;
|
||||||
margin-right:10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,7 +345,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullWidthWrapper.withPartners {
|
.fullWidthWrapper.withPartners {
|
||||||
background: url(<%= asset_data_uri('homepage_bg_fade.png') %>) no-repeat center -300px;
|
background: url(<%= asset_path('homepage_bg_fade.png') %>) no-repeat center -300px;
|
||||||
}
|
}
|
||||||
.homeWrapper.homePartners {
|
.homeWrapper.homePartners {
|
||||||
padding: 64px 0 280px;
|
padding: 64px 0 280px;
|
||||||
|
@ -364,7 +384,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.openCheatsheet {
|
.openCheatsheet {
|
||||||
background-image: url(<%= asset_data_uri('help_sprite.png') %>);
|
background-image: url(<%= asset_path('help_sprite.png') %>);
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
}
|
}
|
||||||
.openCheatsheet:hover {
|
.openCheatsheet:hover {
|
||||||
|
@ -373,7 +393,7 @@
|
||||||
.mapInfoIcon {
|
.mapInfoIcon {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 56px; /* puts it just offscreen */
|
top: 56px; /* puts it just offscreen */
|
||||||
background-image: url(<%= asset_data_uri('mapinfo_sprite.png') %>);
|
background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
}
|
}
|
||||||
.mapInfoIcon:hover {
|
.mapInfoIcon:hover {
|
||||||
|
@ -382,8 +402,9 @@
|
||||||
.mapPage .mapInfoIcon {
|
.mapPage .mapInfoIcon {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.starMap {
|
.starMap {
|
||||||
background-image: url(<%= asset_data_uri('starmap_sprite.png') %>);
|
background-image: url(<%= asset_path('starmap_sprite.png') %>);
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
@ -437,7 +458,7 @@
|
||||||
.takeScreenshot {
|
.takeScreenshot {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>);
|
background-image: url(<%= asset_path 'screenshot_sprite.png' %>);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.takeScreenshot:hover {
|
.takeScreenshot:hover {
|
||||||
|
@ -450,15 +471,15 @@
|
||||||
.zoomExtents {
|
.zoomExtents {
|
||||||
margin-bottom:5px;
|
margin-bottom:5px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-image: url(<%= asset_data_uri('extents_sprite.png') %>);
|
background-image: url(<%= asset_path('extents_sprite.png') %>);
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomExtents:hover {
|
.zoomExtents:hover {
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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,
|
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .notificationsIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
||||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .importDialog:hover .tooltipsUnder, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,9 +535,16 @@
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.importDialog .tooltipsUnder {
|
||||||
|
left: -22px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebarFilterIcon .tooltipsUnder {
|
.sidebarFilterIcon .tooltipsUnder {
|
||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
}
|
}
|
||||||
|
.notificationsIcon .tooltipsUnder {
|
||||||
|
left: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebarForkIcon .tooltipsUnder {
|
.sidebarForkIcon .tooltipsUnder {
|
||||||
margin-left: -34px;
|
margin-left: -34px;
|
||||||
|
@ -594,7 +622,12 @@
|
||||||
border-bottom: 5px solid transparent;
|
border-bottom: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
.addMap div:after,
|
||||||
|
.importDialog div:after,
|
||||||
|
.sidebarForkIcon div:after,
|
||||||
|
.sidebarFilterIcon div:after,
|
||||||
|
.notificationsIcon div:after,
|
||||||
|
.sidebarAccountIcon .tooltipsUnder:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 40%;
|
right: 40%;
|
||||||
|
@ -605,9 +638,15 @@
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
.notificationsIcon .unread-notifications-dot:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
.sidebarFilterIcon div:after {
|
.sidebarFilterIcon div:after {
|
||||||
right: 37% !important;
|
right: 37% !important;
|
||||||
}
|
}
|
||||||
|
.notificationsIcon div:after {
|
||||||
|
right: 46% !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mapInfoIcon div:after, .openCheatsheet div:after, .starMap div:after, .openMetacodeSwitcher div:after, .pinCarousel div:after {
|
.mapInfoIcon div:after, .openCheatsheet div:after, .starMap div:after, .openMetacodeSwitcher div:after, .pinCarousel div:after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -623,7 +662,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomIn {
|
.zoomIn {
|
||||||
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>);
|
background-image: url(<%= asset_path('zoom_sprite.png') %>);
|
||||||
background-position: 0 /…0;
|
background-position: 0 /…0;
|
||||||
border-top-left-radius: 2px;
|
border-top-left-radius: 2px;
|
||||||
border-top-right-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
|
@ -632,7 +671,7 @@
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
.zoomOut {
|
.zoomOut {
|
||||||
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>);
|
background-image: url(<%= asset_path('zoom_sprite.png') %>);
|
||||||
background-position:0 -32px;
|
background-position:0 -32px;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
border-bottom-right-radius: 2px;
|
border-bottom-right-radius: 2px;
|
||||||
|
@ -650,15 +689,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMaps {
|
#exploreMaps {
|
||||||
padding: 0 5%;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 90%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMaps > div {
|
#exploreMaps > div {
|
||||||
margin-top: 110px;
|
margin: 110px auto 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.loadMore {
|
.button.loadMore {
|
||||||
|
@ -739,26 +777,38 @@
|
||||||
top:5px;
|
top:5px;
|
||||||
left:5px;
|
left:5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exploreMapsCenter .authedApps .exploreMapsIcon {
|
||||||
|
background-image: url(<%= asset_path('user_sprite.png') %>);
|
||||||
|
background-position: 0 -32px;
|
||||||
|
}
|
||||||
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .sharedMaps .exploreMapsIcon {
|
.exploreMapsCenter .sharedMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -128px 0;
|
background-position: -128px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .activeMaps .exploreMapsIcon {
|
.exploreMapsCenter .activeMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .featuredMaps .exploreMapsIcon {
|
.exploreMapsCenter .featuredMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .starredMaps .exploreMapsIcon {
|
.exploreMapsCenter .starredMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
|
.exploreMapsCenter .notificationsLink .exploreMapsIcon {
|
||||||
|
background-image: url(<%= asset_path 'topright_sprite.png' %>);
|
||||||
|
background-position: -128px 0;
|
||||||
|
}
|
||||||
|
.authedApps:hover .exploreMapsIcon, .authedApps.active .exploreMapsIcon {
|
||||||
|
background-position-x: -32px;
|
||||||
|
}
|
||||||
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
||||||
background-position: -32px -32px;
|
background-position: -32px -32px;
|
||||||
}
|
}
|
||||||
|
@ -774,6 +824,9 @@
|
||||||
.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon {
|
.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon {
|
||||||
background-position: -128px -32px;
|
background-position: -128px -32px;
|
||||||
}
|
}
|
||||||
|
.notificationsLink:hover .exploreMapsIcon, .notificationsLink.active .exploreMapsIcon {
|
||||||
|
background-position-y: -32px;
|
||||||
|
}
|
||||||
|
|
||||||
.mapsWrapper {
|
.mapsWrapper {
|
||||||
/*overflow-y: auto; */
|
/*overflow-y: auto; */
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
/* =USERVOICE ICON DEFINE
|
.unauthenticated .feedback-icon {
|
||||||
--------------------------------------------------------*/
|
|
||||||
|
|
||||||
.unauthenticated .uv-icon {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.uv-icon.uv-bottom-left {
|
.feedback-icon {
|
||||||
|
position: fixed;
|
||||||
background-image: url(<%= asset_data_uri 'feedback_sprite.png' %>);
|
background-image: url(<%= asset_data_uri 'feedback_sprite.png' %>);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
color:#FFFFFF;
|
color:#FFFFFF;
|
||||||
|
@ -20,6 +18,8 @@ div.uv-icon.uv-bottom-left {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.uv-icon.uv-bottom-left:hover {
|
.feedback-icon:hover {
|
||||||
background-position: 0 -110px;
|
background-position: 0 -110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,13 +90,16 @@
|
||||||
left: 30px;
|
left: 30px;
|
||||||
top: 72px;
|
top: 72px;
|
||||||
}
|
}
|
||||||
|
#chat-box-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
.chat-box {
|
.chat-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
float: right;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #424242;
|
background: #424242;
|
||||||
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
||||||
|
@ -114,7 +117,6 @@
|
||||||
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
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 {
|
.chat-box .chat-button .chat-unread {
|
||||||
display: none;
|
|
||||||
background: #DAB539;
|
background: #DAB539;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
|
@ -176,7 +178,6 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.chat-box .participants .conversation-live {
|
.chat-box .participants .conversation-live {
|
||||||
display: none;
|
|
||||||
padding: 5px 10px 5px 10px;
|
padding: 5px 10px 5px 10px;
|
||||||
background: #c04f4f;
|
background: #c04f4f;
|
||||||
margin: 5px 10px;
|
margin: 5px 10px;
|
||||||
|
@ -187,15 +188,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #EBFF00;
|
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 {
|
.chat-box .participants .participant {
|
||||||
width: 89%;
|
width: 89%;
|
||||||
padding: 8px 8px 2px 8px;
|
padding: 8px 8px 2px 8px;
|
||||||
|
@ -225,32 +217,18 @@
|
||||||
padding: 2px 8px 0;
|
padding: 2px 8px 0;
|
||||||
text-align: left;
|
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-call,
|
||||||
.chat-box .participants .participant .chat-participant-invite-join
|
.chat-box .participants .participant .chat-participant-invite-join
|
||||||
{
|
{
|
||||||
float: right;
|
float: right;
|
||||||
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant.pending .chat-participant-invite-call,
|
.chat-box .participants .participant .chat-participant-invite-call.pending,
|
||||||
.chat-box .participants .participant.pending .chat-participant-invite-join {
|
.chat-box .participants .participant .chat-participant-invite-join.pending {
|
||||||
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant .chat-participant-participating {
|
.chat-box .participants .participant .chat-participant-participating {
|
||||||
float: right;
|
float: right;
|
||||||
display: none;
|
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
||||||
|
@ -259,9 +237,6 @@
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant.active .chat-participant-participating {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.chat-box .chat-header {
|
.chat-box .chat-header {
|
||||||
width: 276px;
|
width: 276px;
|
||||||
padding: 16px 8px 16px 16px;
|
padding: 16px 8px 16px 16px;
|
||||||
|
@ -339,6 +314,7 @@
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
padding: 2px 8px 0;
|
padding: 2px 8px 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
.chat-box .chat-messages .chat-message .chat-message-time {
|
.chat-box .chat-messages .chat-message .chat-message-time {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
278
app/assets/stylesheets/mapcard.scss.erb
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
/* Map Cards */
|
||||||
|
|
||||||
|
.map {
|
||||||
|
display:inline-block;
|
||||||
|
width:220px;
|
||||||
|
height:340px;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: left;
|
||||||
|
overflow: visible;
|
||||||
|
background: #e8e8e8;
|
||||||
|
border-radius:2px;
|
||||||
|
margin:16px;
|
||||||
|
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||||
|
|
||||||
|
&.newMap {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #dcdcdc;
|
||||||
|
|
||||||
|
.newMapImage {
|
||||||
|
background-position: 0 -72px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
height: 340px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newMapImage {
|
||||||
|
display: block;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
background-image: url("<%= asset_data_uri('newmap_sprite.png') %>");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -36px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
padding-top: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapCard {
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:308px;
|
||||||
|
padding: 0 0 16px 0;
|
||||||
|
color: #424242;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.dropdownMenu .menuToggle .circle {
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
.dropdownMenu .menuToggle:hover .circle {
|
||||||
|
background-color: #DDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainContent {
|
||||||
|
filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapMetadata {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapHasMapper, .mapHasConversation {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #FFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
.mapperList {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapperList {
|
||||||
|
display: none;
|
||||||
|
padding: 8px;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
&.live {
|
||||||
|
height: 32px;
|
||||||
|
padding-left: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mapHasMapper {
|
||||||
|
background: url('<%= asset_path('junto.png') %>') no-repeat 4px 0;
|
||||||
|
}
|
||||||
|
.mapHasConversation {
|
||||||
|
background: url('<%= asset_path('junto.gif') %>') no-repeat 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownMenu {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.menuToggle {
|
||||||
|
width: 30px;
|
||||||
|
height: 10px;
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #454545;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .circle {
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItems {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 0px;
|
||||||
|
background: #FFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
list-style-type: none;
|
||||||
|
color: #454545;
|
||||||
|
|
||||||
|
li {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #DDD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapScreenshot {
|
||||||
|
width: 100%;
|
||||||
|
height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapScreenshot img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size:18px;
|
||||||
|
line-height:22px;
|
||||||
|
height: 71px;
|
||||||
|
display:table;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.innerTitle {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.creatorAndPerm {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creatorImage {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.creatorName {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardViewOnly {
|
||||||
|
float: right;
|
||||||
|
line-height: 32px;
|
||||||
|
padding-right: 10px;
|
||||||
|
color: #454545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
display:block;
|
||||||
|
font-family: helvetica, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.mapMetadata {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 40px 20px 0;
|
||||||
|
height: 300px;
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #FFF;
|
||||||
|
background: -moz-linear-gradient(top, rgba(0,0,0,0.65) 0%, rgba(0,0,0,0.43) 81%, rgba(0,0,0,0) 100%);
|
||||||
|
background: -webkit-linear-gradient(top, rgba(0,0,0,0.65) 0%,rgba(0,0,0,0.43) 81%,rgba(0,0,0,0) 100%);
|
||||||
|
background: linear-gradient(to bottom, rgba(0,0,0,0.65) 0%,rgba(0,0,0,0.43) 81%,rgba(0,0,0,0) 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#a6000000', endColorstr='#00000000',GradientType=0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadataSection {
|
||||||
|
padding: 16px 0;
|
||||||
|
width: 90px;
|
||||||
|
float: left;
|
||||||
|
font-family: 'din-medium', sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
div {
|
||||||
|
background: url('<%= asset_path('metadata.png') %>') no-repeat;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.numTopicsIcon {
|
||||||
|
background-position: 0 -32px;
|
||||||
|
}
|
||||||
|
.numStarsIcon {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
.numSynapsesIcon {
|
||||||
|
background-position: -32px -32px;
|
||||||
|
}
|
||||||
|
.numContributorsIcon {
|
||||||
|
background-position: -32px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,55 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Smartphones (portrait and landscape) ----------- */
|
@media only screen and (max-width : 752px) and (min-width : 504px) {
|
||||||
@media only screen and (max-device-width : 480px) {
|
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
||||||
.upperLeftUI, .upperRightUI, .openCheatsheet, .mapInfoIcon, .uv-icon, .chat-box, #exploreMapsHeader {
|
width: 160px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when this switches to two lines */
|
||||||
|
@media only screen and (max-width : 728px) {
|
||||||
|
.controller-notifications .notificationsPage .notification .notification-read-unread a {
|
||||||
|
margin-top: -20px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width : 390px) {
|
||||||
|
.map .mapCard .mobileMetadata {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (min-width : 390px) {
|
||||||
|
.map .mapCard .mobileMetadata {
|
||||||
|
width: 390px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 800 is the max-width for centerContent */
|
||||||
|
@media only screen and (max-width : 800px) {
|
||||||
|
.centerContent.withPadding {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Smartphones (portrait and landscape) ----------- the minimum space that two map cards can fit side by side */
|
||||||
|
@media only screen and (max-width : 504px) {
|
||||||
|
.upperLeftUI, .upperRightUI, .openCheatsheet, .mapInfoIcon, .feedback-icon, .chat-box, #exploreMapsHeader {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notificationsPage .page-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller-notifications .notificationsPage .notification .notification-read-unread {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.controller-notifications .notificationsPage .notification .notification-date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#mobile_header {
|
#mobile_header {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +83,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#yield {
|
#yield {
|
||||||
height: 100%;
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new_session, .new_user, .edit_user, .login, .forgotPassword {
|
.new_session, .new_user, .edit_user, .login, .forgotPassword {
|
||||||
|
@ -49,14 +92,14 @@
|
||||||
left: auto;
|
left: auto;
|
||||||
width: 78%;
|
width: 78%;
|
||||||
padding: 16px 10%;
|
padding: 16px 10%;
|
||||||
margin: 50px auto 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centerGreyForm input[type="text"], .centerGreyForm input[type="email"], .centerGreyForm input[type="password"] {
|
.centerGreyForm input[type="text"], .centerGreyForm input[type="email"], .centerGreyForm input[type="password"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper div.mapInfoBox {
|
.wrapper .mapInfoBox {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
|
@ -74,6 +117,11 @@
|
||||||
margin-top: 70px;
|
margin-top: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapper {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.map.newMap {
|
.map.newMap {
|
||||||
a {
|
a {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
@ -100,29 +148,67 @@
|
||||||
.map.newMap:hover .newMapImage {
|
.map.newMap:hover .newMapImage {
|
||||||
background-position: 0 -40px;
|
background-position: 0 -40px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Smartphones (portrait) ----------- */
|
|
||||||
@media only screen and (max-width : 400px) {
|
|
||||||
|
|
||||||
.map {
|
.map {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 0 30px 0;
|
margin: 0 0 30px 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard {
|
.mapCard {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.mainContent {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobileHasMapper, .mobileHasConversation {
|
||||||
|
.mapperList {
|
||||||
|
padding: 8px 16px;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
&.live {
|
||||||
|
height: 32px;
|
||||||
|
padding-left: 32px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapCard .title {
|
img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobileHasMapper {
|
||||||
|
background: url('<%= asset_path('junto.png') %>') no-repeat 12px 0;
|
||||||
|
}
|
||||||
|
.mobileHasConversation {
|
||||||
|
background: url('<%= asset_path('junto.gif') %>') no-repeat 12px 0;
|
||||||
|
}
|
||||||
|
.mobileMetadata {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapCard .mapScreenshot {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#mobile_header {
|
#mobile_header {
|
||||||
|
@ -153,6 +239,15 @@
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mobile_header #menu_icon .unread-notifications-dot {
|
||||||
|
top: 5px;
|
||||||
|
left: 29px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border: 3px solid #eee;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
#mobile_menu {
|
#mobile_menu {
|
||||||
display: none;
|
display: none;
|
||||||
background: #EEE;
|
background: #EEE;
|
||||||
|
@ -162,11 +257,31 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
||||||
}
|
|
||||||
|
|
||||||
#mobile_menu li {
|
li {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
|
&.notifications {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.unread-notifications-dot {
|
||||||
|
top: 50%;
|
||||||
|
left: 0px;
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the mobile menu, even if it's been opened by a user, should
|
||||||
|
* not show up if they resize their browser back to full size
|
||||||
|
*/
|
||||||
|
@media only screen and (max-width : 504px) {
|
||||||
|
#mobile_menu.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li.mobileMenuUser {
|
li.mobileMenuUser {
|
||||||
|
|
138
app/assets/stylesheets/notifications.scss.erb
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
$unread_notifications_dot_size: 8px;
|
||||||
|
.unread-notifications-dot {
|
||||||
|
width: $unread_notifications_dot_size;
|
||||||
|
height: $unread_notifications_dot_size;
|
||||||
|
background-color: #e22;
|
||||||
|
border-radius: $unread_notifications_dot_size / 2;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upperRightUI {
|
||||||
|
.notificationsIcon {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller-notifications {
|
||||||
|
ul.notifications {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationPage,
|
||||||
|
.notificationsPage {
|
||||||
|
font-family: 'din-regular', Sans-Serif;
|
||||||
|
|
||||||
|
& a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .notification-title {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 0.25em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsPage {
|
||||||
|
header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyInbox {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #F6F6F6;
|
||||||
|
|
||||||
|
.notification-read-unread {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
float: left;
|
||||||
|
width: 85%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actor {
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
margin-left: 50px;
|
||||||
|
|
||||||
|
.in-bold {
|
||||||
|
font-family: 'din-medium', Sans-Serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
background: #4fb5c0;
|
||||||
|
color: #FFF;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
color: #607d8b;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 13px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-read-unread {
|
||||||
|
display: none;
|
||||||
|
float: left;
|
||||||
|
width: 15%;
|
||||||
|
|
||||||
|
a {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unread {
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.notificationPage .notification-body {
|
||||||
|
p, div {
|
||||||
|
margin: 1em auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
app/assets/stylesheets/request_access.scss.erb
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
.viewOnly {
|
||||||
|
float: left;
|
||||||
|
margin-left: 16px;
|
||||||
|
display: none;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid #BDBDBD;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #424242;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 32px;
|
||||||
|
|
||||||
|
&.isViewOnly {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyeball {
|
||||||
|
background: url('<%= asset_path('view-only.png') %>') no-repeat 4px 0;
|
||||||
|
padding-left: 40px;
|
||||||
|
border-right: #747474;
|
||||||
|
padding-right: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestNotice {
|
||||||
|
display: none;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestAccess {
|
||||||
|
background-color: #a354cd;
|
||||||
|
&:hover {
|
||||||
|
background-color: #9150bc;
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestPending {
|
||||||
|
background-color: #4fc059;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestNotAccepted {
|
||||||
|
background-color: #c04f4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sendRequest .requestAccess {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.sentRequest .requestPending {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.requestDenied .requestNotAccepted {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.request_access {
|
||||||
|
position: absolute;
|
||||||
|
width: 90%;
|
||||||
|
margin: 0 5%;
|
||||||
|
|
||||||
|
.monkey {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
border: 6px solid #424242;
|
||||||
|
border-radius: 125px;
|
||||||
|
background: url(https://s3.amazonaws.com/metamaps-assets/site/monkeyselfie.jpg) no-repeat;
|
||||||
|
background-position: 50% 20%;
|
||||||
|
background-size: 100%;
|
||||||
|
margin: 80px auto 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explainer_text {
|
||||||
|
padding: 0 20% 0 20%;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.make_request {
|
||||||
|
background-color: #a354cd;
|
||||||
|
display: block;
|
||||||
|
width: 220px;
|
||||||
|
height: 14px;
|
||||||
|
padding: 16px 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0px 1px 1.5px rgba(0,0,0,0.12), 0 1px 1px rgba(0,0,0,0.24);
|
||||||
|
margin: 0 auto 20px auto;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -93,7 +93,7 @@
|
||||||
|
|
||||||
.sidebarSearchField {
|
.sidebarSearchField {
|
||||||
float: left;
|
float: left;
|
||||||
width: 380px;
|
width: 379px;
|
||||||
padding: 7px 10px 3px 10px;
|
padding: 7px 10px 3px 10px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-top: 1px solid #BDBDBD;
|
border-top: 1px solid #BDBDBD;
|
||||||
|
|
4
app/channels/application_cable/channel.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module ApplicationCable
|
||||||
|
class Channel < ActionCable::Channel::Base
|
||||||
|
end
|
||||||
|
end
|
22
app/channels/application_cable/connection.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module ApplicationCable
|
||||||
|
class Connection < ActionCable::Connection::Base
|
||||||
|
identified_by :current_user
|
||||||
|
|
||||||
|
def connect
|
||||||
|
self.current_user = find_verified_user
|
||||||
|
logger.add_tags 'ActionCable', current_user.name
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def find_verified_user
|
||||||
|
verified_user = User.find_by(id: cookies.signed['user.id'])
|
||||||
|
if verified_user && cookies.signed['user.expires_at'] > Time.now.getlocal
|
||||||
|
verified_user
|
||||||
|
else
|
||||||
|
reject_unauthorized_connection
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
8
app/channels/map_channel.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class MapChannel < ApplicationCable::Channel
|
||||||
|
# Called when the consumer has successfully
|
||||||
|
# become a subscriber of this channel.
|
||||||
|
def subscribed
|
||||||
|
return unless Pundit.policy(current_user, Map.find(params[:id])).show?
|
||||||
|
stream_from "map_#{params[:id]}"
|
||||||
|
end
|
||||||
|
end
|
92
app/controllers/access_controller.rb
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class AccessController < ApplicationController
|
||||||
|
before_action :require_user, only: [:access, :access_request, :approve_access, :approve_access_post,
|
||||||
|
:deny_access, :deny_access_post, :request_access]
|
||||||
|
before_action :set_map, only: [:access, :access_request, :approve_access, :approve_access_post,
|
||||||
|
:deny_access, :deny_access_post, :request_access]
|
||||||
|
after_action :verify_authorized
|
||||||
|
|
||||||
|
# GET maps/:id/request_access
|
||||||
|
def request_access
|
||||||
|
@map = nil
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
render 'maps/request_access'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/access_request
|
||||||
|
def access_request
|
||||||
|
request = AccessRequest.create(user: current_user, map: @map)
|
||||||
|
NotificationService.access_request(request)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { head :ok }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/access
|
||||||
|
def access
|
||||||
|
user_ids = params[:access].to_a.map(&:to_i) || []
|
||||||
|
|
||||||
|
@map.add_new_collaborators(user_ids).each do |user_id|
|
||||||
|
# add_new_collaborators returns array of added users,
|
||||||
|
# who we then send a notification to
|
||||||
|
user = User.find(user_id)
|
||||||
|
NotificationService.invite_to_edit(@map, current_user, user)
|
||||||
|
end
|
||||||
|
@map.remove_old_collaborators(user_ids)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { head :ok }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET maps/:id/approve_access/:request_id
|
||||||
|
def approve_access
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.approve # also marks mailboxer notification as read
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to map_path(@map), notice: 'Request was approved' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET maps/:id/deny_access/:request_id
|
||||||
|
def deny_access
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.deny # also marks mailboxer notification as read
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to map_path(@map), notice: 'Request was turned down' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/approve_access/:request_id
|
||||||
|
def approve_access_post
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.approve
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/deny_access/:request_id
|
||||||
|
def deny_access_post
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.deny
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_map
|
||||||
|
@map = Map.find(params[:id])
|
||||||
|
authorize @map
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,2 +0,0 @@
|
||||||
class Api::MappingsController < API::RestfulController
|
|
||||||
end
|
|
|
@ -1,2 +0,0 @@
|
||||||
class Api::MapsController < API::RestfulController
|
|
||||||
end
|
|
|
@ -1,50 +0,0 @@
|
||||||
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)
|
|
||||||
@token_user ||= access_token.user if access_token
|
|
||||||
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
|
|
|
@ -1,2 +0,0 @@
|
||||||
class Api::SynapsesController < API::RestfulController
|
|
||||||
end
|
|
|
@ -1,17 +0,0 @@
|
||||||
class Api::TokensController < API::RestfulController
|
|
||||||
def my_tokens
|
|
||||||
raise Pundit::NotAuthorizedError 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
|
|
|
@ -1,2 +0,0 @@
|
||||||
class Api::TopicsController < API::RestfulController
|
|
||||||
end
|
|
12
app/controllers/api/v1/deprecated_controller.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class DeprecatedController < ApplicationController
|
||||||
|
def deprecated
|
||||||
|
render json: {
|
||||||
|
error: '/api/v1 has been deprecated! Please use /api/v2 instead.'
|
||||||
|
}, status: :gone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
31
app/controllers/api/v2/mappings_controller.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class MappingsController < RestfulController
|
||||||
|
def searchable_columns
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
instantiate_resource
|
||||||
|
resource.user = current_user if current_user.present?
|
||||||
|
resource.updated_by = current_user if current_user.present?
|
||||||
|
authorize resource
|
||||||
|
create_action
|
||||||
|
respond_with_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
resource.updated_by = current_user if current_user.present?
|
||||||
|
update_action
|
||||||
|
respond_with_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
resource.updated_by = current_user if current_user.present?
|
||||||
|
destroy_action
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
app/controllers/api/v2/maps_controller.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class MapsController < RestfulController
|
||||||
|
def searchable_columns
|
||||||
|
[:name, :desc]
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_filters(collection)
|
||||||
|
collection = collection.where(user_id: params[:user_id]) if params[:user_id]
|
||||||
|
collection
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
app/controllers/api/v2/metacodes_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class MetacodesController < RestfulController
|
||||||
|
def searchable_columns
|
||||||
|
[:name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
198
app/controllers/api/v2/restful_controller.rb
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class RestfulController < ActionController::Base
|
||||||
|
include Pundit
|
||||||
|
include PunditExtra
|
||||||
|
|
||||||
|
snorlax_used_rest!
|
||||||
|
|
||||||
|
before_action :load_resource, only: [:show, :update, :destroy]
|
||||||
|
after_action :verify_authorized
|
||||||
|
|
||||||
|
def index
|
||||||
|
authorize resource_class
|
||||||
|
instantiate_collection
|
||||||
|
respond_with_collection
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
instantiate_resource
|
||||||
|
resource.user = current_user if current_user.present?
|
||||||
|
authorize resource
|
||||||
|
create_action
|
||||||
|
respond_with_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
destroy_action
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
def catch_404
|
||||||
|
skip_authorization
|
||||||
|
render json: { error: '404 Not found' }, status: :not_found
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def accessible_records
|
||||||
|
if current_user
|
||||||
|
visible_records
|
||||||
|
else
|
||||||
|
public_records
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user
|
||||||
|
token_user || doorkeeper_user || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_resource
|
||||||
|
super
|
||||||
|
authorize resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_serializer
|
||||||
|
"Api::V2::#{resource_name.camelize}Serializer".constantize
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_with_resource(scope: default_scope, serializer: resource_serializer,
|
||||||
|
root: serializer_root)
|
||||||
|
if resource.errors.empty?
|
||||||
|
render json: resource, scope: scope, serializer: serializer, root: root
|
||||||
|
else
|
||||||
|
respond_with_errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_with_collection(resources: collection, scope: default_scope,
|
||||||
|
serializer: resource_serializer, root: serializer_root)
|
||||||
|
pagination_link_headers!(pagination(resources))
|
||||||
|
render json: resources, scope: scope, each_serializer: serializer, root: root,
|
||||||
|
meta: pagination(resources), meta_key: :page
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_scope
|
||||||
|
{
|
||||||
|
embeds: embeds,
|
||||||
|
current_user: current_user
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def embeds
|
||||||
|
(params[:embed] || '').split(',').map(&:to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
def token_user
|
||||||
|
token = params[:access_token]
|
||||||
|
access_token = Token.find_by_token(token)
|
||||||
|
@token_user ||= access_token.user if access_token
|
||||||
|
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
|
||||||
|
|
||||||
|
def serializer_root
|
||||||
|
'data'
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination(collection)
|
||||||
|
return @pagination_data unless @pagination_data.nil?
|
||||||
|
|
||||||
|
current_page = (params[:page] || 1).to_i
|
||||||
|
per = (params[:per] || 25).to_i
|
||||||
|
total_pages = (collection.total_count.to_f / per).ceil
|
||||||
|
@pagination_data = {
|
||||||
|
current_page: current_page,
|
||||||
|
next_page: current_page < total_pages ? current_page + 1 : 0,
|
||||||
|
prev_page: current_page > 1 ? current_page - 1 : 0,
|
||||||
|
total_pages: total_pages,
|
||||||
|
total_count: collection.total_count,
|
||||||
|
per: per
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_link_headers!(data)
|
||||||
|
base_url = request.base_url + request.path
|
||||||
|
old_query = request.query_parameters
|
||||||
|
nxt = old_query.merge(page: data[:next_page]).map { |x| x.join('=') }.join('&')
|
||||||
|
prev = old_query.merge(page: data[:prev_page]).map { |x| x.join('=') }.join('&')
|
||||||
|
last = old_query.merge(page: data[:total_pages]).map { |x| x.join('=') }.join('&')
|
||||||
|
|
||||||
|
response.headers['Link'] = [
|
||||||
|
%(<#{base_url}?#{nxt}>; rel="next"),
|
||||||
|
%(<#{base_url}?#{prev}>; rel="prev"),
|
||||||
|
%(<#{base_url}?#{last}>; rel="last")
|
||||||
|
].join(',')
|
||||||
|
response.headers['X-Total-Pages'] = data[:total_pages].to_s
|
||||||
|
response.headers['X-Total-Count'] = data[:total_count].to_s
|
||||||
|
response.headers['X-Per-Page'] = data[:per].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def instantiate_collection
|
||||||
|
collection = accessible_records
|
||||||
|
collection = yield collection if block_given?
|
||||||
|
collection = search_by_q(collection) if params[:q]
|
||||||
|
collection = apply_filters(collection)
|
||||||
|
collection = order_by_sort(collection) if params[:sort]
|
||||||
|
collection = collection.page(params[:page]).per(params[:per])
|
||||||
|
self.collection = collection
|
||||||
|
end
|
||||||
|
|
||||||
|
# override this method to explicitly set searchable columns
|
||||||
|
def searchable_columns
|
||||||
|
columns = resource_class.columns.select do |column|
|
||||||
|
column.type == :text || column.type == :string
|
||||||
|
end
|
||||||
|
columns.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# thanks to http://stackoverflow.com/questions/4430578
|
||||||
|
def search_by_q(collection)
|
||||||
|
table = resource_class.arel_table
|
||||||
|
safe_query = "%#{params[:q].gsub(/[%_]/, '\\\\\0')}%"
|
||||||
|
search_column = -> (column) { table[column].matches(safe_query) }
|
||||||
|
|
||||||
|
condition = searchable_columns.reduce(nil) do |prev, column|
|
||||||
|
next search_column.call(column) if prev.nil?
|
||||||
|
search_column.call(column).or(prev)
|
||||||
|
end
|
||||||
|
collection.where(condition)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_filters(collection)
|
||||||
|
# override this function for specific filters
|
||||||
|
collection
|
||||||
|
end
|
||||||
|
|
||||||
|
def order_by_sort(collection)
|
||||||
|
builder = collection
|
||||||
|
sorts = params[:sort].split(',')
|
||||||
|
sorts.each do |sort|
|
||||||
|
direction = sort.starts_with?('-') ? 'desc' : 'asc'
|
||||||
|
sort = sort.sub(/^-/, '')
|
||||||
|
if resource_class.columns.map(&:name).include?(sort)
|
||||||
|
builder = builder.order(sort => direction)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
builder
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible_records
|
||||||
|
policy_scope(resource_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_records
|
||||||
|
policy_scope(resource_class)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
app/controllers/api/v2/sessions_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class SessionsController < ApplicationController
|
||||||
|
def create
|
||||||
|
@user = User.find_by(email: params[:email])
|
||||||
|
if @user&.valid_password(params[:password])
|
||||||
|
sign_in(@user)
|
||||||
|
render json: @user
|
||||||
|
else
|
||||||
|
render json: { error: 'Error' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
sign_out
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
29
app/controllers/api/v2/stars_controller.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class StarsController < RestfulController
|
||||||
|
skip_before_action :load_resource
|
||||||
|
|
||||||
|
def create
|
||||||
|
@map = Map.find(params[:id])
|
||||||
|
@star = Star.new(user: current_user, map: @map)
|
||||||
|
authorize @map, :star?
|
||||||
|
create_action
|
||||||
|
|
||||||
|
if @star.errors.empty?
|
||||||
|
render json: @map, scope: default_scope, serializer: MapSerializer, root: serializer_root
|
||||||
|
else
|
||||||
|
respond_with_errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@map = Map.find(params[:id])
|
||||||
|
authorize @map, :unstar?
|
||||||
|
@star = @map.stars.find_by(user: current_user)
|
||||||
|
@star.destroy if @star.present?
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
app/controllers/api/v2/synapses_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class SynapsesController < RestfulController
|
||||||
|
def searchable_columns
|
||||||
|
[:desc]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
23
app/controllers/api/v2/tokens_controller.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class TokensController < RestfulController
|
||||||
|
def searchable_columns
|
||||||
|
[:description]
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
if params[:token].blank?
|
||||||
|
self.resource = resource_class.new
|
||||||
|
else
|
||||||
|
instantiate_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
resource.user = current_user if current_user.present?
|
||||||
|
authorize resource
|
||||||
|
create_action
|
||||||
|
respond_with_resource
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
app/controllers/api/v2/topics_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class TopicsController < RestfulController
|
||||||
|
def searchable_columns
|
||||||
|
[:name, :desc, :link]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
24
app/controllers/api/v2/users_controller.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Api
|
||||||
|
module V2
|
||||||
|
class UsersController < RestfulController
|
||||||
|
def current
|
||||||
|
@user = current_user
|
||||||
|
authorize @user
|
||||||
|
show # delegate to the normal show function
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def searchable_columns
|
||||||
|
[:name]
|
||||||
|
end
|
||||||
|
|
||||||
|
# only ask serializer to return is_admin field if we're on the
|
||||||
|
# current_user action
|
||||||
|
def default_scope
|
||||||
|
super.merge(show_full_user: action_name == 'current')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
include Pundit
|
include Pundit
|
||||||
|
@ -5,7 +6,8 @@ class ApplicationController < ActionController::Base
|
||||||
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
|
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
|
||||||
protect_from_forgery(with: :exception)
|
protect_from_forgery(with: :exception)
|
||||||
|
|
||||||
before_action :get_invite_link
|
before_action :invite_link
|
||||||
|
before_action :prepare_exception_notifier
|
||||||
after_action :allow_embedding
|
after_action :allow_embedding
|
||||||
|
|
||||||
def default_serializer_options
|
def default_serializer_options
|
||||||
|
@ -19,51 +21,39 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :authenticated?
|
helper_method :authenticated?
|
||||||
helper_method :admin?
|
helper_method :admin?
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
|
||||||
sign_in_url = url_for(action: 'new', controller: 'sessions', only_path: false)
|
|
||||||
|
|
||||||
if request.referer == sign_in_url
|
|
||||||
super
|
|
||||||
elsif params[:uv_login] == '1'
|
|
||||||
'http://support.metamaps.cc/login_success?sso=' + current_sso_token
|
|
||||||
else
|
|
||||||
stored_location_for(resource) || request.referer || root_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_unauthorized
|
def handle_unauthorized
|
||||||
if authenticated?
|
if authenticated? && (params[:controller] == 'maps') && (params[:action] == 'show')
|
||||||
head :forbidden # TODO: make this better
|
redirect_to request_access_map_path(params[:id])
|
||||||
|
elsif authenticated?
|
||||||
|
redirect_to root_path, notice: "You don't have permission to see that page."
|
||||||
else
|
else
|
||||||
redirect_to new_user_session_path, notice: 'Try signing in to do that.'
|
store_location_for(resource, request.fullpath)
|
||||||
|
redirect_to sign_in_path, notice: 'Try signing in to do that.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def get_invite_link
|
def invite_link
|
||||||
@invite_link = "#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
@invite_link = "#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_no_user
|
def require_no_user
|
||||||
if authenticated?
|
return true unless authenticated?
|
||||||
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
||||||
return false
|
false
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_user
|
def require_user
|
||||||
unless authenticated?
|
return true if authenticated?
|
||||||
redirect_to new_user_session_path, notice: 'You must be logged in.'
|
redirect_to sign_in_path, notice: 'You must be logged in.'
|
||||||
return false
|
false
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_admin
|
def require_admin
|
||||||
unless authenticated? && admin?
|
return true if authenticated? && admin?
|
||||||
redirect_to root_url, notice: 'You need to be an admin for that.'
|
redirect_to root_url, notice: 'You need to be an admin for that.'
|
||||||
return false
|
false
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def user
|
def user
|
||||||
|
@ -84,4 +74,10 @@ class ApplicationController < ActionController::Base
|
||||||
# or allow a whitelist
|
# or allow a whitelist
|
||||||
# response.headers['X-Frame-Options'] = 'ALLOW-FROM http://blog.metamaps.cc'
|
# response.headers['X-Frame-Options'] = 'ALLOW-FROM http://blog.metamaps.cc'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare_exception_notifier
|
||||||
|
request.env['exception_notifier.exception_data'] = {
|
||||||
|
current_user: current_user
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
89
app/controllers/explore_controller.rb
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class ExploreController < ApplicationController
|
||||||
|
before_action :require_authentication, only: [:mine, :shared, :starred]
|
||||||
|
before_action :authorize_explore
|
||||||
|
after_action :verify_authorized
|
||||||
|
after_action :verify_policy_scoped
|
||||||
|
|
||||||
|
respond_to :html, :json, :csv
|
||||||
|
|
||||||
|
# GET /explore/active
|
||||||
|
def active
|
||||||
|
@maps = map_scope(Map.where.not(name: 'Untitled Map').where.not(permission: 'private'))
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
# root url => main/home. main/home renders maps/activemaps view.
|
||||||
|
redirect_to(root_url) && return if authenticated?
|
||||||
|
respond_with(@maps, @user)
|
||||||
|
end
|
||||||
|
format.json { render json: @maps.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /explore/featured
|
||||||
|
def featured
|
||||||
|
@maps = map_scope(Map.where(featured: true))
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { respond_with(@maps, @user) }
|
||||||
|
format.json { render json: @maps.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /explore/mine
|
||||||
|
def mine
|
||||||
|
@maps = map_scope(Map.where(user_id: current_user.id))
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { respond_with(@maps, @user) }
|
||||||
|
format.json { render json: @maps.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /explore/shared
|
||||||
|
def shared
|
||||||
|
@maps = map_scope(Map.where(id: current_user.shared_maps.map(&:id)))
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { respond_with(@maps, @user) }
|
||||||
|
format.json { render json: @maps.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /explore/starred
|
||||||
|
def starred
|
||||||
|
@maps = map_scope(Map.where(id: current_user.stars.map(&:map_id)))
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { respond_with(@maps, @user) }
|
||||||
|
format.json { render json: @maps.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /explore/mapper/:id
|
||||||
|
def mapper
|
||||||
|
@user = User.find(params[:id])
|
||||||
|
@maps = map_scope(Map.where(user: @user))
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { respond_with(@maps, @user) }
|
||||||
|
format.json { render json: @maps.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def map_scope(scope)
|
||||||
|
policy_scope(scope).order(updated_at: :desc).page(params[:page]).per(20)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_explore
|
||||||
|
authorize :Explore
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_authentication
|
||||||
|
# skip_policy_scope
|
||||||
|
redirect_to explore_active_path unless authenticated?
|
||||||
|
end
|
||||||
|
end
|
44
app/controllers/hacks_controller.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
# bad code that should be checked over before entering one of the
|
||||||
|
# nice files from the right side of this repo
|
||||||
|
class HacksController < ApplicationController
|
||||||
|
include ActionView::Helpers::TextHelper # string truncate method
|
||||||
|
|
||||||
|
# rate limited by rack-attack - currently 5r/s
|
||||||
|
# TODO: what else can we do to make get_with_redirects safer?
|
||||||
|
def load_url_title
|
||||||
|
authorize :Hack
|
||||||
|
url = params[:url]
|
||||||
|
response, url = get_with_redirects(url)
|
||||||
|
title = get_encoded_title(response)
|
||||||
|
render json: { success: true, title: title, url: url }
|
||||||
|
rescue StandardError
|
||||||
|
render json: { success: false }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def get_with_redirects(url)
|
||||||
|
uri = URI.parse(url)
|
||||||
|
response = Net::HTTP.get_response(uri)
|
||||||
|
while response.code == '301'
|
||||||
|
uri = URI.parse(response['location'])
|
||||||
|
response = Net::HTTP.get_response(uri)
|
||||||
|
end
|
||||||
|
[response, uri.to_s]
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_encoded_title(http_response)
|
||||||
|
# ensure there's actually an html title tag
|
||||||
|
title = http_response.body.sub(%r{.*(<title>.*</title>).*}m, '\1')
|
||||||
|
return '' unless title.starts_with?('<title>')
|
||||||
|
return '' unless title.ends_with?('</title>')
|
||||||
|
title = title.sub('<title>', '').sub(%r{</title>$}, '')
|
||||||
|
|
||||||
|
# encode and trim the title to 140 usable characters
|
||||||
|
charset = http_response['content-type'].sub(/.*charset=(.*);?.*/, '\1')
|
||||||
|
charset = nil if charset == 'text/html'
|
||||||
|
title = title.force_encoding(charset) if charset
|
||||||
|
truncate(title, length: 140)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,171 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class MainController < ApplicationController
|
class MainController < ApplicationController
|
||||||
include TopicsHelper
|
before_action :authorize_main
|
||||||
include MapsHelper
|
after_action :verify_authorized
|
||||||
include UsersHelper
|
|
||||||
include SynapsesHelper
|
|
||||||
|
|
||||||
after_action :verify_policy_scoped, except: [:requestinvite, :searchmappers]
|
# GET /
|
||||||
|
|
||||||
respond_to :html, :json
|
|
||||||
|
|
||||||
# home page
|
|
||||||
def home
|
def home
|
||||||
@maps = policy_scope(Map).order('updated_at DESC').page(1).per(20)
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {
|
format.html do
|
||||||
if !authenticated?
|
if authenticated?
|
||||||
|
@maps = policy_scope(Map).where.not(name: 'Untitled Map').where.not(permission: 'private')
|
||||||
|
.order(updated_at: :desc).page(1).per(20)
|
||||||
|
render 'explore/active'
|
||||||
|
else
|
||||||
render 'main/home'
|
render 'main/home'
|
||||||
else
|
|
||||||
render 'maps/activemaps'
|
|
||||||
end
|
end
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
### SEARCHING ###
|
# GET /request
|
||||||
|
def requestinvite
|
||||||
# get /search/topics?term=SOMETERM
|
|
||||||
def searchtopics
|
|
||||||
term = params[:term]
|
|
||||||
user = params[:user] ? params[:user] : false
|
|
||||||
|
|
||||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero?
|
|
||||||
|
|
||||||
# remove "topic:" if appended at beginning
|
|
||||||
term = term[6..-1] if term.downcase[0..5] == 'topic:'
|
|
||||||
|
|
||||||
# if desc: search desc instead
|
|
||||||
desc = false
|
|
||||||
if term.downcase[0..4] == 'desc:'
|
|
||||||
term = term[5..-1]
|
|
||||||
desc = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# if link: search link instead
|
private
|
||||||
link = false
|
|
||||||
if term.downcase[0..4] == 'link:'
|
|
||||||
term = term[5..-1]
|
|
||||||
link = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# check whether there's a filter by metacode as part of the query
|
def authorize_main
|
||||||
filterByMetacode = false
|
authorize :Main
|
||||||
Metacode.all.each do |m|
|
|
||||||
lOne = m.name.length + 1
|
|
||||||
lTwo = m.name.length
|
|
||||||
|
|
||||||
if term.downcase[0..lTwo] == m.name.downcase + ':'
|
|
||||||
term = term[lOne..-1]
|
|
||||||
filterByMetacode = m
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
search = '%' + term.downcase + '%'
|
|
||||||
builder = policy_scope(Topic)
|
|
||||||
|
|
||||||
if filterByMetacode
|
|
||||||
if term == ''
|
|
||||||
builder = builder.none
|
|
||||||
else
|
|
||||||
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
|
|
||||||
builder = builder.where('LOWER("desc") like ?', search)
|
|
||||||
elsif link
|
|
||||||
builder = builder.where('LOWER("link") like ?', search)
|
|
||||||
else # regular case, just search the name
|
|
||||||
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
|
|
||||||
|
|
||||||
render json: autocomplete_array_json(@topics)
|
|
||||||
end
|
|
||||||
|
|
||||||
# get /search/maps?term=SOMETERM
|
|
||||||
def searchmaps
|
|
||||||
term = params[:term]
|
|
||||||
user = params[:user] ? params[:user] : nil
|
|
||||||
|
|
||||||
if term && !term.empty? && term.downcase[0..5] != 'topic:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero?
|
|
||||||
|
|
||||||
# remove "map:" if appended at beginning
|
|
||||||
term = term[4..-1] if term.downcase[0..3] == 'map:'
|
|
||||||
|
|
||||||
# if desc: search desc instead
|
|
||||||
desc = false
|
|
||||||
if term.downcase[0..4] == 'desc:'
|
|
||||||
term = term[5..-1]
|
|
||||||
desc = true
|
|
||||||
end
|
|
||||||
|
|
||||||
search = '%' + term.downcase + '%'
|
|
||||||
builder = policy_scope(Map)
|
|
||||||
|
|
||||||
builder = if desc
|
|
||||||
builder.where('LOWER("desc") like ?', search)
|
|
||||||
else
|
|
||||||
builder.where('LOWER("name") like ?', search)
|
|
||||||
end
|
|
||||||
builder = builder.where(user: user) if user
|
|
||||||
@maps = builder.order(:name)
|
|
||||||
else
|
|
||||||
@maps = []
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: autocomplete_map_array_json(@maps)
|
|
||||||
end
|
|
||||||
|
|
||||||
# get /search/mappers?term=SOMETERM
|
|
||||||
def searchmappers
|
|
||||||
term = params[:term]
|
|
||||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero?
|
|
||||||
|
|
||||||
# remove "mapper:" if appended at beginning
|
|
||||||
term = term[7..-1] if term.downcase[0..6] == 'mapper:'
|
|
||||||
search = term.downcase + '%'
|
|
||||||
|
|
||||||
skip_policy_scope # TODO: builder = policy_scope(User)
|
|
||||||
builder = User.where('LOWER("name") like ?', search)
|
|
||||||
@mappers = builder.order(:name)
|
|
||||||
else
|
|
||||||
@mappers = []
|
|
||||||
end
|
|
||||||
render json: autocomplete_user_array_json(@mappers)
|
|
||||||
end
|
|
||||||
|
|
||||||
# get /search/synapses?term=SOMETERM OR
|
|
||||||
# get /search/synapses?topic1id=SOMEID&topic2id=SOMEID
|
|
||||||
def searchsynapses
|
|
||||||
term = params[:term]
|
|
||||||
topic1id = params[:topic1id]
|
|
||||||
topic2id = params[:topic2id]
|
|
||||||
|
|
||||||
if term && !term.empty?
|
|
||||||
@synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"')
|
|
||||||
|
|
||||||
@synapses = @synapses.uniq(&:desc)
|
|
||||||
elsif topic1id && !topic1id.empty?
|
|
||||||
@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
|
|
||||||
else
|
|
||||||
@synapses = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# limit to 5 results
|
|
||||||
@synapses = @synapses.slice(0, 5)
|
|
||||||
|
|
||||||
render json: autocomplete_synapse_array_json(@synapses)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|