1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-01 12:24:28 +01:00

init depot fabmanager

This commit is contained in:
cyril 2015-05-05 03:10:25 +02:00
parent 7ff3f1a581
commit 68eab24fa1
3336 changed files with 392769 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
/vendor/cache
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp
# uploads and public assets
/public/uploads
/public/assets
# MacOS and IDE files
.idea
.DS_Store
# machine specific database config
/config/database.yml

4
.rspec Normal file
View File

@ -0,0 +1,4 @@
--color
--require spec_helper
--format documentation
--backtrace

1
.ruby-gemset Normal file
View File

@ -0,0 +1 @@
fabmanager

1
.ruby-version Normal file
View File

@ -0,0 +1 @@
ruby-2.2.1

6
Capfile Normal file
View File

@ -0,0 +1,6 @@
load 'deploy'
# Uncomment if you are using Rails' asset pipeline
load 'deploy/assets'
load 'config/deploy' # remove this line to skip loading any of the default tasks
require 'capistrano/sidekiq'

113
Gemfile Normal file
View File

@ -0,0 +1,113 @@
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.1'
# Use SCSS for stylesheets
gem 'sass-rails', '5.0.1'
gem 'compass-rails', '2.0.4'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.1.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', platforms: :ruby
# Use jquery as the JavaScript library
gem 'jquery-rails'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'forgery'
gem 'responders', '~> 2.0'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
# Access an IRB console on exception pages or by using <%= console %> in views
gem 'web-console', '~> 2.0'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'factory_girl_rails'
gem 'rspec-rails'
gem 'spring-commands-rspec'
gem 'guard-rspec', require: false
end
group :development do
# Preview mail in the browser
gem 'letter_opener'
gem 'awesome_print'
gem "puma"
gem 'foreman'
gem 'capistrano'
gem 'rvm-capistrano', require: false
gem 'capistrano-sidekiq', require: false
end
group :test do
gem 'database_cleaner'
gem 'faker'
end
group :production do
gem 'unicorn'
gem 'rails_12factor'
end
gem 'seed_dump'
gem 'pg'
gem 'devise'
gem 'devise-async'
gem 'rolify'
gem 'kaminari'
gem 'figaro'
gem 'bootstrap-sass'
gem 'font-awesome-rails'
gem 'angularjs-rails'
# Image processing ruby wrapper for ImageMagick
gem 'mini_magick'
# upload files
gem 'carrierwave'
gem 'twitter'
gem 'twitter-text'
# slug url
gem 'friendly_id', '~> 5.1.0'
# state machine
gem 'aasm'
# Background job processing
gem 'sidekiq'
gem 'sinatra', require: false
# Recurring jobs for Sidekiq
gem 'sidekiq-cron'
gem 'recurrence'
# Fork de la gem avec support Attachments
gem 'mandrill_dm', github: 'AbleTech/mandrill_dm'
gem 'disqus_api'
gem 'notify_with'
gem 'pundit'

452
Gemfile.lock Normal file
View File

@ -0,0 +1,452 @@
GIT
remote: git://github.com/AbleTech/mandrill_dm.git
revision: 2bbb35dd81887bb915f606699d63a723b450711d
specs:
mandrill_dm (1.1.0)
mandrill-api (~> 1.0.51)
GEM
remote: https://rubygems.org/
specs:
aasm (4.1.0)
actionmailer (4.2.1)
actionpack (= 4.2.1)
actionview (= 4.2.1)
activejob (= 4.2.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.1)
actionview (= 4.2.1)
activesupport (= 4.2.1)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
actionview (4.2.1)
activesupport (= 4.2.1)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
activejob (4.2.1)
activesupport (= 4.2.1)
globalid (>= 0.3.0)
activemodel (4.2.1)
activesupport (= 4.2.1)
builder (~> 3.1)
activerecord (4.2.1)
activemodel (= 4.2.1)
activesupport (= 4.2.1)
arel (~> 6.0)
activesupport (4.2.1)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.3.8)
angularjs-rails (1.3.15)
arel (6.0.0)
autoprefixer-rails (5.1.8)
execjs
json
awesome_print (1.6.1)
bcrypt (3.1.10)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootstrap-sass (3.3.4.1)
autoprefixer-rails (>= 5.0.0.1)
sass (>= 3.2.19)
buftok (0.2.0)
builder (3.2.2)
byebug (4.0.4)
columnize (= 0.9.0)
capistrano (2.15.5)
highline
net-scp (>= 1.0.0)
net-sftp (>= 2.0.0)
net-ssh (>= 2.0.14)
net-ssh-gateway (>= 1.1.0)
capistrano-sidekiq (0.5.2)
capistrano
sidekiq
carrierwave (0.10.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
celluloid (0.16.0)
timers (~> 4.0.0)
chunky_png (1.3.4)
coderay (1.1.0)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
coffee-script (2.3.0)
coffee-script-source
execjs
coffee-script-source (1.9.1)
columnize (0.9.0)
compass (1.0.3)
chunky_png (~> 1.2)
compass-core (~> 1.0.2)
compass-import-once (~> 1.0.5)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
sass (>= 3.3.13, < 3.5)
compass-core (1.0.3)
multi_json (~> 1.0)
sass (>= 3.3.0, < 3.5)
compass-import-once (1.0.5)
sass (>= 3.2, < 3.5)
compass-rails (2.0.4)
compass (~> 1.0.0)
sass-rails (<= 5.0.1)
sprockets (< 2.13)
connection_pool (2.1.3)
database_cleaner (1.4.1)
debug_inspector (0.0.2)
devise (3.4.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
responders
thread_safe (~> 0.1)
warden (~> 1.2.3)
devise-async (0.9.0)
devise (~> 3.2)
diff-lcs (1.2.5)
disqus_api (0.0.4)
activesupport (>= 3.0.0)
faraday (>= 0.8)
faraday_middleware (>= 0.9)
equalizer (0.0.11)
erubis (2.7.0)
excon (0.45.1)
execjs (2.4.0)
factory_girl (4.5.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.5.0)
factory_girl (~> 4.5.0)
railties (>= 3.0.0)
faker (1.4.3)
i18n (~> 0.5)
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.9.1)
faraday (>= 0.7.4, < 0.10)
ffi (1.9.8)
figaro (1.1.0)
thor (~> 0.14)
font-awesome-rails (4.3.0.0)
railties (>= 3.2, < 5.0)
foreman (0.78.0)
thor (~> 0.19.1)
forgery (0.6.0)
formatador (0.2.5)
friendly_id (5.1.0)
activerecord (>= 4.0.0)
globalid (0.3.3)
activesupport (>= 4.1.0)
guard (2.12.5)
formatador (>= 0.2.4)
listen (~> 2.7)
lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.5.0)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
highline (1.7.1)
hike (1.2.3)
hitimes (1.2.2)
http (0.6.4)
http_parser.rb (~> 0.6.0)
http_parser.rb (0.6.0)
i18n (0.7.0)
jbuilder (2.2.12)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
jquery-rails (4.0.3)
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.2)
kaminari (0.16.3)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.9.3)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.3.0)
launchy (~> 2.2)
libv8 (3.16.14.7)
listen (2.10.0)
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
loofah (2.0.1)
nokogiri (>= 1.5.9)
lumberjack (1.0.9)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mandrill-api (1.0.53)
excon (>= 0.16.0, < 1.0)
json (>= 1.7.7, < 2.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.8.2)
mime-types (2.4.3)
mini_magick (4.2.0)
mini_portile (0.6.2)
minitest (5.5.1)
multi_json (1.11.0)
multipart-post (2.0.0)
naught (1.0.0)
nenv (0.2.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-sftp (2.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.9.2)
net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
notiffany (0.0.6)
nenv (~> 0.1)
shellany (~> 0.0)
notify_with (0.0.2)
jbuilder (~> 2.0)
rails (>= 4.2.0)
responders (~> 2.0)
orm_adapter (0.5.0)
pg (0.18.1)
pry (0.10.1)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
puma (2.11.1)
rack (>= 1.1, < 2.0)
pundit (1.0.0)
activesupport (>= 3.0.0)
rack (1.6.0)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.1)
actionmailer (= 4.2.1)
actionpack (= 4.2.1)
actionview (= 4.2.1)
activejob (= 4.2.1)
activemodel (= 4.2.1)
activerecord (= 4.2.1)
activesupport (= 4.2.1)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.1)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.6)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.2)
loofah (~> 2.0)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
rails_serve_static_assets (0.0.4)
rails_stdout_logging (0.0.3)
railties (4.2.1)
actionpack (= 4.2.1)
activesupport (= 4.2.1)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.13.0)
rake (10.4.2)
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rdoc (4.2.0)
recurrence (1.3.0)
activesupport
i18n
redis (3.2.1)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
ref (1.0.5)
responders (2.1.0)
railties (>= 4.2.0, < 5)
rolify (4.0.0)
rspec (3.2.0)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-core (3.2.2)
rspec-support (~> 3.2.0)
rspec-expectations (3.2.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-mocks (3.2.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-rails (3.2.1)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-support (~> 3.2.0)
rspec-support (3.2.2)
rufus-scheduler (3.0.9)
tzinfo
rvm-capistrano (1.5.6)
capistrano (~> 2.15.4)
sass (3.4.13)
sass-rails (5.0.1)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (~> 1.1)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
seed_dump (3.2.2)
activerecord (~> 4)
activesupport (~> 4)
shellany (0.0.1)
sidekiq (3.3.3)
celluloid (>= 0.16.0)
connection_pool (>= 2.1.1)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sidekiq-cron (0.2.0)
rufus-scheduler (>= 2.0.24)
sidekiq (>= 2.17.3)
tilt (< 2.0.0)
simple_oauth (0.3.1)
sinatra (1.4.6)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
slop (3.6.0)
spring (1.3.3)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (2.12.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (2.2.4)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
thor (0.19.1)
thread_safe (0.3.5)
tilt (1.4.1)
timers (4.0.1)
hitimes
twitter (5.14.0)
addressable (~> 2.3)
buftok (~> 0.2.0)
equalizer (~> 0.0.9)
faraday (~> 0.9.0)
http (~> 0.6.0)
http_parser.rb (~> 0.6.0)
json (~> 1.8)
memoizable (~> 0.4.0)
naught (~> 1.0)
simple_oauth (~> 0.3.0)
twitter-text (1.11.0)
unf (~> 0.1.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.6)
unicorn (4.8.3)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
warden (1.2.3)
rack (>= 1.0)
web-console (2.1.2)
activemodel (>= 4.0)
binding_of_caller (>= 0.7.2)
railties (>= 4.0)
sprockets-rails (>= 2.0, < 4.0)
PLATFORMS
ruby
DEPENDENCIES
aasm
angularjs-rails
awesome_print
bootstrap-sass
byebug
capistrano
capistrano-sidekiq
carrierwave
coffee-rails (~> 4.1.0)
compass-rails (= 2.0.4)
database_cleaner
devise
devise-async
disqus_api
factory_girl_rails
faker
figaro
font-awesome-rails
foreman
forgery
friendly_id (~> 5.1.0)
guard-rspec
jbuilder (~> 2.0)
jquery-rails
kaminari
letter_opener
mandrill_dm!
mini_magick
notify_with
pg
puma
pundit
rails (= 4.2.1)
rails_12factor
recurrence
responders (~> 2.0)
rolify
rspec-rails
rvm-capistrano
sass-rails (= 5.0.1)
sdoc (~> 0.4.0)
seed_dump
sidekiq
sidekiq-cron
sinatra
spring
spring-commands-rspec
therubyracer
twitter
twitter-text
uglifier (>= 1.3.0)
unicorn
web-console (~> 2.0)

86
Guardfile Normal file
View File

@ -0,0 +1,86 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
## Uncomment and set this to only include directories you want to watch
# directories %w(app lib config test spec features)
## Uncomment to clear the screen before every task
# clearing :on
## Guard internally checks for changes in the Guardfile and exits.
## If you want Guard to automatically start up again, run guard in a
## shell loop, e.g.:
##
## $ while bundle exec guard; do echo "Restarting Guard..."; done
##
## Note: if you are using the `directories` clause above and you are not
## watching the project directory ('.'), then you will want to move
## the Guardfile to a watched dir and symlink it back, e.g.
#
# $ mkdir config
# $ mv Guardfile config/
# $ ln -s config/Guardfile .
#
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
# Note: The cmd option is now required due to the increasing number of ways
# rspec may be run, below are examples of the most common uses.
# * bundler: 'bundle exec rspec'
# * bundler binstubs: 'bin/rspec'
# * spring: 'bin/rspec' (This will use spring if running and you have
# installed the spring binstubs per the docs)
# * zeus: 'zeus rspec' (requires the server to be started separately)
# * 'just' rspec: 'rspec'
guard :rspec, cmd: "bundle exec spring rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# Feel free to open issues for suggestions and improvements
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
# Rails files
rails = dsl.rails(view_extensions: %w(erb haml slim))
dsl.watch_spec_files_for(rails.app_files)
dsl.watch_spec_files_for(rails.views)
watch(rails.controllers) do |m|
[
rspec.spec.("routing/#{m[1]}_routing"),
rspec.spec.("controllers/#{m[1]}_controller"),
rspec.spec.("acceptance/#{m[1]}")
]
end
# Rails config changes
watch(rails.spec_helper) { rspec.spec_dir }
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
# Capybara features specs
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
end
watch('spec/spec_helper.rb') { "spec" }
watch('config/routes.rb') { "spec/routing" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
end

2
Procfile Normal file
View File

@ -0,0 +1,2 @@
web: bundle exec rails server puma -p $PORT
worker: bundle exec sidekiq -C ./config/sidekiq.yml

127
README.md Normal file
View File

@ -0,0 +1,127 @@
# README
This project is the FabLab Manager web application.
The purpose of this web application is to allow users to document their FabLab projects. The FabLab also have the ability
to plan some events (workshops or courses) and to expose them to its users.
This product can be extended to be used as a complete internal management system for a FabLab.
The underlying technologies are:
- `Ruby on Rails` for the backend application (server RESTful API)
- `AngularJS` for the frontend application (web-based graphical user interface)
## 1. Configuration
The following files must be filled with the correct configuration to allow FabManager to run correctly:
- config/environments/production.rb
- `mandrill` -> change this if you're using a different mailing system
- config/environments/staging.rb
- `config.action_mailer.default_url_options` -> change the URL according to the staging deployment url
- `mandrill` -> change this if you're using a different mailing system
- config/application.yml
- `DEVISE_KEY` -> generate any secret phrase to secure the Devise authentication. You can use the `$ rake secret` command for this purpose.
- `SECRET_KEY_BASE` -> generate any secret phrase here to prevent XSS attacks. You can use the `$ rake secret` command for this purpose.
- `DEFAULT_MAIL_FROM` -> default e-mail address from which the emails are sent
- `MANDRILL_USERNAME` -> if you plan to use mandrill
- `MANDRILL_APIKEY` -> if you plan to use mandrill
- `TWITTER_NAME` -> twitter api configuration
- `TWITTER_CONSUMER_KEY` -> twitter api configuration
- `TWITTER_CONSUMER_SECRET` -> twitter api configuration
- `TWITTER_ACCESS_TOKEN` -> twitter api configuration
- `TWITTER_ACCESS_TOKEN_SECRET` -> twitter api configuration
- `GOOGLE_ANALYTICS_ACCOUNT` -> Google analytics account identifier (if you want to use GA)
- `APPLICATION_ROOT_URL` -> The public URL where you application is deployed in production (eg. fablab.lacasemate.com)
- config/mandrill.rb
You may change this if you don't want to use mandrill as your production mailing system
- config/database.yml.default
Copy/Paste this file to `config/database.yml` and modify the configuration according to your postgreSQL configuration
- config/disqus_api.yml
Insert here your identifiers for the Disqus API
## 2. Setup a development environment
1. Install RVM with latest ruby version
See http://rvm.io/rvm/install
2. Retrieve the project from Git
`$ git clone git@github.com:LaCasemate/fab-manager.git`
3. Install the dependencies
- Ubuntu: `$ sudo apt-get install libpq-dev postgresql redis-server imagemagick`
- MacOS: `$ brew install postgresql redis imagemagick`
4. Init the RVM instance and check it was correctly configured
```
$ cd fab-manager
$ rvm current
```
5. Setup the project requirements
`$ bundle install`
6. Build the database. You may have to configure your postgreSQL instance before, as described in chapter `3.2 Setup the FabManager database in PostgreSQL`
`$ rake db:setup`
7. Create the pids folder used by sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml`
`$ mkdir -p tmp/pids`
8. Configure the application environment variables, as explained in chapter `1. Configuration`
9. Start the development web server
`$ foreman s -p 3000`
## 3. PostgreSQL
### 3.1 Launch PostgreSQL on MacOS
$ ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
The first command will start postgresql at login with launchd. The second will load postgresql now.
### 3.2 Setup the FabManager database in PostgreSQL
1. Login as the postgres user
`$ sudo -i -u postgres`
2. Run the postgreSQL administration command line interface
`$ psql`
3. Create a new user in postgres (in this example, the user will be named "sleede")
`# CREATE USER sleede;`
4. Grant him the right to create databases
`# ALTER ROLE sleede WITH CREATEDB;`
5. Then create the fablab database
`# CREATE DATABASE fabmanager_development OWNER sleede;`
6. To finish, attribute a password to this user
`# ALTER USER sleede WITH ENCRYPTED PASSWORD 'sleede';`
## 4. Know issues
If you encounter a problem with bundler (unable to run `$ rails c` or `$ rails g`), you can fix it running the following commands:
$ bundle pack
$ bundle install --path vendor/cache
## 5. Related Documentation
- Angular-Bootstrap: http://angular-ui.github.io/bootstrap/

6
Rakefile Normal file
View File

@ -0,0 +1,6 @@
# 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.
require File.expand_path('../config/application', __FILE__)
Rails.application.load_tasks

0
app/assets/images/.keep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
app/assets/images/no_avatar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

View File

@ -0,0 +1,91 @@
'use strict';
/**
* The application file bootstraps the angular app by initializing the main module and
* creating namespaces and moduled for controllers, filters, services, and directives.
*/
var Application = Application || {};
Application.Constants = angular.module('application.constants', []);
Application.Services = angular.module('application.services', []);
Application.Controllers = angular.module('application.controllers', []);
Application.Filters = angular.module('application.filters', []);
Application.Directives = angular.module('application.directives', []);
angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ngAnimate', 'ngCookies', 'ui.router', 'ui.bootstrap',
'ngUpload', 'duScroll', 'application.filters','application.services', 'application.directives',
'application.constants', 'application.controllers', 'application.router', 'ui.select2', 'angularMoment',
'Devise', 'DeviseModal', 'angular-growl', 'xeditable', 'checklist-model', 'unsavedChanges', 'angular-loading-bar',
'ngTouch', 'angular-google-analytics', 'angularUtils.directives.dirDisqus', 'angular-redactor']).
config(['$locationProvider', '$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfigProvider", "AnalyticsProvider", "datepickerPopupConfig",
function($locationProvider, $httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, datepickerPopupConfig) {
<% if Rails.env.production? and ENV["GOOGLE_ANALYTICS_ACCOUNT"] != 'UA-YOUR_ID_HERE' and ENV["GOOGLE_ANALYTICS_ACCOUNT"] != nil %>
AnalyticsProvider.setAccount('<%= ENV["GOOGLE_ANALYTICS_ACCOUNT"] %>');
// track all routes (or not)
AnalyticsProvider.trackPages(true);
AnalyticsProvider.setDomainName('<%= ENV["APPLICATION_ROOT_URL"] %>');
AnalyticsProvider.useAnalytics(true);
AnalyticsProvider.setPageEvent('$stateChangeSuccess');
<% else %>
AnalyticsProvider.setAccount('DISABLED');
<% end %>
datepickerPopupConfig.closeText = "Fermer";
datepickerPopupConfig.cleartext = "Effacer";
datepickerPopupConfig.currentText = "Aujourd'hui";
// custom message for angular-unsavedChanges
unsavedWarningsConfigProvider.navigateMessage = "Vous perdrez les modifications non enregistrées si vous quittez cette page";
unsavedWarningsConfigProvider.reloadMessage = "Vous perdrez les modifications non enregistrées si vous rechargez cette page";
growlProvider.globalTimeToLive(5000);
growlProvider.globalEnableHtml(true);
$locationProvider.hashPrefix('!');
}]).run(["$rootScope", "$log", "AuthService", "Auth", "amMoment", "$state", "editableOptions", "$location", "Analytics", function($rootScope, $log, AuthService, Auth, amMoment, $state, editableOptions, $location, Analytics){
amMoment.changeLocale('fr');
editableOptions.theme = 'bs3';
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
$state.prevState = fromState;
$state.prevParams = fromParams;
});
$rootScope.backPrevLocation = function(event){
event.preventDefault();
event.stopPropagation();
if($state.prevState.name == ""){
$state.prevState = "app.public.home";
}
$state.go($state.prevState, $state.prevParams);
};
}]).filter('array', function() {
return function(arrayLength) {
if (arrayLength) {
arrayLength = Math.ceil(arrayLength);
var arr = new Array(arrayLength), i = 0;
for (; i < arrayLength; i++) {
arr[i] = i;
}
return arr;
}
};
}).directive('datepickerPopup', function (){
// fixes https://github.com/angular-ui/bootstrap/issues/2659
return {
restrict: 'EAC',
require: 'ngModel',
link: function(scope, element, attr, controller) {
//remove the default formatter from the input directive to prevent conflict
controller.$formatters.shift();
}
}
});

View File

@ -0,0 +1,59 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require jquery-ui/ui/jquery.ui.core
//= require jquery-ui/ui/jquery.ui.widget
//= require jquery-ui/ui/jquery.ui.mouse
//= require jquery-ui/ui/jquery.ui.draggable
//= require jquery-ui/ui/jquery.ui.droppable
//= require jquery-ui/ui/jquery.ui.resizable
//= require angular
//= require angular-i18n/angular-locale_fr-fr.js
//= require angular-cookies
//= require angular-resource
//= require angular-sanitize
//= require angular-animate
//= require angular-cookies
//= require angular-touch
//= require angular-ui-router/release/angular-ui-router
//= require angular-bootstrap/ui-bootstrap-tpls
//= require select2/select2
//= require select2/select2_locale_fr
//= require angular-ui-select2/src/select2
//= require moment/moment
//= require moment/locale/fr
//= require angular-moment/angular-moment
//= require ngUpload/ng-upload
//= require jasny-bootstrap/js/fileinput
//= require holderjs/holder
//= require angular-devise/lib/devise
//= require devise-modal
//= require angular-growl/build/angular-growl
//= require angular-xeditable/dist/js/xeditable
//= require checklist-model/checklist-model
//= require angular-unsavedChanges/src/unsavedChanges
//= require angular-loading-bar/src/loading-bar
//= require angular-scroll/angular-scroll
//= require angular-google-analytics/src/angular-google-analytics
//= require dirDisqus
//= require humanize
//= require redactor
//= require angular-redactor/angular-redactor
//= require underscore/underscore
//= require app
//= require router
//= require_tree ./controllers
//= require_tree ./services
//= require_tree ./directives
//= require_tree ./filters

View File

@ -0,0 +1,242 @@
'use strict'
### COMMON CODE ###
##
# Provides a set of common properties and methods to the $scope parameter. They are used
# in the various events' admin controllers.
#
# Provides :
# - $scope.categories = [{Category}]
# - $scope.datePicker = {}
# - $scope.submited(content)
# - $scope.cancel()
# - $scope.addFile()
# - $scope.deleteFile(file)
# - $scope.fileinputClass(v)
# - $scope.openStartDatePicker($event)
# - $scope.openEndDatePicker($event)
# - $scope.toggleRecurrenceEnd(e)
#
# Requires :
# - $scope.event.event_files_attributes = []
# - $state (Ui-Router) [ 'app.public.events_list' ]
##
class EventsController
constructor: ($scope, $state, Event, Category) ->
## Retrieve the list of categories from the server (stage, atelier, ...)
Category.query().$promise.then (data)->
$scope.categories = data.map (d) ->
id: d.id
name: d.name
## default parameters for AngularUI-Bootstrap datepicker
$scope.datePicker =
format: 'dd/MM/yyyy'
startOpened: false # default: datePicker is not shown
endOpened: false
recurrenceEndOpened: false
options:
startingDay: 1 # France: the week starts on monday
##
# For use with ngUpload (https://github.com/twilson63/ngUpload).
# Intended to be the callback when an upload is done: any raised error will be stacked in the
# $scope.alerts array. If everything goes fine, the user is redirected to the project page.
# @param content {Object} JSON - The upload's result
##
$scope.submited = (content) ->
if !content.id?
$scope.alerts = []
angular.forEach content, (v, k)->
angular.forEach v, (err)->
$scope.alerts.push({msg: k+': '+err, type: 'danger'})
else
$state.go('app.public.events_list')
##
# Changes the user's view to the events list page
##
$scope.cancel = ->
$state.go('app.public.events_list')
##
# For use with 'ng-class', returns the CSS class name for the uploads previews.
# The preview may show a placeholder or the content of the file depending on the upload state.
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
##
$scope.fileinputClass = (v)->
if v
'fileinput-exists'
else
'fileinput-new'
##
# This will create a single new empty entry into the event's attachements list.
##
$scope.addFile = ->
$scope.event.event_files_attributes.push {}
##
# This will remove the given file from the event's attachements list. If the file was previously uploaded
# to the server, it will be marked for deletion on the server. Otherwise, it will be simply truncated from
# the attachements array.
# @param file {Object} the file to delete
##
$scope.deleteFile = (file) ->
index = $scope.event.event_files_attributes.indexOf(file)
if file.id?
file._destroy = true
else
$scope.event.event_files_attributes.splice(index, 1)
##
# Show/Hide the "start" datepicker (open the drop down/close it)
##
$scope.toggleStartDatePicker = ($event) ->
$event.preventDefault()
$event.stopPropagation()
$scope.datePicker.startOpened = !$scope.datePicker.startOpened
##
# Show/Hide the "end" datepicker (open the drop down/close it)
##
$scope.toggleEndDatePicker = ($event) ->
$event.preventDefault()
$event.stopPropagation()
$scope.datePicker.endOpened = !$scope.datePicker.endOpened
##
# Masks/displays the recurrence pane allowing the admin to set the current event as recursive
##
$scope.toggleRecurrenceEnd = (e)->
e.preventDefault()
e.stopPropagation()
$scope.datePicker.recurrenceEndOpened = !$scope.datePicker.recurrenceEndOpened
##
# Controller used in the events listing page (admin view)
##
Application.Controllers.controller "adminEventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) ->
### PUBLIC SCOPE ###
## The events displayed on the page
$scope.events = []
## By default, the pagination mode is activated to limit the page size
$scope.paginateActive = true
## The currently displayed page number
$scope.page = 1
##
# Adds a bucket of events to the bottom of the page, grouped by month
##
$scope.loadMoreEvents = ->
Event.query {page: $scope.page}, (data)->
$scope.events = $scope.events.concat data
if data.length
$scope.paginateActive = false if $scope.events.length >= data[0].nb_total_events
else
$scope.paginateActive = false
$scope.page += 1
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
$scope.loadMoreEvents()
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]
##
# Controller used in the event creation page
##
Application.Controllers.controller "newEventController", ["$scope", "$state", 'Event', 'Category', 'CSRF', ($scope, $state, Event, Category, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/events/"
## Form action on the above URL
$scope.method = 'post'
## Default event parameters
$scope.event =
event_files_attributes: []
start_date: new Date()
end_date: new Date()
start_time: new Date()
end_time: new Date()
all_day: 'false'
recurrence: 'none'
## Possible types of recurrences for an event
$scope.recurrenceTypes = [
{label: 'Aucune', value: 'none'},
{label: 'Tous les jours', value: 'day'},
{label: 'Chaque semaine', value: 'week'},
{label: 'Chaque mois', value: 'month'},
{label: 'Chaque année', value: 'year'}
]
## Using the EventsController
new EventsController($scope, $state, Event, Category)
]
##
# Controller used in the events edition page
##
Application.Controllers.controller "editEventController", ["$scope", "$state", "$stateParams", 'Event', 'Category', 'CSRF', ($scope, $state, $stateParams, Event, Category, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/events/" + $stateParams.id
## Form action on the above URL
$scope.method = 'put'
## Retrieve the event details, in case of error the user is redirected to the events listing
Event.get {id: $stateParams.id}
, (event)->
$scope.event = event
return
, ->
$state.go('app.public.events_list')
## Using the EventsController
new EventsController($scope, $state, Event, Category)
]

View File

@ -0,0 +1,153 @@
'use strict'
### COMMON CODE ###
##
# Provides a set of common properties and methods to the $scope parameter. They are used
# in the various members' admin controllers.
#
# Provides :
# - $scope.groups = [{Group}]
# - $scope.datePicker = {}
# - $scope.submited(content)
# - $scope.cancel()
# - $scope.fileinputClass(v)
# - $scope.openDatePicker($event)
#
# Requires :
# - $state (Ui-Router) [ 'app.admin.members' ]
##
class MembersController
constructor: ($scope, $state, Group) ->
## Retrieve the profiles groups (eg. students ...)
Group.query (groups) ->
$scope.groups = groups
$scope.user.group_id = $scope.groups[0].id
## Default parameters for AngularUI-Bootstrap datepicker
$scope.datePicker =
format: 'dd/MM/yyyy'
opened: false # default: datePicker is not shown
options:
startingDay: 1 # France: the week starts on monday
##
# Shows the birth day datepicker
# @param $event {Object} jQuery event object
##
$scope.openDatePicker = ($event) ->
$event.preventDefault()
$event.stopPropagation()
$scope.datePicker.opened = true
##
# For use with ngUpload (https://github.com/twilson63/ngUpload).
# Intended to be the callback when an upload is done: any raised error will be stacked in the
# $scope.alerts array. If everything goes fine, the user is redirected to the members listing page.
# @param content {Object} JSON - The upload's result
##
$scope.submited = (content) ->
if !content.id?
$scope.alerts = []
angular.forEach content, (v, k)->
angular.forEach v, (err)->
$scope.alerts.push
msg: k+': '+err,
type: 'danger'
else
$state.go('app.admin.members')
##
# Changes the admin's view to the members list page
##
$scope.cancel = ->
$state.go('app.admin.members')
##
# For use with 'ng-class', returns the CSS class name for the uploads previews.
# The preview may show a placeholder or the content of the file depending on the upload state.
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
##
$scope.fileinputClass = (v)->
if v
'fileinput-exists'
else
'fileinput-new'
##
# Controller used in the member edition page
##
Application.Controllers.controller "editMemberController", ["$scope", "$state", "$stateParams", "Member", 'dialogs', 'growl', 'Group', 'CSRF', ($scope, $state, $stateParams, Member, dialogs, growl, Group, CSRF) ->
CSRF.setMetaTags()
### PUBLIC SCOPE ###
## API URL where the form will be posted
$scope.actionUrl = "/api/members/" + $stateParams.id
## Form action on the above URL
$scope.method = 'patch'
## The user to edit
$scope.user = {}
## Profiles types (student/standard/...)
$scope.groups = []
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
## Retrieve the member's profile details
Member.get {id: $stateParams.id}, (resp)->
$scope.user = resp
## Using the MembersController
new MembersController($scope, $state, Group)
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]
##
# Controller used in the member's creation page (admin view)
##
Application.Controllers.controller "newMemberController", ["$scope", "$state", "$stateParams", "Member", 'Group', 'CSRF', ($scope, $state, $stateParams, Member, Group, CSRF) ->
CSRF.setMetaTags()
### PUBLIC SCOPE ###
## API URL where the form will be posted
$scope.actionUrl = "/api/members"
## Form action on the above URL
$scope.method = 'post'
## Default member's profile parameters
$scope.user =
plan_interval: ''
## Using the MembersController
new MembersController($scope, $state, Group)
]

View File

@ -0,0 +1,157 @@
'use strict'
Application.Controllers.controller "projectElementsController", ["$scope", "$state", 'Component', 'Licence', 'Theme', ($scope, $state, Component, Licence, Theme) ->
## Materials list (plastic, wood ...)
$scope.components = Component.query()
## Licences list (Creative Common ...)
$scope.licences = Licence.query()
## Themes list (cooking, sport ...)
$scope.themes = Theme.query()
##
# Saves a new component / Update an existing material to the server (form validation callback)
# @param data {Object} component name
# @param [data] {number} component id, in case of update
##
$scope.saveComponent = (data, id) ->
if id?
Component.update {id: id}, data
else
Component.save data, (resp)->
$scope.components[$scope.components.length-1].id = resp.id
##
# Deletes the component at the specified index
# @param index {number} component index in the $scope.components array
##
$scope.removeComponent = (index) ->
Component.delete $scope.components[index]
$scope.components.splice(index, 1)
##
# Creates a new empty entry in the $scope.components array
##
$scope.addComponent = ->
$scope.inserted =
name: ''
$scope.components.push($scope.inserted)
##
# Removes the newly inserted but not saved component / Cancel the current component modification
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
# @param index {number} component index in the $scope.components array
##
$scope.cancelComponent = (rowform, index) ->
if $scope.components[index].id?
rowform.$cancel()
else
$scope.components.splice(index, 1)
##
# Saves a new theme / Update an existing theme to the server (form validation callback)
# @param data {Object} theme name
# @param [data] {number} theme id, in case of update
##
$scope.saveTheme = (data, id) ->
if id?
Theme.update {id: id}, data
else
Theme.save data, (resp)->
$scope.themes[$scope.themes.length-1].id = resp.id
##
# Deletes the theme at the specified index
# @param index {number} theme index in the $scope.themes array
##
$scope.removeTheme = (index) ->
Theme.delete $scope.themes[index]
$scope.themes.splice(index, 1)
##
# Creates a new empty entry in the $scope.themes array
##
$scope.addTheme = ->
$scope.inserted =
name: ''
$scope.themes.push($scope.inserted)
##
# Removes the newly inserted but not saved theme / Cancel the current theme modification
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
# @param index {number} theme index in the $scope.themes array
##
$scope.cancelTheme = (rowform, index) ->
if $scope.themes[index].id?
rowform.$cancel()
else
$scope.themes.splice(index, 1)
##
# Saves a new licence / Update an existing licence to the server (form validation callback)
# @param data {Object} licence name and description
# @param [data] {number} licence id, in case of update
##
$scope.saveLicence = (data, id) ->
if id?
Licence.update {id: id}, data
else
Licence.save data, (resp)->
$scope.licences[$scope.licences.length-1].id = resp.id
##
# Deletes the licence at the specified index
# @param index {number} licence index in the $scope.licences array
##
$scope.removeLicence = (index) ->
Licence.delete $scope.licences[index]
$scope.licences.splice(index, 1)
##
# Creates a new empty entry in the $scope.licences array
##
$scope.addLicence = ->
$scope.inserted =
name: ''
description: ''
$scope.licences.push($scope.inserted)
##
# Removes the newly inserted but not saved licence / Cancel the current licence modification
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
# @param index {number} licence index in the $scope.licences array
##
$scope.cancelLicence = (rowform, index) ->
if $scope.licences[index].id?
rowform.$cancel()
else
$scope.licences.splice(index, 1)
]

View File

@ -0,0 +1,330 @@
'use strict'
Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "Session", "AuthService", "Auth", "$modal", "$state", 'growl', 'Notification', '$interval', ($rootScope, $scope, Session, AuthService, Auth, $modal, $state, growl, Notification, $interval) ->
### PRIVATE STATIC CONSTANTS ###
# User's notifications will get refreshed every 30s
NOTIFICATIONS_CHECK_PERIOD = 30000
### PUBLIC SCOPE ###
##
# Set the current user to the provided value and initialize the session
# @param user {Object} Rails/Devise user
##
$scope.setCurrentUser = (user) ->
$scope.currentUser = user
Session.create(user);
getNotifications()
##
# Login callback
# @param e {Object} jQuery event
# @param callback {function}
##
$scope.login = (e, callback) ->
e.preventDefault() if e
openLoginModal null, null, callback
##
# Logout callback
# @param e {Object} jQuery event
##
$scope.logout = (e) ->
e.preventDefault()
Auth.logout().then (oldUser) ->
# console.log(oldUser.name + " you're signed out now.");
Session.destroy()
$scope.currentUser = null
$rootScope.toCheckNotifications = false
$scope.notifications = []
$state.go('app.public.home')
, (error) ->
# An error occurred logging out.
##
# Open the modal window allowing the user to create an account.
# @param e {Object} jQuery event
##
$scope.signup = (e) ->
e.preventDefault() if e
$modal.open
templateUrl: '<%= asset_path "shared/signupModal.html" %>'
size: 'md'
controller: ['$scope', '$modalInstance', 'Group', ($scope, $modalInstance, Group) ->
# default parameters for the date picker in the account creation modal
$scope.datePicker =
format: 'dd/MM/yyyy'
opened: false
options:
startingDay: 1
# callback to open the date picker (account creation modal)
$scope.openDatePicker = ($event) ->
$event.preventDefault()
$event.stopPropagation()
$scope.datePicker.opened = true
# retrieve the groups (standard, student ...)
Group.query (groups) ->
$scope.groups = groups
# default user's parameters
$scope.user =
is_allow_contact: true
# Errors display
$scope.alerts = []
$scope.closeAlert = (index) ->
$scope.alerts.splice(index, 1)
# callback for form validation
$scope.ok = ->
# try to create the account
$scope.alerts = []
Auth.register($scope.user).then (user) ->
# creation successful
$modalInstance.close(user)
, (error) ->
# creation failed...
angular.forEach error.data.errors, (v, k) ->
angular.forEach v, (err) ->
$scope.alerts.push
msg: k+': '+err
type: 'danger'
]
.result['finally'](null).then (user) ->
# when the account was created succesfully, set the session to the newly created account
$scope.setCurrentUser(user)
##
# Open the modal window allowing the user to change his password.
# @param token {string} security token for password changing. The user should have recieved it by mail
##
$scope.editPassword = (token) ->
$modal.open
templateUrl: '<%= asset_path "shared/passwordEditModal.html" %>'
size: 'md'
controller: ['$scope', '$modalInstance', '$http', ($scope, $modalInstance, $http) ->
$scope.user =
reset_password_token: token
$scope.alerts = []
$scope.closeAlert = (index) ->
$scope.alerts.splice(index, 1)
$scope.changePassword = ->
$scope.alerts = []
$http.put('/users/password.json', {user: $scope.user}).success (data) ->
$modalInstance.close()
.error (data) ->
angular.forEach data.errors, (v, k) ->
angular.forEach v, (err) ->
$scope.alerts.push
msg: k+': '+err
type: 'danger'
]
.result['finally'](null).then (user) ->
growl.addInfoMessage('Votre mot de passe a bien été modifié.')
Auth.login().then (user) ->
$scope.setCurrentUser(user)
, (error) ->
# Authentication failed...
##
# Compact/Expend the width of the left navigation bar
# @param e {Object} jQuery event object
##
$scope.toggleNavSize = (event) ->
if typeof event == 'undefined'
console.error '[applicationController::toggleNavSize] Missing event parameter'
return
toggler = $(event.target)
toggler = toggler.closest('[data-toggle^="class"]') unless toggler.data('toggle')
$class = toggler.data()['toggle']
$target = toggler.data('target') or toggler.attr('data-link')
if $class
$tmp = $class.split(':')[1]
$classes = $tmp.split(',') if $tmp
if $target
$targets = $target.split(',')
if $classes and $classes.length
$.each $targets, ( index, value ) ->
if $classes[index].indexOf( '*' ) != -1
patt = new RegExp( '\\s'
+ $classes[index].replace( /\*/g, '[A-Za-z0-9-_]+' ).split( ' ' ).join( '\\s|\\s' )
+ '\\s', 'g' )
$(toggler).each ( i, it ) ->
cn = ' ' + it.className + ' '
while patt.test( cn )
cn = cn.replace( patt, ' ' )
it.className = $.trim( cn )
($targets[index] !='#') and $($targets[index]).toggleClass($classes[index]) or toggler.toggleClass($classes[index])
toggler.toggleClass('active')
return
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
# try to retrieve any currently logged user
Auth.login().then (user) ->
$scope.setCurrentUser(user)
, (error) ->
# Authentication failed...
$rootScope.toCheckNotifications = false
# bind to the $stateChangeStart event (AngularJS/UI-Router)
$rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams) ->
return unless toState.data
authorizedRoles = toState.data.authorizedRoles
unless AuthService.isAuthorized(authorizedRoles)
event.preventDefault()
if AuthService.isAuthenticated()
# user is not allowed
console.log('user is not allowed')
else
# user is not logged in
openLoginModal(toState, toParams)
# shorthands
$scope.isAuthenticated = Auth.isAuthenticated;
$scope.isAuthorized = AuthService.isAuthorized;
##
# Retreive once the notifications from the server and display a message popup for each new one.
# Then, periodically check for new notifications.
##
getNotifications = ->
$rootScope.toCheckNotifications = true
unless $rootScope.checkNotificationsIsInit or !$scope.currentUser
$scope.notifications = Notification.query {is_read: false}
$scope.$watch 'notifications', (newValue, oldValue) ->
diff = []
angular.forEach newValue, (value) ->
find = false
for i in [0..oldValue.length] by 1
if oldValue[i] and (value.id is oldValue[i].id)
find = true
break
unless find
diff.push(value)
angular.forEach diff, (notification, key) ->
growl.addInfoMessage(notification.message.description)
, true
checkNotifications = ->
if $rootScope.toCheckNotifications
Notification.query({is_read: false}).$promise.then (data) ->
$scope.notifications = data;
$interval(checkNotifications, NOTIFICATIONS_CHECK_PERIOD)
$rootScope.checkNotificationsIsInit = true
##
# Open the modal window allowing the user to log in.
##
openLoginModal = (toState, toParams, callback) ->
$modal.open
templateUrl: '<%= asset_path "shared/deviseModal.html" %>'
size: 'sm'
controller: ['$scope', '$modalInstance', ($scope, $modalInstance) ->
user = $scope.user = {}
$scope.login = () ->
Auth.login(user).then (user) ->
# Authentification succeeded ...
$modalInstance.close(user)
if callback and typeof callback is "function"
callback(user)
, (error) ->
# Authentication failed...
$scope.alerts = []
$scope.alerts.push
msg: 'E-mail ou mot de passe incorrect.'
type: 'danger'
# handle modal behaviors. The provided reason will be used to define the following actions
$scope.dismiss = ->
$modalInstance.dismiss('cancel')
$scope.openSignup = (e) ->
e.preventDefault()
$modalInstance.dismiss('signup')
$scope.openResetPassword = (e) ->
e.preventDefault()
$modalInstance.dismiss('resetPassword')
]
# what to do when the modal is closed
.result['finally'](null).then (user) ->
# authentification succeeded, set the session, gather the notifications and redirect
$scope.setCurrentUser(user)
if toState isnt null and toParams isnt null
$state.go(toState, toParams)
, (reason) ->
# authentification did not ended successfully
if reason is 'signup'
# open signup modal
$scope.signup()
else if reason is 'resetPassword'
# open the 'reset password' modal
$modal.open
templateUrl: '<%= asset_path "shared/passwordNewModal.html" %>'
size: 'sm'
controller: ['$scope', '$modalInstance', '$http', ($scope, $modalInstance, $http) ->
$scope.user = {email: ''}
$scope.sendReset = () ->
$scope.alerts = []
$http.post('/users/password.json', {user: $scope.user}).success ->
$modalInstance.close()
.error ->
$scope.alerts.push
msg: "Votre adresse email n'existe pas."
type: 'danger'
]
.result['finally'](null).then ->
growl.addInfoMessage('Vous allez recevoir sous quelques minutes un e-mail vous indiquant comment réinitialiser votre mot de passe.')
# otherwise the user just closed the modal
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]

View File

@ -0,0 +1,43 @@
'use strict'
##
# Controller used on the private projects listing page (my dashboard/projects)
##
Application.Controllers.controller "dashboardProjectsController", ["$scope", 'Member', ($scope, Member) ->
## Current user's profile
$scope.user = Member.get {id: $scope.currentUser.id}
]
##
# Controller used on the personal trainings page (my dashboard/trainings)
##
Application.Controllers.controller "dashboardTrainingsController", ["$scope", 'Member', ($scope, Member) ->
## Current user's profile
$scope.user = Member.get {id: $scope.currentUser.id}
]
##
# Controller used on the private events page (my dashboard/events)
##
Application.Controllers.controller "dashboardEventsController", ["$scope", 'Member', ($scope, Member) ->
## Current user's profile
$scope.user = Member.get {id: $scope.currentUser.id}
]
##
# Controller used on the personal invoices listing page (my dashboard/invoices)
##
Application.Controllers.controller "dashboardInvoicesController", ["$scope", 'Member', ($scope, Member) ->
## Current user's profile
$scope.user = Member.get {id: $scope.currentUser.id}
]

View File

@ -0,0 +1,120 @@
'use strict'
Application.Controllers.controller "eventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) ->
### PRIVATE STATIC CONSTANTS ###
# Number of events added to the page when the user clicks on 'load next events'
EVENTS_PER_PAGE = 12
### PUBLIC SCOPE ###
## The events displayed on the page
$scope.events = []
## By default, the pagination mode is activated to limit the page size
$scope.paginateActive = true
## The currently displayed page number
$scope.page = 1
##
# Adds EVENTS_PER_PAGE events to the bottom of the page, grouped by month
##
$scope.loadMoreEvents = ->
Event.query {page: $scope.page}, (data) ->
$scope.events = $scope.events.concat data
if data.length > 0
$scope.paginateActive = false if ($scope.page-2)*EVENTS_PER_PAGE+data.length >= data[0].nb_total_events
$scope.eventsGroupByMonth = _.groupBy($scope.events, (obj) ->
_.map ['month', 'year'], (key, value) -> obj[key]
)
$scope.monthOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)->
monthYearArray = k.split(',')
date = new Date()
date.setMonth(monthYearArray[0])
date.setYear(monthYearArray[1])
return -date.getTime()
else
$scope.paginateActive = false
$scope.page += 1
##
# Callback to redirect the user to the specified event page
# @param event {{id:number}}
##
$scope.showEvent = (event) ->
$state.go('app.public.events_show', {id: event.id})
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
$scope.loadMoreEvents()
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]
Application.Controllers.controller "showEventController", ["$scope", "$state", "$stateParams", "Event", '$modal', 'Member', ($scope, $state, $stateParams, Event, $modal, Member) ->
### PUBLIC SCOPE ###
## current event details
$scope.event = {}
##
# Callback to delete the provided event (admins only)
# @param event {$resource} angular's Event $resource
##
$scope.deleteEvent = (event) ->
event.$delete ->
$state.go('app.public.events_list')
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
# get the details for the current event (event's id is recovered from the current URL)
Event.get {id: $stateParams.id}
, (data) ->
$scope.event = data
if !$scope.event.reduced_amount
$scope.event.reduced_amount = 0
return
, ->
$state.go('app.public.events_list')
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]

View File

@ -0,0 +1,60 @@
'use strict'
Application.Controllers.controller "homeController", ['$scope', '$stateParams', 'Member', 'Twitter', 'Project', 'Event', ($scope, $stateParams, Member, Twitter, Project, Event) ->
### PRIVATE STATIC CONSTANTS ###
# The 4 last users will be displayed on the home page
LAST_MEMBERS_LIMIT = 4
# Only the last tweet is shown
LAST_TWEETS_LIMIT = 1
# The 3 closest events are shown
LAST_EVENTS_LIMIT = 3
### PUBLIC SCOPE ###
## The last registered members who confirmed their addresses
$scope.last_members = []
## The last tweets from the Fablab official twitter account
$scope.last_tweets = []
## The last projects published/documented on the plateform
$scope.last_projects = []
## The closest upcoming events
$scope.upcoming_events = []
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
# display the reset password dialog if the parameter was provided
if $stateParams.reset_password_token
$scope.$parent.editPassword($stateParams.reset_password_token)
# initialize the homepage data
Member.lastSubscribed {limit: LAST_MEMBERS_LIMIT}, (members) ->
$scope.last_members = members
Twitter.query {limit: LAST_TWEETS_LIMIT}, (tweets) ->
$scope.last_tweets = tweets
Project.lastPublished (projects) ->
$scope.last_projects = projects
Event.upcoming {limit: LAST_EVENTS_LIMIT}, (events) ->
$scope.upcoming_events = events
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]

View File

@ -0,0 +1,164 @@
'use strict'
### COMMON CODE ###
##
# Provides a set of common callback methods to the $scope parameter. These methods are used
# in the various machines' admin controllers.
#
# Provides :
# - $scope.submited(content)
# - $scope.cancel()
# - $scope.fileinputClass(v)
# - $scope.addFile()
# - $scope.deleteFile(file)
#
# Requires :
# - $scope.machine.machine_files_attributes = []
# - $state (Ui-Router) [ 'app.public.machines_list' ]
##
class MachinesController
constructor: ($scope, $state)->
##
# For use with ngUpload (https://github.com/twilson63/ngUpload).
# Intended to be the callback when the upload is done: any raised error will be stacked in the
# $scope.alerts array. If everything goes fine, the user is redirected to the machines list.
# @param content {Object} JSON - The upload's result
##
$scope.submited = (content) ->
if !content.id?
$scope.alerts = []
angular.forEach content, (v, k)->
angular.forEach v, (err)->
$scope.alerts.push
msg: k+': '+err
type: 'danger'
else
$state.go('app.public.machines_list')
##
# Changes the current user's view, redirecting him to the machines list
##
$scope.cancel = ->
$state.go('app.public.machines_list')
##
# For use with 'ng-class', returns the CSS class name for the uploads previews.
# The preview may show a placeholder or the content of the file depending on the upload state.
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
##
$scope.fileinputClass = (v)->
if v
'fileinput-exists'
else
'fileinput-new'
##
# This will create a single new empty entry into the machine attachements list.
##
$scope.addFile = ->
$scope.machine.machine_files_attributes.push {}
##
# This will remove the given file from the machine attachements list. If the file was previously uploaded
# to the server, it will be marked for deletion on the server. Otherwise, it will be simply truncated from
# the attachements array.
# @param file {Object} the file to delete
##
$scope.deleteFile = (file) ->
index = $scope.machine.machine_files_attributes.indexOf(file)
if file.id?
file._destroy = true
else
$scope.machine.machine_files_attributes.splice(index, 1)
##
# Controller used in the public listing page, allowing everyone to see the list of machines
##
Application.Controllers.controller "machinesController", ["$scope", "$state", 'Machine', '$modal', ($scope, $state, Machine, $modal) ->
## Retrieve the list of machines
$scope.machines = Machine.query()
##
# Redirect the user to the machine details page
##
$scope.showMachine = (machine) ->
$state.go('app.public.machines_show', {id: machine.slug})
]
##
# Controller used in the machine creation page (admin)
##
Application.Controllers.controller "newMachineController", ["$scope", "$state", 'CSRF', ($scope, $state, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/machines/"
## Form action on the above URL
$scope.method = "post"
## default machine parameters
$scope.machine =
machine_files_attributes: []
## Using the MachinesController
new MachinesController($scope, $state)
]
##
# Controller used in the machine edition page (admin)
##
Application.Controllers.controller "editMachineController", ["$scope", "$state", '$stateParams', 'Machine', 'CSRF', ($scope, $state, $stateParams, Machine, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/machines/" + $stateParams.id
## Form action on the above URL
$scope.method = "put"
## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list
$scope.machine = Machine.get {id: $stateParams.id}
, ->
return
, ->
$state.go('app.public.machines_list')
## Using the MachinesController
new MachinesController($scope, $state)
]
##
# Controller used in the machine details page (public)
##
Application.Controllers.controller "showMachineController", ['$scope', '$state', '$modal', '$stateParams', 'Machine', ($scope, $state, $modal, $stateParams, Machine) ->
## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list
$scope.machine = Machine.get {id: $stateParams.id}
, ->
return
, ->
$state.go('app.public.machines_list')
##
# Callback to delete the current machine (admins only)
##
$scope.delete = (machine) ->
# check the permissions
if $scope.currentUser.role isnt 'admin'
console.error 'Unauthorized operation'
else
# delete the machine then redirect to the machines listing
machine.$delete ->
$state.go('app.public.machines_list')
]

View File

@ -0,0 +1,57 @@
'use strict'
##
# Navigation controller. List the links availables in the left navigation pane and their icon.
##
Application.Controllers.controller "mainNavController", ["$scope", "$location", "$cookies", ($scope, $location, $cookies) ->
## Common links (public application)
$scope.navLinks = [
{
state: 'app.public.home'
linkText: 'Accueil'
linkIcon: 'home'
}
{
state: 'app.public.machines_list'
linkText: 'Liste des machines'
linkIcon: 'gears'
}
{
state: 'app.public.events_list'
linkText: 'Liste des stages et ateliers'
linkIcon: 'tags'
}
{
state: 'app.public.projects_list'
linkText: 'Galerie de projets'
linkIcon: 'th'
}
]
## Admin links (backoffice application)
$scope.adminNavLinks = [
{
state: 'app.admin.members'
linkText: 'Suivi utilisateurs'
linkIcon: 'users'
}
{
state: 'app.admin.events'
linkText: 'Suivi stages et ateliers'
linkIcon: 'tags'
}
{
state: 'app.public.machines_list'
linkText: 'Gérer les machines'
linkIcon: 'cogs'
}
{
state: 'app.admin.project_elements'
linkText: 'Gérer les éléments Projets'
linkIcon: 'tasks'
}
]
]

View File

@ -0,0 +1,109 @@
'use strict'
##
# Controller used in the members listing page
##
Application.Controllers.controller "membersController", ["$scope", "$state", 'Member', ($scope, $state, Member) ->
## members list
$scope.members = Member.query()
## Merbers ordering/sorting. Default: not sorted
$scope.orderMember = null
##
# Change the members ordering criterion to the one provided
# @param orderBy {string} ordering criterion
##
$scope.setOrderMember = (orderBy)->
if $scope.orderMember == orderBy
$scope.orderMember = '-'+orderBy
else
$scope.orderMember = orderBy
]
##
# Controller used when editing the current user's profile
##
Application.Controllers.controller "editProfileController", ["$scope", "$state", "Member", "Auth", 'growl', 'dialogs', 'CSRF', ($scope, $state, Member, Auth, growl, dialogs, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/members/" + $scope.currentUser.id
## Form action on the above URL
$scope.method = 'patch'
## Current user's profile
$scope.user = Member.get {id: $scope.currentUser.id}
## Angular-Bootstrap datepicker configuration for birthday
$scope.datePicker =
format: 'dd/MM/yyyy'
opened: false # default: datePicker is not shown
options:
startingDay: 1 # France: the week starts on monday
##
# Callback to diplay the datepicker as a dropdown when clicking on the input field
# @param $event {Object} jQuery event object
##
$scope.openDatePicker = ($event) ->
$event.preventDefault()
$event.stopPropagation()
$scope.datePicker.opened = true
##
# For use with ngUpload (https://github.com/twilson63/ngUpload).
# Intended to be the callback when the upload is done: any raised error will be stacked in the
# $scope.alerts array. If everything goes fine, the user's profile is updated and the user is
# redirected to the home page
# @param content {Object} JSON - The upload's result
##
$scope.submited = (content) ->
if !content.id?
$scope.alerts = []
angular.forEach content, (v, k)->
angular.forEach v, (err)->
$scope.alerts.push
msg: k+': '+err,
type: 'danger'
else
$scope.currentUser.profile.user_avatar = content.profile.user_avatar
Auth._currentUser.profile.user_avatar = content.profile.user_avatar
$scope.currentUser.name = content.name
Auth._currentUser.name = content.name
$scope.currentUser = content
Auth._currentUser = content
$state.go('app.public.home')
##
# For use with 'ng-class', returns the CSS class name for the uploads previews.
# The preview may show a placeholder or the content of the file depending on the upload state.
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
##
$scope.fileinputClass = (v)->
if v
'fileinput-exists'
else
'fileinput-new'
]
##
# Controller used on the public user's profile page (seeing another user's profile)
##
Application.Controllers.controller "showProfileController", ["$scope", "$stateParams", 'Member', ($scope, $stateParams, Member) ->
## Selected user's profile (id from the current URL)
$scope.user = Member.get {id: $stateParams.id}
]

View File

@ -0,0 +1,87 @@
'use strict'
##
# Controller used in notifications page
# inherits $scope.$parent.notifications (unread notifications) from ApplicationController
##
Application.Controllers.controller "notificationsController", ["$scope", 'Notification', ($scope, Notification) ->
### PRIVATE STATIC CONSTANTS ###
# Number of notifications added to the page when the user clicks on 'load next notifications'
NOTIFICATIONS_PER_PAGE = 15
### PUBLIC SCOPE ###
## Array containg the archived notifications (already read)
$scope.notificationsRead = []
## By default, the pagination mode is activated to limit the page size
$scope.paginateActive = true
## The currently displayed page number
$scope.page = 1
##
# Mark the provided notification as read, updating its status on the server and moving it
# to the already read notifications list.
# @param notification {{id:number}} the notification to mark as read
# @param e {Object} jQuery event object
##
$scope.markAsRead = (notification, e) ->
e.preventDefault()
Notification.update {id: notification.id},
id: notification.id
is_read: true
, ->
index = $scope.$parent.notifications.indexOf(notification)
$scope.$parent.notifications.splice(index,1)
$scope.notificationsRead.push notification
##
# Mark every unread notifications as read and move them for the unread list to to read array.
##
$scope.markAllAsRead = ->
Notification.update {}
, -> # success
angular.forEach $scope.$parent.notifications, (n)->
$scope.notificationsRead.push n
$scope.$parent.notifications.splice(0, $scope.$parent.notifications.length)
##
# Request the server to retrieve the next undisplayed notifications and add them
# to the archived notifications list.
##
$scope.addMoreNotificationsReaded = ->
Notification.query {is_read: true, page: $scope.page}, (notifications) ->
$scope.notificationsRead = $scope.notificationsRead.concat notifications
$scope.paginateActive = false if notifications.length < NOTIFICATIONS_PER_PAGE
$scope.page += 1
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
$scope.addMoreNotificationsReaded()
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]

View File

@ -0,0 +1,358 @@
'use strict'
### COMMON CODE ###
##
# Provides a set of common properties and methods to the $scope parameter. They are used
# in the various projects' admin controllers.
#
# Provides :
# - $scope.machines = [{Machine}]
# - $scope.components = [{Component}]
# - $scope.themes = [{Theme}]
# - $scope.licences = [{Licence}]
# - $scope.submited(content)
# - $scope.cancel()
# - $scope.addFile()
# - $scope.deleteFile(file)
# - $scope.addStep()
# - $scope.deleteStep(step)
#
# Requires :
# - $scope.project.project_caos_attributes = []
# - $scope.project.project_steps_attributes = []
# - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ]
##
class ProjectsController
constructor: ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document)->
## Retrieve the list of machines from the server
Machine.query().$promise.then (data)->
$scope.machines = data.map (d) ->
id: d.id
name: d.name
## Retrieve the list of components from the server
Component.query().$promise.then (data)->
$scope.components = data.map (d) ->
id: d.id
name: d.name
## Retrieve the list of themes from the server
Theme.query().$promise.then (data)->
$scope.themes = data.map (d) ->
id: d.id
name: d.name
## Retrieve the list of licences from the server
Licence.query().$promise.then (data)->
$scope.licences = data.map (d) ->
id: d.id
name: d.name
##
# For use with ngUpload (https://github.com/twilson63/ngUpload).
# Intended to be the callback when an upload is done: any raised error will be stacked in the
# $scope.alerts array. If everything goes fine, the user is redirected to the project page.
# @param content {Object} JSON - The upload's result
##
$scope.submited = (content) ->
if !content.id?
$scope.alerts = []
angular.forEach content, (v, k)->
angular.forEach v, (err)->
$scope.alerts.push
msg: k+': '+err
type: 'danger'
# using https://github.com/oblador/angular-scroll
$('section[ui-view=main]').scrollTop(0, 200)
return
else
$state.go('app.public.projects_show', {id: content.id})
##
# Changes the user's view to the projects list page
##
$scope.cancel = ->
$state.go('app.public.projects_list')
##
# For use with 'ng-class', returns the CSS class name for the uploads previews.
# The preview may show a placeholder or the content of the file depending on the upload state.
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
##
$scope.fileinputClass = (v)->
if v
'fileinput-exists'
else
'fileinput-new'
##
# This will create a single new empty entry into the project's CAO attachements list.
##
$scope.addFile = ->
$scope.project.project_caos_attributes.push {}
##
# This will remove the given file from the project's CAO attachements list. If the file was previously uploaded
# to the server, it will be marked for deletion on the server. Otherwise, it will be simply truncated from
# the CAO attachements array.
# @param file {Object} the file to delete
##
$scope.deleteFile = (file) ->
index = $scope.project.project_caos_attributes.indexOf(file)
if file.id?
file._destroy = true
else
$scope.project.project_caos_attributes.splice(index, 1)
##
# This will create a single new empty entry into the project's steps list.
##
$scope.addStep = ->
$scope.project.project_steps_attributes.push {}
##
# This will remove the given stip from the project's steps list. If the step was previously saved
# on the server, it will be marked for deletion for the next saving. Otherwise, it will be simply truncated from
# the steps array.
# @param file {Object} the file to delete
##
$scope.deleteStep = (step) ->
index = $scope.project.project_steps_attributes.indexOf(step)
if step.id?
step._destroy = true
else
$scope.project.project_steps_attributes.splice(index, 1)
##
# Controller used on projects listing page
##
Application.Controllers.controller "projectsController", ["$scope", "$state", 'Project', 'Machine', 'Theme', 'Component', ($scope, $state, Project, Machine, Theme, Component) ->
### PRIVATE STATIC CONSTANTS ###
# Number of notifications added to the page when the user clicks on 'load next notifications'
PROJECTS_PER_PAGE = 12
### PUBLIC SCOPE ###
## list of projects to display
$scope.projects = []
## list of machines / used for filtering
$scope.machines = []
## list of themes / used for filtering
$scope.themes = Theme.query()
## list of components / used for filtering
$scope.components = Component.query()
## By default, the pagination mode is activated to limit the page size
$scope.paginateActive = true
## The currently displayed page number
$scope.page = 1
##
# Request the server to retrieve the next undisplayed projects and add them
# to the local projects list.
##
$scope.loadMoreProjects = ->
Project.query {page: $scope.page}, (projects) ->
$scope.projects = $scope.projects.concat projects
$scope.paginateActive = false if projects.length < PROJECTS_PER_PAGE
$scope.page += 1
##
# Callback to switch the user's view to the detailled project page
# @param project {{slug:string}} The project to display
##
$scope.showProject = (project) ->
$state.go('app.public.projects_show', {id: project.slug})
##
# Callback to delete the provided project. Then, the projects list page is refreshed (admins only)
##
$scope.delete = (project) ->
# check the permissions
if $scope.currentUser.role isnt 'admin'
console.error 'Unauthorized operation'
else
# delete the project then refresh the projects list
project.$delete ->
$state.go('app.public.projects_list', {}, {reload: true})
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
Machine.query().$promise.then (data)->
$scope.machines = data.map (d) ->
id: d.id
name: d.name
$scope.loadMoreProjects()
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]
##
# Controller used in the project creation page
##
Application.Controllers.controller "newProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/projects/"
## Form action on the above URL
$scope.method = 'post'
## Button litteral text value
$scope.submitName = 'Enregistrer comme brouillon'
## Default project parameters
$scope.project =
project_steps_attributes: []
project_caos_attributes: []
## Other members list (project collaborators)
Member.query().$promise.then (data)->
$scope.members = data.filter (m) ->
m.id != $scope.currentUser.id
.map (d) ->
id: d.id
name: d.name
## Using the ProjectsController
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document)
]
##
# Controller used in the project edition page
##
Application.Controllers.controller "editProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF) ->
CSRF.setMetaTags()
## API URL where the form will be posted
$scope.actionUrl = "/api/projects/" + $stateParams.id
## Form action on the above URL
$scope.method = 'put'
## Button litteral text value
$scope.submitName = 'Enregistrer'
## Retrieve the project's details, if an error occured, redirect the user to the projects list page
$scope.project = Project.get {id: $stateParams.id}
, -> # success
return
, -> # failed
$state.go('app.public.projects_list')
## Other members list (project collaborators)
Member.query().$promise.then (data)->
$scope.members = data.filter (m) ->
m.id != $scope.project.author_id
.map (d) ->
id: d.id
name: d.name
## Using the ProjectsController
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document)
]
##
# Controller used in the public project's details page
##
Application.Controllers.controller "showProjectController", ["$scope", "$state", "$stateParams", "Project", '$location', ($scope, $state, $stateParams, Project, $location) ->
### PUBLIC SCOPE ###
## Will be set to true once the project details are loaded. Used to load the Disqus plugin at the right moment
$scope.contentLoaded = false
## Store the project's details
$scope.project = {}
##
# Test if the provided user has the edition rights on the current project
# @param [user] {{id:number}} (optional) the user to check rights
# @returns boolean
##
$scope.projectEditableBy = (user) ->
return false if not user?
return true if $scope.project.author_id == user.id
canEdit = false
angular.forEach $scope.project.project_users, (u)->
canEdit = true if u.id == user.id and u.is_valid
return canEdit
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
## Retrieve the project content
$scope.project = Project.get {id: $stateParams.id}
, -> # success
$scope.contentLoaded = true
$scope.project_url = $location.absUrl()
return
, -> # failed, redirect the user to the projects listing
$state.go('app.public.projects_list')
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]

View File

@ -0,0 +1,37 @@
'use strict';
Application.Directives.directive('bsJasnyFileinput', [function(){
return {
require : ['ngModel'],
link : function($scope, elm, attrs, requiredCtrls){
var ngModelCtrl = requiredCtrls[0];
var fileinput = elm.parents('[data-provides=fileinput]');
var filetypeRegex = attrs.bsJasnyFileinput;
fileinput.on('clear.bs.fileinput', function(e){
if(ngModelCtrl){
ngModelCtrl.$setViewValue(null);
ngModelCtrl.$setPristine();
$scope.$apply();
}
});
fileinput.on('change.bs.fileinput', function(e, files){
if(ngModelCtrl){
if(files){
ngModelCtrl.$setViewValue(files.result);
} else {
ngModelCtrl.$setPristine();
}
// TODO: ne marche pas pour filetype
if (filetypeRegex) {
if(files && typeof files.type !== "undefined" && files.type.match(new RegExp(filetypeRegex)))
ngModelCtrl.$setValidity('filetype', true);
else
ngModelCtrl.$setValidity('filetype', false);
}
}
$scope.$apply();
});
}
}
}]);

View File

@ -0,0 +1,68 @@
'use strict'
Application.Directives.directive 'fileread', [ ->
{
scope:
fileread: "="
link: (scope, element, attributes) ->
element.bind "change", (changeEvent) ->
scope.$apply ->
scope.fileread = changeEvent.target.files[0]
}
]
# This `bsHolder` angular directive is a workaround for
# an incompatability between angular and the holder.js
# image placeholder library.
#
# To use, simply define `bs-holder` on any element
Application.Directives.directive 'bsHolder', [ ->
{
link: (scope, element, attrs) ->
Holder.addTheme("icon", { background: "white", foreground: "#e9e9e9", size: 80, font: "FontAwesome"})
.addTheme("avatar", { background: "#eeeeee", foreground: "#555555", size: 16, font: "FontAwesome"})
.run(element[0])
return
}
]
Application.Directives.directive 'match', [ ->
{
require: 'ngModel'
restrict: 'A'
scope:
match: '='
link: (scope, elem, attrs, ctrl) ->
scope.$watch ->
(ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match == ctrl.$modelValue
, (currentValue) ->
ctrl.$setValidity('match', currentValue)
}
]
Application.Directives.directive 'publishProject', [ ->
{
restrict: 'A'
link: (scope, elem, attrs, ctrl) ->
elem.bind 'click', ($event)->
if ($event)
$event.preventDefault()
$event.stopPropagation()
return if (elem.attr('disabled'))
input = angular.element('<input name="project[state]" type="hidden" value="published">')
form = angular.element('form')
form.append(input)
form.triggerHandler('submit')
form[0].submit()
}
]
Application.Directives.directive "disableAnimation", ($animate) ->
restrict: "A"
link: (scope, elem, attrs) ->
attrs.$observe "disableAnimation", (value) ->
$animate.enabled not value, elem

View File

@ -0,0 +1,10 @@
Application.Directives.directive 'fabUserAvatar', [ ->
{
restrict: 'E'
scope:
userAvatar: "=ngModel"
avatarClass: '@'
templateUrl: '<%= asset_path "shared/_user_avatar.html" %>'
}
]

View File

@ -0,0 +1,119 @@
'use strict'
# filter for projects and trainings
Application.Controllers.filter "machineFilter", [ ->
(elements, selectedMachine) ->
if !angular.isUndefined(elements) and !angular.isUndefined(selectedMachine) and elements? and selectedMachine?
filteredElements = []
angular.forEach elements, (element)->
if element.machine_ids.indexOf(selectedMachine) != -1
filteredElements.push(element)
filteredElements
else
elements
]
Application.Controllers.filter "projectMemberFilter", [ "Auth", (Auth)->
(projects, selectedMember) ->
if !angular.isUndefined(projects) and angular.isDefined(selectedMember) and projects? and selectedMember? and selectedMember != ""
filteredProject = []
# Mes projets
if selectedMember == '0'
angular.forEach projects, (project)->
if project.author_id == Auth._currentUser.id
filteredProject.push(project)
# les projets auxquels je collabore
else
angular.forEach projects, (project)->
if project.user_ids.indexOf(Auth._currentUser.id) != -1
filteredProject.push(project)
filteredProject
else
projects
]
Application.Controllers.filter "themeFilter", [ ->
(projects, selectedTheme) ->
if !angular.isUndefined(projects) and !angular.isUndefined(selectedTheme) and projects? and selectedTheme?
filteredProjects = []
angular.forEach projects, (project)->
if project.theme_ids.indexOf(selectedTheme) != -1
filteredProjects.push(project)
filteredProjects
else
projects
]
Application.Controllers.filter "componentFilter", [ ->
(projects, selectedComponent) ->
if !angular.isUndefined(projects) and !angular.isUndefined(selectedComponent) and projects? and selectedComponent?
filteredProjects = []
angular.forEach projects, (project)->
if project.component_ids.indexOf(selectedComponent) != -1
filteredProjects.push(project)
filteredProjects
else
projects
]
Application.Controllers.filter "projectsByAuthor", [ ->
(projects, authorId) ->
if !angular.isUndefined(projects) and angular.isDefined(authorId) and projects? and authorId? and authorId != ""
filteredProject = []
angular.forEach projects, (project)->
if project.author_id == authorId
filteredProject.push(project)
filteredProject
else
projects
]
Application.Controllers.filter "projectsCollabored", [ ->
(projects, memberId) ->
if !angular.isUndefined(projects) and angular.isDefined(memberId) and projects? and memberId? and memberId != ""
filteredProject = []
angular.forEach projects, (project)->
if project.user_ids.indexOf(memberId) != -1
filteredProject.push(project)
filteredProject
else
projects
]
# depend on humanize.js lib in /vendor
Application.Controllers.filter "humanize", [ ->
(element, param) ->
Humanize.truncate(element, param, null)
]
Application.Controllers.filter "breakFilter", [ ->
(text) ->
if text != undefined
text.replace(/\n/g, '<br />')
]
Application.Controllers.filter "toTrusted", [ "$sce", ($sce) ->
(text) ->
$sce.trustAsHtml text
]
Application.Controllers.filter "eventsFilter", [ ->
(elements, selectedScope) ->
if !angular.isUndefined(elements) and !angular.isUndefined(selectedScope) and elements? and selectedScope? and selectedScope != ""
filteredElements = []
angular.forEach elements, (element)->
element.start_at = element.availability.start_at if angular.isUndefined(element.start_at)
switch selectedScope
when "future"
if new Date(element.start_at) > new Date
filteredElements.push(element)
when "passed"
if new Date(element.start_at) <= new Date
filteredElements.push(element)
else
return []
filteredElements
else
elements
]

View File

@ -0,0 +1,222 @@
angular.module('application.router', ['ui.router']).
config ['$stateProvider', '$urlRouterProvider', '$locationProvider', ($stateProvider, $urlRouterProvider, $locationProvider) ->
$locationProvider.hashPrefix('!')
$urlRouterProvider.otherwise("/")
# abstract root parents states
# these states controls the access rights to the various routes inherited from them
$stateProvider
.state 'app',
abstract: true
views:
'header': { templateUrl: '<%= asset_path "shared/header.html" %>' }
'leftnav':
templateUrl: '<%= asset_path "shared/leftnav.html" %>'
controller: 'mainNavController'
'main':
templateUrl: '<%= asset_path "home.html" %>'
controller: 'homeController'
.state 'app.public',
abstract: true
.state 'app.logged',
abstract: true
data:
authorizedRoles: ['member', 'admin']
resolve:
currentUser: ['Auth', (Auth)->
Auth.currentUser()
]
onEnter: ["currentUser", "$rootScope", (currentUser, $rootScope)->
$rootScope.currentUser = currentUser
]
.state 'app.admin',
abstract: true
data:
authorizedRoles: ['admin']
resolve:
currentUser: ['Auth', (Auth)->
Auth.currentUser()
]
onEnter: ["currentUser", "$rootScope", (currentUser, $rootScope)->
$rootScope.currentUser = currentUser
]
# main pages
.state 'app.public.about',
url: '/about'
views:
'content@': { templateUrl: '<%= asset_path "shared/about.html" %>' }
.state 'app.public.home',
url: '/?reset_password_token'
views:
'main':
templateUrl: '<%= asset_path "home.html" %>'
controller: 'homeController'
# dashboard
.state 'app.logged.dashboard_profile',
url: '/dashboard/profile'
views:
'main@':
templateUrl: '<%= asset_path "dashboard/profile.html" %>'
controller: 'editProfileController'
.state 'app.logged.dashboard_projects',
url: '/dashboard/projects'
views:
'main@':
templateUrl: '<%= asset_path "dashboard/projects.html" %>'
controller: 'dashboardProjectsController'
# members
.state 'app.logged.members_show',
url: '/members/:id'
views:
'main@':
templateUrl: '<%= asset_path "members/show.html" %>'
controller: 'showProfileController'
.state 'app.logged.members',
url: '/members'
views:
'main@':
templateUrl: '<%= asset_path "members/index.html" %>'
controller: 'membersController'
# projects
.state 'app.public.projects_list',
url: '/projects'
views:
'main@':
templateUrl: '<%= asset_path "projects/index.html" %>'
controller: 'projectsController'
.state 'app.public.projects_show',
url: '/projects/:id'
views:
'main@':
templateUrl: '<%= asset_path "projects/show.html" %>'
controller: 'showProjectController'
.state 'app.logged.projects_new',
url: '/projects/new'
views:
'main@':
templateUrl: '<%= asset_path "projects/new.html" %>'
controller: 'newProjectController'
.state 'app.logged.projects_edit',
url: '/projects/:id/edit'
views:
'main@':
templateUrl: '<%= asset_path "projects/edit.html" %>'
controller: 'editProjectController'
# machines
.state 'app.public.machines_list',
url: '/machines'
views:
'main@':
templateUrl: '<%= asset_path "machines/index.html" %>'
controller: 'machinesController'
.state 'app.public.machines_show',
url: '/machines/:id'
views:
'main@':
templateUrl: '<%= asset_path "machines/show.html" %>'
controller: 'showMachineController'
.state 'app.admin.machines_new',
url: '/machines/new'
views:
'main@':
templateUrl: '<%= asset_path "machines/new.html" %>'
controller: 'newMachineController'
.state 'app.admin.machines_edit',
url: '/machines/:id/edit'
views:
'main@':
templateUrl: '<%= asset_path "machines/edit.html" %>'
controller: 'editMachineController'
# notifications
.state 'app.logged.notifications',
url: '/notifications'
views:
'main@':
templateUrl: '<%= asset_path "notifications/index.html" %>'
controller: 'notificationsController'
# events
.state 'app.public.events_list',
url: '/events'
views:
'main@':
templateUrl: '<%= asset_path "events/index.html" %>'
controller: 'eventsController'
.state 'app.public.events_show',
url: '/events/:id'
views:
'main@':
templateUrl: '<%= asset_path "events/show.html" %>'
controller: 'showEventController'
# --- namespace /admin/... ---
# project's elements
.state 'app.admin.project_elements',
url: '/admin/project_elements'
views:
'main@':
templateUrl: '<%= asset_path "admin/project_elements/index.html" %>'
controller: 'projectElementsController'
# events
.state 'app.admin.events',
url: '/admin/events'
views:
'main@':
templateUrl: '<%= asset_path "admin/events/index.html" %>'
controller: 'adminEventsController'
.state 'app.admin.events_new',
url: '/admin/events/new'
views:
'main@':
templateUrl: '<%= asset_path "events/new.html" %>'
controller: 'newEventController'
.state 'app.admin.events_edit',
url: '/admin/events/:id/edit'
views:
'main@':
templateUrl: '<%= asset_path "events/edit.html" %>'
controller: 'editEventController'
# members
.state 'app.admin.members',
url: '/admin/members'
views:
'main@':
templateUrl: '<%= asset_path "admin/members/index.html" %>'
controller: 'membersController'
.state 'app.admin.members_new',
url: '/admin/members/new'
views:
'main@':
templateUrl: '<%= asset_path "admin/members/new.html" %>'
controller: 'newMemberController'
.state 'app.admin.members_edit',
url: '/admin/members/:id/edit'
views:
'main@':
templateUrl: '<%= asset_path "admin/members/edit.html" %>'
controller: 'editMemberController'
]

View File

@ -0,0 +1,12 @@
'use strict'
Application.Services.factory 'AuthService', ["Session", (Session) ->
isAuthenticated: ->
Session.currentUser? and Session.currentUser.id?
isAuthorized: (authorizedRoles) ->
if !angular.isArray(authorizedRoles)
authorizedRoles = [authorizedRoles]
@isAuthenticated() and authorizedRoles.indexOf(Session.currentUser.role) != -1
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'Category', ["$resource", ($resource)->
$resource "/api/categories/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'Component', ["$resource", ($resource)->
$resource "/api/components/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,14 @@
'use strict'
Application.Services.service 'CSRF', ['$cookies',
($cookies)->
return {
setMetaTags: ->
if angular.element('meta[name="csrf-param"]').length == 0
angular.element('head').append("<meta name=\"csrf-param\" content=\"authenticity_token\">")
angular.element('head').append("<meta name=\"csrf-token\" content=\"#{$cookies['XSRF-TOKEN']}\">")
else
angular.element('meta[name="csrf-token"]').replaceWith("<meta name=\"csrf-token\" content=\"#{$cookies['XSRF-TOKEN']}\">")
return
}
]

View File

@ -0,0 +1,27 @@
'use strict'
Application.Services.factory 'dialogs', ["$modal", ($modal) ->
confirm: (options, success, error)->
defaultOpts =
templateUrl: '<%= asset_path "shared/confirm_modal.html" %>'
size: 'sm'
resolve:
object: ->
title: 'Titre de confirmation'
msg: 'Message de confiramtion'
controller: ['$scope', '$modalInstance', '$state', 'object', ($scope, $modalInstance, $state, object) ->
$scope.object = object
$scope.ok = ->
$modalInstance.close()
$scope.cancel = ->
$modalInstance.dismiss('cancel')
]
angular.extend(defaultOpts, options) if angular.isObject options
$modal.open defaultOpts
.result['finally'](null).then ->
if angular.isFunction(success)
success()
, ->
if angular.isFunction(error)
error()
]

View File

@ -0,0 +1,13 @@
'use strict'
Application.Services.factory 'Event', ["$resource", ($resource)->
$resource "/api/events/:id",
{id: "@id"},
update:
method: 'PUT'
upcoming:
method: 'GET'
url: '/api/events/upcoming/:limit'
params: {limit: "@limit"}
isArray: true
]

View File

@ -0,0 +1,6 @@
'use strict'
Application.Services.factory 'Group', ["$resource", ($resource)->
$resource "/api/groups/:id",
{id: "@id"}
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'Licence', ["$resource", ($resource)->
$resource "/api/licences/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'Machine', ["$resource", ($resource)->
$resource "/api/machines/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,11 @@
'use strict'
Application.Services.factory 'Member', ["$resource", ($resource)->
$resource "/api/members/:id",
{id: "@id"},
lastSubscribed:
method: 'GET'
url: '/api/last_subscribed/:limit'
params: {limit: "@limit"}
isArray: true
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'Notification', ["$resource", ($resource)->
$resource "/api/notifications/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,10 @@
'use strict'
Application.Services.factory 'Project', ["$resource", ($resource)->
$resource "/api/projects/:id",
{id: "@id"},
lastPublished:
method: 'GET'
url: '/api/projects/last_published'
isArray: true
]

View File

@ -0,0 +1,11 @@
'use strict'
Application.Services.service 'Session', [ ->
@create = (user)->
@currentUser = user
@destroy = ->
@currentUser = null
return @
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'Theme', ["$resource", ($resource)->
$resource "/api/themes/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,5 @@
'use strict'
Application.Services.factory 'Twitter', ["$resource", ($resource)->
$resource "/api/feeds/twitter_timelines"
]

View File

@ -0,0 +1,169 @@
// reset
html {
overflow-x: hidden;
}
body {
-webkit-font-smoothing: antialiased;
}
.h1, .h2, .h3, .h4, .h5, .h6{
margin: 0;
}
h1, .page-title {
line-height: rem-calc(24);
text-transform: uppercase;
font-weight: 900;
}
h2 {
color: $red;
line-height: rem-calc(24);
font-weight: 900;
}
h3 { font-weight: 600; }
h4 { font-weight: bold; }
h5 {
display: inline-block;
position: relative;
line-height: rem-calc(18);
color: $red;
font-size: rem-calc(16);
&:after {
position: absolute;
top: 18px;
left: 0px;
content: '';
width: 35%;
height: 1px;
background-color: $red;
}
}
// Links
// -------------------------
a {
color: $link-color;
text-decoration: none;
}
a:hover,
a:focus {
color: $link-hover-color;
text-decoration: none;
}
label{ font-weight: 600; }
small, .small{font-size: $font-size-sm;}
pre { padding: 0; }
p {
font-size: rem-calc($font-size-base);
line-height: rem-calc(24);
&.intro, .intro {
font-family: $font-proxima-condensed;
font-size: rem-calc(16);
line-height: rem-calc(24);
margin: 1.038em 0px 30px 0px;
font-weight: 600;
}
}
dt {
color: $black-light;
}
dd {
color: $black-light;
margin-bottom: 20px;
}
//
// -------------------------
.col-0{clear:left;}
.row.no-gutter{
margin-left: 0;
margin-right: 0;
}
.no-gutter > [class*="col"]{
padding: 0;
}
/* centered columns styles */
.row-centered {
text-align:center;
}
.col-centered {
display:inline-block;
float:none;
/* reset the text-align */
text-align:left;
/* inline-block space fix */
margin-right:-4px;
}
::-webkit-input-placeholder { /* WebKit browsers */
color: black;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: black;
opacity: 1;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
color: black;
opacity: 1;
}
:-ms-input-placeholder { /* Internet Explorer 10+ */
color: black;
}
// .ng-hide-remove-active {
// -webkit-transition:0.5s linear all;
// transition:0.5s linear all;
// }
[ui-view].ng-enter, [ui-view].ng-leave {
position: absolute;
left: 0;
right: 0;
@include transition(all .7s ease-in-out);
}
[ui-view].ng-enter {
opacity: 0;
// -webkit-transform:scale3d(0.5, 0.5, 0.5);
// -moz-transform:scale3d(0.5, 0.5, 0.5);
// transform:scale3d(0.5, 0.5, 0.5);
}
[ui-view].ng-enter-active {
opacity: 1;
// -webkit-transform:scale3d(1, 1, 1);
// -moz-transform:scale3d(1, 1, 1);
// transform:scale3d(1, 1, 1);
}
[ui-view].ng-leave {
opacity: 1;
/*padding-left: 0px;*/
// -webkit-transform:translate3d(0, 0, 0);
// -moz-transform:translate3d(0, 0, 0);
// transform:translate3d(0, 0, 0);
}
[ui-view].ng-leave-active {
opacity: 0;
/*padding-left: 100px;*/
// -webkit-transform:translate3d(100px, 0, 0);
// -moz-transform:translate3d(100px, 0, 0);
// transform:translate3d(100px, 0, 0);
}
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}

View File

@ -0,0 +1,44 @@
.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open > .btn-default.dropdown-toggle {
background-color: #f2f2f2;
}
.btn{
> i{
&.pull-left,
&.pull-right{
line-height: 1.428571429;
}
}
}
.btn-warning-full {
outline: 0;
text-transform: uppercase;
border: 3px solid $yellow;
background-color: $yellow;
&:hover {
background-color: white;
}
}
.btn-block {
padding-left: 12px;
padding-right: 12px;
}
.btn-group-vertical > .btn:first-child:not(:last-child){
border-top-right-radius: $border-radius-base;
}
.btn-group-vertical > .btn:last-child:not(:first-child){
border-bottom-left-radius: $border-radius-base;
}
.btn-inactive{
-webkit-box-shadow: none !important;
box-shadow: none !important;
}

View File

@ -0,0 +1,36 @@
.bg-light { background-color: $brand-light; }
.bg-red { background-color: $red; color: white; }
.bg-red-dark { background-color: $red-dark; }
.bg-yellow { background-color: $yellow !important; }
.bg-machine { background-color: $beige; }
.bg-formation { background-color: $violet; }
.bg-atelier { background-color: $blue; }
.bg-stage { background-color: $violet; }
.bg-success { background-color: $brand-success; }
.bg-info { background-color: $brand-info; }
.bg-black-light { background-color: #424242 !important; }
.bg-white {
background-color: #fff;
color: $text-color;
a {
color: $link-color;
&:hover{
color: darken($link-color, 10%);
}
}
.text-muted{color: $text-muted !important;}
}
.bg-white-only{background-color:#fff;}
.bg-empty{background-color: transparent;}
.text-black{ color: #000 !important; };
.text-black-light { color: #424242 !important; }
.text-gray { color: #5a5a5a !important; }
.text-white { color: #fff !important; }
.text-yellow { color: $yellow !important; }
.text-blue { color: $blue; }
.text-muted { color: $text-muted; }
.text-danger, .red { color: $red !important; }

View File

@ -0,0 +1,438 @@
.widget {
h1, h2, h3 {
margin: 0;
line-height: rem-calc(18);
font-size: rem-calc(14);
font-weight: 600;
color: black;
}
h1 {
font-size: rem-calc(16); text-transform: uppercase;
}
h2 { font-weight: bold; }
h3 { color: $red; }
h4 {
font-size: rem-calc(12);
margin: 8px 0;
}
p { font-size: rem-calc(14); margin-top: 15px; }
a {
color: $black-light;
&:hover { color: $red; }
}
.fa {
// color: $red;
}
.widget-content {
font-size: rem-calc(14);
}
.list-group-item {
&.no-b {
padding: 10px 15px;
}
}
}
//modal
.modal-dialog { top: 90px; }
.modal-header {
.modal-logo {
position: absolute;
top: -70px;
left: 0;
right: 0;
margin: 0 auto;
}
h1 {
margin: 25px 0 20px 0;
font-weight: bold;
text-transform: uppercase;
text-align: center;
color: $red;
}
}
.modal-backdrop {
height: 100%;
}
.box-thumb {
opacity: 0.9;
&:hover { opacity: 1;}
&:hover .box-footer { opacity: 1; }
position: relative;
background-size: cover;
background-repeat: no-repeat;
border-radius: 8px;
margin-bottom: 30px;
cursor: pointer;
// todo
overflow: hidden;
height: 280px;
img {
opacity: 0.9;
}
.project-caption {
text-shadow: rgba(29, 29, 29, 0.5) 0 -1px, rgba(29, 29, 29, 0.5) -1px 0,
rgba(29, 29, 29, 0.5) 1px 0, rgba(29, 29, 29, 0.5) 0 1px;
}
.box-content {
position: absolute;
top: 45px;
left: 0;
right: 0;
h1 {
padding: 0 20px;
color: white;
text-transform: uppercase;
font-size: rem-calc(24);
font-weight: 900;
line-height: rem-calc(30);
text-align: center;
}
}
.box-footer {
opacity: 0;
position: absolute;
bottom: 10px;
left: 0;
right: 0;
text-align: center;
}
}
.article {
max-width: $screen-md-min;
margin: 0 auto;
h2, h3, h4, h5 { margin: 1.8em 0 1em 0; }
h2, h3 {
color: $red;
font-size: rem-calc(18);
font-weight: bold;
}
h2 { text-transform: uppercase; }
h4 {
font-weight: 600;
font-size: rem-calc(16);
color: #686868;
}
.article-thumbnail {
max-height: 400px;
overflow: hidden;
}
}
.label-staging {
position: absolute;
top: 10px;
right: -13px;
}
.notification-open {
a {
position: relative;
.badge {
position: absolute;
top: 18px; right: 18px;
padding: 3px 6px 1px 6px;
}
}
.fa { color: black; font-size: rem-calc(24); }
}
.panel {
margin-bottom: 30px;
.panel-heading {
&.small {
padding: 15px 15px;
}
&.picture {
height: 250px;
background-size: cover;
background-position: center;
@include transition(opacity .5s ease);
cursor: pointer;
padding:0;
img { @include border-radius(6px 6px 0 0); }
&:hover {
opacity: 0.8;
}
}
.align {
position: relative;
top: -7px;
}
}
}
.pricing-panel {
border: 1px solid $border-color;
&:first-child {
border-right: none;
@include border-radius(3px 0 0 3px);
}
&:last-child {
@include border-radius(0 3px 3px 0);
}
.title {
margin: 10px 0;
font-size: rem-calc(16);
text-transform: uppercase;
}
.content {
padding: 15px 0;
background-color: $bg-gray;
.wrap {
width: 100px; height: 100px;
display: inline-block;
background: white;
@include border-radius(50%);
border: 3px solid $yellow;
}
.price {
position: relative;
top: 5px; left: 5px;
height: 84px; width: 84px;
background-color: black;
@include border-radius(50%);
.amount {
padding-top: 16px;
font-weight: bold;
font-size: rem-calc(26);
color: white;
}
.period {
position: relative;
top: -14px;
font-size: rem-calc(14);
color: white;
}
}
}
.cta-button {
margin: 20px 0;
.btn {
outline: 0;
font-weight: 600;
font-size: rem-calc(16);
background-color: white;
padding-left: 30px;
padding-right: 30px;
&:hover { background-color: $yellow; }
}
}
}
.well {
&.well-warning {
border-color: #ffdc4e;
background-color: #ffdc4e;
@include border-radius(3px);
padding: 5px 10px;
}
}
.read {
opacity: 0.7;
background: #F2F2F2;
}
.badge {
&.inverse {
color: black;
background-color: $yellow;
font-weight: 400;
}
}
.avatar{
position: relative;
display: block;
border-radius: 500px;
white-space: nowrap;
&.avatar-block {
white-space: inherit;
height: 76px;
.user-name {
display: block;
font-size: rem-calc(14);
line-height: rem-calc(14);
}
}
img{
border-radius: 500px;
width: 100%;
}
i{
position: absolute;
left: 0;
top: 0;
width: 10px;
height: 10px;
border-width: 2px;
border-style: solid;
border-radius: 100%;
&.md{
width: 12px;
height: 12px;
margin: 1px;
}
&.right{
left: auto;
right: 0;
}
&.bottom{
left: auto;
top: auto;
bottom: 0;
right: 0;
}
&.on{
background-color: $brand-success;
}
&.off{
background-color: $text-muted;
}
&.busy{
background-color: $brand-danger;
}
&.away{
background-color: $brand-warning;
}
}
}
.block-link {
cursor: pointer;
&:hover { background-color: $yellow; }
}
.form-control.form-control-ui-select .select2-choices .select2-search-choice {
font-size: 85% !important;
}
.about-link {
.label {
font-size: rem-calc(26);
padding: 0px 10px 0px 11px;
vertical-align: bottom;
&.label-icon { font-size: rem-calc(22); padding: 4px 9px 1px 10px; }
}
}
.about-fablab {
position: relative;
z-index: 101;
height: 93%;
background-color: white;
@include transition(.5s linear all);
opacity:1;
.about-picture {
padding: 70px 0;
height: 326px;
background: white asset-url("about-fablab.jpg") no-repeat;
background-size: cover;
margin-bottom: 30px;
}
.about-title {
margin: 0;
font-size: rem-calc(50);
line-height: rem-calc(48);
color: #fff;
font-weight: 900; //black
}
.about-title-aside {
margin-top: 0;
font-size: rem-calc(18);
}
p {
font-size: rem-calc(18);
line-height: rem-calc(30);
color: $black-light;
text-align: justify;
}
&.ng-hide {
opacity: 0;
@include transition(.5s linear all);
}
&.ng-hide-add,
&.ng-hide-remove {
display:block!important;
}
}
.event {
transition: all 0.07s linear;
}
.event:hover {
background-color: #cb1117;
color: white;
}
.event:hover * {
color: #eee;
border-color: #eee;
}
.box-h-m {
height: 150px;
max-height: 150px;
}
.half-w {
width: 50%;
}
.b-light-dark {
border-color: #d0d0d0;
}
.p-sm {
padding: 10px;
}
.crop-130 {
height: 130px;
width: 130px;
max-width: 130px;
max-height: 130px;
overflow: hidden;
vertical-align: bottom;
}
.crop-130 img {
height: 130px;
width: auto;
}
@media only screen and (max-width: 1280px) and (min-width: 770px) {
.crop-130 {
height: 90px;
width: 90px;
margin-top: 25px;
}
}

View File

@ -0,0 +1,63 @@
// This is the default html and body font-size for the base rem value.
$rem-base: 10px !default;
// IMPORT ONCE
// We use this to prevent styles from being loaded multiple times for compenents that rely on other components.
$modules: () !default;
@mixin exports($name) {
@if (index($modules, $name) == false) {
$modules: append($modules, $name);
@content;
}
}
//
// @functions
//
// STRIP UNIT
// It strips the unit of measure and returns it
@function strip-unit($num) {
@return $num / ($num * 0 + 1);
}
// CONVERT TO REM
@function convert-to-rem($value, $base-value: $rem-base) {
$value: strip-unit($value) / strip-unit($base-value) * 1rem;
@if ($value == 0rem) { $value: 0; } // Turn 0rem into 0
@return $value;
}
// REM CALC
// New Syntax, allows to optionally calculate on a different base value to counter compounding effect of rem's.
// Call with 1, 2, 3 or 4 parameters, 'px' is not required but supported:
//
// rem-calc(10 20 30px 40);
//
// Space delimited, if you want to delimit using comma's, wrap it in another pair of brackets
//
// rem-calc((10, 20, 30, 40px));
//
// Optionally call with a different base (eg: 8px) to calculate rem.
//
// rem-calc(16px 32px 48px, 8px);
//
// If you require to comma separate your list
//
// rem-calc((16px, 32px, 48), 8px);
@function rem-calc($values, $base-value: $rem-base) {
$max: length($values);
@if $max == 1 { @return convert-to-rem(nth($values, 1), $base-value); }
$remValues: ();
@for $i from 1 through $max {
$remValues: append($remValues, convert-to-rem(nth($values, $i), $base-value));
}
@return $remValues;
}

View File

@ -0,0 +1,344 @@
/*layout*/
.header,
.footer{
min-height: 50px;
padding: 0 15px;
> p{
margin-top: 15px;
display: inline-block;
}
> .btn,
> .btn-group,
> .btn-toolbar,
{
margin-top: 14px;
}
> .btn-lg{
margin-top: 0;
}
.nav-tabs {
border: none;
margin-left: -15px;
margin-right: -15px;
> li {
a{
border: none !important;
border-radius: 0;
padding-top: 15px;
padding-bottom: 15px;
line-height: 20px;
&:hover,
&:focus
{
background-color: transparent;
}
}
&.active a{
color: $text-color;
&,
&:hover{
background-color: $body-bg;
}
}
}
&.nav-white{
> li.active a{
&,
&:hover{
background-color: #fff;
}
}
}
}
&.navbar{
border-radius: 0;
border: none;
margin-bottom: 0;
padding:0;
position: relative !important;
z-index: 1000;
}
}
.header {
background-color: $header-bg;
@include box-shadow(0 1px 0px #cfcfcf);
}
.heading {
.heading-btn {
a {
width: 100%;
padding: 35px 40%;
display: inline-block;
cursor: pointer;
color: black;
&:hover {
background-color: $yellow;
}
i:before { content: "\f177"; }
}
}
.heading-title {
overflow: hidden;
height: 94px;
h1 {
margin: 0 0 0 15px;
padding: 36px 15px;
}
}
.heading-actions {
height: 94px;
text-align: center;
}
}
body.container{
padding: 0;
}
.aside-md{
width: 250px;
}
@media (min-width: 768px) {
body.container{
@include box-shadow(0 3px 60px rgba(0,0,0,0.3));
border-left: 1px solid darken($border-color, 10%);
border-right: 1px solid darken($border-color, 10%);
}
.app{
&,
body{
height:100%;
overflow: hidden;
}
.hbox{
&.stretch{
height:100%;
}
}
.vbox{
> section,
> footer{
position: absolute;
}
&.flex{
> section{
> section{
overflow: auto;
}
}
}
}
}
.hbox{
display: table;
table-layout: fixed;
border-spacing: 0;
width: 100%;
> aside,
> section{
display: table-cell;
vertical-align: top;
height: 100%;
float: none;
&.show,
&.hidden-sm {
display: table-cell !important;
}
}
}
.vbox{
display: table;
border-spacing:0;
position: relative;
height: 100%;
width: 100%;
> section,
> footer {
top: 0;
bottom: 0;
width: 100%;
}
> header{
~ section {
top: 50px;
}
&.header-md{
~ section {
top: $header-md-height+2;
}
}
}
> section{
&.w-f{
bottom: 50px;
}
}
> footer {
top: auto;
z-index: 100;
~ section {
bottom: 50px;
}
}
&.flex{
> header,
> section,
> footer {
position: inherit;
}
> section{
display: table-row;
height: 100%;
> section {
position: relative;
height: 100%;
-webkit-overflow-scrolling:touch;
.ie & {
display: table-cell;
}
> section {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
}
}
}
}
.aside-xs{
width: 60px;
}
.aside-sm{
width: 150px;
}
.aside{
width: 200px;
}
.aside-md{
width: 250px;
}
.aside-lg{
width: 300px;
}
.aside-xl{
width: 360px;
}
.aside-xxl{
width: 480px;
}
.header-md{
height: $header-md-height;
.navbar-form{
margin-top: floor( ($header-md-height - 30)/2 );
margin-bottom: floor( ($header-md-height - 30)/2 );
}
}
.scrollable{
-webkit-overflow-scrolling:touch;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background-color:rgba(50,50,50,0.25);
border: 2px solid transparent;
border-radius: 10px;
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover{
background-color:rgba(50,50,50,0.5);
}
::-webkit-scrollbar-track {
background-color:rgba(50,50,50,0.05);
}
}
.scrollable{
overflow-x: hidden;
overflow-y: auto;
}
.no-touch {
.scrollable.hover {
overflow-y: hidden;
&:hover
{
overflow: visible;
overflow-y: auto;
}
}
::-webkit-scrollbar-button {
width: 10px;
height: 6px;
background-color:rgba(50,50,50,0.05);
}
}
.slimScrollBar{
border-radius: 5px;
border: 2px solid transparent;
border-radius: 10px;
background-clip: padding-box !important;
}
@media print {
html, body, .hbox, .vbox{
height: auto;
}
.vbox{
> section,
> footer{
position: relative;
}
}
}
.datepicker-container {
position: relative;
z-index: 100;
}
.datepicker-dropdown {
border: 1px solid #c7c5c5;
border-radius: 0.3em;
padding-top: 0.5em;
padding-bottom: 0.5em;
position: absolute;
right:0;
left: 0;
background-color: #ffffff;
li {
padding-left: 0.8em;
padding-right: 0.8em;
margin-bottom: 1em;
line-height: 2.4em;
vertical-align: middle;
div.input-group {
float:right;
ul.dropdown-menu {
left: -5em !important;
}
}
}
}

View File

@ -0,0 +1,456 @@
/*primary nav*/
.navbar-header{
position: relative;
> .btn{
position: absolute;
font-size: 1.3em;
padding: 9px 16px;
line-height: 30px;
right: 0;
}
.navbar-brand + .btn{
right: 0;
top:0;
left: auto;
}
}
.navbar-brand{
float: none;
text-align: center;
font-size: 20px;
font-weight: 700;
height: auto;
line-height: 50px;
display: inline-block;
padding: 0 15px;
&:hover{
text-decoration: none;
}
img{
max-height: 20px;
margin-top: -4px;
vertical-align: middle;
display: initial;
}
}
.nav-primary {
li {
> a > i{
margin: floor(-($nav-primary-height - $line-height-computed)/2) -10px;
line-height: $nav-primary-height;
width: $nav-primary-height;
float: left;
margin-right: 5px;
text-align: center;
position: relative;
overflow: hidden;
&:before{
position: relative;
z-index: 2;
}
}
}
ul.nav {
> li {
> a{
padding: floor(($nav-primary-height - $line-height-computed)/2) 15px;
position: relative;
font-size: 14px;
@include transition(background-color .2s ease-in-out 0s);
.no-borders & {
border-width: 0 !important;
}
> .badge{
font-size: 11px;
padding: 2px 5px 2px 4px;
margin-top: 2px;
}
> .text-muted{
margin: 0 3px;
}
&.active{
.text{
display: none;
}
.text-active{
display: inline-block !important;
}
}
}
li a{
font-weight: normal;
text-transform: none;
}
&.active{
> ul{
display: block;
}
}
}
ul{
display: none;
}
}
.bg-black &{
> ul.nav-main{
> li{
&:hover,
&:focus,
&:active,
&.active{
> a{
background-color: $brand-success;
}
}
}
}
}
}
@media (min-width: 768px) {
.visible-nav-xs{display: none;}
.nav-xs {
width: $nav-xs-width;
> .nav-container { width: $nav-xs-width; }
.slimScrollDiv,
.slim-scroll {
overflow: visible !important;
}
.slimScrollBar,
.slimScrollRail {
display: none !important;
}
.scrollable{
overflow: visible;
}
.nav-primary{
> ul {
> li {
> a {
position: relative;
padding: 0 !important;
font-size: 11px;
text-align: center;
height: $nav-xs-height;
overflow-y: hidden;
border: none !important;
span {
display: table-cell;
vertical-align: middle;
height: $nav-xs-height;
width: $nav-xs-width;
&.pull-right{
display: none !important;
}
}
i{
width: auto;
float: none;
display: block;
font-size: 16px;
margin: 0;
line-height: $nav-xs-height;
border: none !important;
@include transition(margin-top 0.2s);
b{
left: 0 !important;
}
}
.badge{
position: absolute;
right: 10px;
top: 4px;
z-index: 3;
}
}
&:hover,
&:focus,
&:active,
&.active,
{
> a{
i{
margin-top: -$nav-xs-height;
}
}
}
}
ul{
display: none !important;
position: absolute;
left: 100%;
top: 0;
z-index: 1050;
width: 220px;
-webkit-box-shadow: 0 2px 6px rgba(0,0,0,0.1);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
}
li:hover,
li:focus,
li:active,
{
> ul{
display: block !important;
}
}
}
&.nav-xs-right{
.nav-primary > ul ul{
left: auto;
right: 100%;
}
}
> .vbox > .header,
> .vbox > .footer {
padding:0 floor(($nav-xs-width - 30px)/2);
}
.hidden-nav-xs{
display: none;
}
.visible-nav-xs{
display: inherit;
}
.text-center-nav-xs{
text-align: center;
}
.nav-user{
padding-left: 0;
padding-right: 0;
.avatar{
float: none !important;
margin-right: 0;
}
.dropdown{
> a{
display: block;
text-align: center;
}
}
}
.navbar-header{
float: none;
}
.navbar-brand{
display: block;
padding: 0;
img{
margin-right:0;
}
}
.navbar{
padding: 0
}
}
.navbar-brand{
.header-md &{
line-height: $header-md-height;
img{
max-height: 44px;
}
}
}
.navbar-nav{
> li{
> a{
.header-md &{
padding: floor(($header-md-height - $line-height-computed)/2 - 1);
}
}
}
}
}
@media (max-width: 767px) {
.navbar-fixed-top-xs{
position: fixed !important;
left: 0;
width: 100%;
z-index: 1100;
// + *{
// padding-top: 50px !important;
// }
}
.nav-bar-fixed-bottom{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 1100;
}
/* .off screen nav from left or right*/
html, body{
overflow-x: hidden;
min-height: 100%;
}
.nav-primary{
.dropdown-menu{
position: relative;
float:none;
left: 0;
margin-left: 0;
padding: 0;
a{
padding: 15px;
border-bottom: 1px solid #eee;
}
li:last-child{
a{
border-bottom: none;
}
}
}
}
.navbar-header{
text-align: center;
}
.nav-user{
margin: 0;
padding: 15px;
&.open{
display: inherit !important;
}
.dropdown-menu{
display: block;
position: static;
float: none;
}
.dropdown > a{
display: block;
text-align: center;
font-size: 18px;
padding-bottom: 10px;
}
.avatar{
width: 160px !important;
float: none !important;
display: block;
margin: 20px auto;
padding: 5px;
background-color: rgba(255, 255, 255, 0.1);
position: relative;
&:before {
content: "";
position: absolute;
left: 5px;
right: 5px;
bottom: 5px;
top: 5px;
border: 4px solid #fff;
border-radius: 500px;
}
}
}
.nav-off-screen {
display: block !important;
position: absolute;
left: 0;
top: 0px;
bottom: 0;
width: $off-screen-nav-width;
visibility: visible;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
.nav-primary{
display: block !important;
}
.navbar-fixed-top-xs{
width: $off-screen-nav-width;
}
&.push-right{
.navbar-fixed-top-xs{
left: 100% - $off-screen-nav-width;
}
}
&.push-right{
left: auto;
right: 0;
+ *{
@include translate3d(-$off-screen-nav-width, 0px, 0px);
}
}
+ *{
background-color: $body-bg;
@include transition-transform(0.2s ease-in-out);
@include transition-delay(0s);
@include translate3d(0px, 0px, 0px);
@include backface-visibility(hidden);
@include translate3d($off-screen-nav-width, 0px, 0px);
overflow: hidden;
position: absolute;
width: 100%;
top: 0px;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
.nav-off-screen-block {
display:block !important;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1950;
}
}
}
.navbar + section{
.nav-off-screen{
top: 50px;
+ *{
top: 50px;
}
}
}
.slimScrollDiv,
.slim-scroll {
overflow: visible !important;
height: auto !important;
}
.slimScrollBar,
.slimScrollRail {
display: none !important;
}
}
#nav {
// border-right: 1px solid $red-dark;
.nav {
background-color: $red;
> li {
> a {
padding: 13px 17px;
font-weight: 600;
color: white;
&:hover,
&:focus, &.active {
background-color: $red-light;
color: white;
}
&.active {
border-left: 3px solid #870003;
}
}
}
}
}
// overrides bootstrap
.nav-justified > li, .nav-tabs.nav-justified > li {
cursor: pointer;
}
.user-profile-nav > a {
display: inline-block !important;
padding: 11px 44px !important;
}

View File

@ -0,0 +1,297 @@
// Redactor
.redactor_editor, .redactor_editor:focus {
height: 200px;
}
// Growl
.growl {
top: 90px;
z-index: 1100;
}
// UI Select
.form-control {
&.form-control-ui-select {
height: auto;
.select2-choices {
border: none;
background: transparent;
.select2-search-choice {
@extend .label;
padding-left: .9em;
font-size: 100%;
font-weight: normal;
}
}
}
}
.editable-input label {
display: block;
}
/* ==========================================================================
Slider
========================================================================== */
.carousel-caption {
left: 0 !important;
right: 0 !important;
bottom: 0px;
padding-left: 30px;
padding-right: 30px;
text-align: left;
background: rgba(29, 29, 29, 0.5);
.title {
font-size: rem-calc(30);
line-height: rem-calc(24);
color: white;
font-weight: 800;
a {
color: white;
&:hover { color: $yellow; }
}
}
.description {
font-size: rem-calc(18);
line-height: rem-calc(18);
color: white;
}
}
.carousel-control {
cursor: pointer;
background: none;
@include border-radius($border-radius-base);
&:hover, &:focus {
color: $yellow;
}
.glyphicon-chevron-left {
font-family: 'fontawesome' !important;
&:before {
content: "\f053" !important;
}
}
.glyphicon-chevron-right {
font-family: 'fontawesome' !important;
&:before {
content: "\f054" !important;
}
}
}
.carousel-indicators {
display: none;
}
.banner { }
.slider {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
&.slider-min {
.slider-arrows {
bottom: 40%;
position: relative;
}
.slider-arrow {
margin-bottom: 0;
color: black;
border: none;
&.slider-arrow--left {
left: -16px;
}
&.slider-arrow--right {
right: -16px;
}
.icon {
font-size: 24px;
&.icon-angle-left {
width: 18px;
}
&:hover { color: $yellow; }
}
}
}
&.slider-actu {
.slider-arrows {
.slider-arrow--left { display: none; }
.slider-arrow--right {
bottom: 41px;
}
}
}
}
.slides {
width: 100%;
height: 100%;
/* Clear fix */
overflow: hidden;
*zoom: 1;
/**
* Prevent blinking issue
* Not tested. Experimental.
*/
-webkit-backface-visibility: hidden;
-webkit-transform-style: preserve-3d;
}
.slide {
height: 100%;
float: left;
clear: none;
&.content_article {
height: 620px;
-webkit-background-size: cover;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
color: #fff;
text-align: center;
position: relative;
}
.content_article-wrapper {
position: absolute;
bottom: 0;
top: 215px;
width: 45%;
margin: 0 auto;
left: 0;
right: 0;
.content_article-h {
font-weight: 900;
color: white;
font-size: 50px;
letter-spacing: 0.5px;
margin: 0;
text-transform: uppercase;
margin-bottom: 20px;
line-height: 50px;
}
.content_article-c {
color: $red;
font-size: 20px;
font-weight: 900;
margin-top: 0;
margin-bottom: 10px;
}
.content_article-p {
// font-family: $font-proxima-condensed;
font-size: 20px;
color: white;
margin: 0;
line-height: 30px;
}
.content_article-btns {
margin-top: 40px;
clear: both;
.header_nav-item-a {
color: #fff;
text-decoration: none;
}
.header_nav-item-a--btn {
padding: 12px 20px;
border-radius: 5px;
border: 2px solid $yellow;
background-color: transparent;
text-transform: uppercase;
font-family: "proxima-nova-condensed";
font-weight: 600;
&:hover {
background-color: $yellow;
color: black;
}
}
}
}
}
.slider-arrow {
position: absolute;
display: block;
margin-bottom: -20px;
padding: 20px;
color: white;
width: 58px; height: 58px;
border: 3px solid white;
border-radius: 50%;
&:hover { border-color: $yellow; color: $yellow; }
.icon {
text-decoration: none;
display: inline-block;
font-size: 40px;
position: absolute;
top: 5px;
left: 0;
right: 0;
margin: 0 auto;
width: 12px;
&.icon-angle-left {
width: 18px;
}
&:hover { color: $yellow; }
}
}
.home {
.nav-branding {
display: none;
}
}
.slider-arrow--right {
bottom: 50%;
right: 30px;
}
.slider-arrow--left {
bottom: 50%;
left: 30px;
}
.slider-nav {
position: absolute;
bottom: 30px;
}
.slider-nav__item {
width: 12px;
height: 12px;
float: left;
clear: none;
display: block;
margin: 0 5px;
background: #fff;
}
.slider-nav__item:hover {
background: #ccc;
}
.slider-nav__item--current {
background: #ccc;
}

View File

@ -0,0 +1,3 @@
/*
* Require here your print media stylesheets
*/

View File

@ -0,0 +1,38 @@
@media screen and (max-width: $screen-sm-min) {
.modal-dialog { top: 0; }
.modal-header {
.modal-logo {
display: none;
}
}
.about-fablab {
.about-title {
font-size: rem-calc(30);
line-height: rem-calc(28);
text-align: center;
}
.about-picture {
padding: 70px 0;
height: 226px;
}
}
}
@media screen and (max-width: $screen-md-min) {
.heading {
.heading-title {
h1 {
font-size: rem-calc(16);
padding: 26px 15px;
}
}
}
}

View File

@ -0,0 +1,334 @@
.nopadding {
padding: 0 !important;
margin: 0 !important;
}
.no-upper {
text-transform: none;
}
.font-felt { font-family: $font-felt; }
p.font-felt {
}
p, .widget p {
&.font-felt, .font-felt { font-size: rem-calc(18) !important; }
&.fleche-left {
position: relative;
padding-left: 5px;
}
img.fleche-left {
position: absolute;
left: -35px;
top: 15px;
}
}
.pos-rlt{position: relative;}
.pos-stc{position: static;}
.pos-abt{position: absolute;}
.line {*width: 100%;height: 2px;margin: 10px 0;font-size:0;overflow: hidden;}
.line-s{height: 1px;}
.line-xs{margin: 0}
.line-lg{margin-top:15px;margin-bottom: 15px}
.line-dashed{border-style: dashed !important;background-color: transparent;border-width:0;}
.no-line{border-width: 0}
.no-border, .no-borders{border-color:transparent;border-width:0}
.no-radius{border-radius: 0}
.block{display:block;}
.block.hide{display: none;}
.inline{display:inline-block !important;}
.none{display: none;}
.pull-right-lg{float: right;}
.pull-none{float: none;}
.rounded{border-radius: 500px;}
.h480 { height: 480px; }
// .btn-s-xs{min-width: 90px}
// .btn-s-sm{min-width: 100px}
// .btn-s-md{min-width: 120px}
// .btn-s-lg{min-width: 150px}
// .btn-s-xl{min-width: 200px}
.l-h-2x{line-height: rem-calc(18);}
// .l-h-1x{line-height: 1.2;}
// .l-h{line-height: 1.5;}
.l-n { line-height: 1em; }
.v-middle{vertical-align: middle !important;}
.v-top{vertical-align: top !important;}
.v-bottom{vertical-align: bottom !important;}
.font-normal{font-weight: normal;}
.font-thin{font-weight: 300;}
.font-sbold{font-weight: 600;}
.font-bold{font-weight: 700;}
.font-ebold{font-weight: 900;}
.text-lg{font-size: $font-size-lg;}
.text-md{font-size: $font-size-md;}
.text-sm{font-size: $font-size-sm;}
.text-xs{font-size: $font-size-xs;}
.text-base{font-size: $font-size-base;}
.text-ellipsis{
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
.text-u-c, .upper{text-transform: uppercase;}
.text-l-t{text-decoration: line-through;}
.text-u-l, .underline {text-decoration: underline;}
.text-c { text-transform: capitalize; }
.text-center { text-align: center; }
.text-active, .active > .text, .active > .auto .text{display: none !important;}
.active > .text-active, .active > .auto .text-active{display: inline-block !important;}
.box-shadow{
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05);
}
.cover { background-size:cover; }
.break-word { word-wrap: break-word; }
.wrapper-xxs{padding: 4px 6px;}
.wrapper-sm{padding: 5px 10px;}
.wrapper{padding: 15px;}
.wrapper-md{padding: 20px;}
.wrapper-lg{padding: 30px;}
.wrapper-xl{padding: 50px;}
.padder{padding-left:15px;padding-right: 15px}
.padder-icon{padding-left:30px;padding-right: 0px; padding-top: 3px; height: 21px}
.padder-v{padding-top:15px;padding-bottom: 15px}
.no-padder{padding: 0 !important;}
.pull-in{margin-left: -15px;margin-right: -15px;}
.pull-out{margin:-10px -15px;}
.width-70 { width: 70%; }
.b{border: 1px solid rgba(0, 0, 0, 0.05)}
.b-a{border: 1px solid $border-color}
.b-t{border-top: 1px solid $border-color}
.b-r{border-right: 1px solid $border-color}
.b-b{border-bottom: 1px solid $border-color}
.b-l{border-left: 1px solid $border-color}
.b-light{border-color: darken($brand-light, 5%)}
.b-dark{border-color: lighten($brand-dark, 5%)}
.b-primary{border-color: lighten($brand-primary, 5%)}
.b-success{border-color: lighten($brand-success, 5%)}
.b-info{border-color: lighten($brand-info, 5%)}
.b-warning{border-color: lighten($brand-warning, 5%)}
.b-danger{border-color: lighten($brand-danger, 5%)}
.b-black{border-color: lighten($brand-black, 5%)}
.b-white{border-color: #fff}
.b-dashed{border-style: dashed !important;}
.b-2x{border-width: 2px}
.b-3x{border-width: 3px}
.b-4x{border-width: 4px}
.b-5x{border-width: 5px}
.no-b { border: none !important; }
.r{
border-radius: $border-radius-base $border-radius-base $border-radius-base $border-radius-base;
}
.r-l{
border-radius: $border-radius-base 0 0 $border-radius-base;
}
.r-r{
border-radius: 0 $border-radius-base $border-radius-base 0;
}
.r-t{
border-radius: $border-radius-base $border-radius-base 0 0;
}
.r-b{
border-radius: 0 0 $border-radius-base $border-radius-base;
}
.r-n { border-radius: 0 0 0 0; }
.p-lg { padding: 30px; }
.p-l { padding: 16px; }
.m-xxs{margin: 2px 4px}
.m-xs{margin: 5px;}
.m-sm{margin: 10px;}
.m{margin: 15px;}
.m-md{margin: 20px;}
.m-lg{margin: 30px;}
.m-xl{margin: 50px;}
.m-n{margin: 0 !important}
.m-l-none{margin-left: 0}
.m-l-xs{margin-left: 5px;}
.m-l-sm{margin-left: 10px;}
.m-l{margin-left: 15px}
.m-l-md{margin-left: 20px;}
.m-l-lg{margin-left: 30px;}
.m-l-xl{margin-left: 40px;}
.m-l-n-xxs{margin-left: -1px}
.m-l-n-xs{margin-left: -5px}
.m-l-n-sm{margin-left: -10px}
.m-l-n{margin-left: -15px}
.m-l-n-md{margin-left: -20px}
.m-l-n-lg{margin-left: -30px}
.m-l-n-xl{margin-left: -40px}
.m-t-none{margin-top:0}
.m-t-xxs{margin-top: 1px;}
.m-t-xs{margin-top: 5px;}
.m-t-sm{margin-top: 10px;}
.m-t{margin-top: 15px}
.m-t-md{margin-top: 20px;}
.m-t-lg{margin-top: 30px;}
.m-t-xl{margin-top: 40px;}
.m-t-n-xxs{margin-top: -1px}
.m-t-n-xs{margin-top: -5px}
.m-t-n-sm{margin-top: -10px}
.m-t-n{margin-top: -15px}
.m-t-n-md{margin-top: -20px}
.m-t-n-lg{margin-top: -30px}
.m-t-n-xl{margin-top: -40px}
.m-r-none{margin-right: 0}
.m-r-xxs{margin-right: 1px}
.m-r-xs{margin-right: 5px}
.m-r-sm{margin-right: 10px}
.m-r{margin-right: 15px}
.m-r-md{margin-right: 20px}
.m-r-lg{margin-right: 30px}
.m-r-xl{margin-right: 40px}
.m-r-n-xxs{margin-right: -1px}
.m-r-n-xs{margin-right: -5px}
.m-r-n-sm{margin-right: -10px}
.m-r-n{margin-right: -15px}
.m-r-n-md{margin-right: -20px}
.m-r-n-lg{margin-right: -30px}
.m-r-n-xl{margin-right: -40px}
.m-b-none{margin-bottom: 0}
.m-b-xxs{margin-bottom: 1px;}
.m-b-xs{margin-bottom: 5px;}
.m-b-sm{margin-bottom: 10px;}
.m-b{margin-bottom: 15px;}
.m-b-md{margin-bottom: 20px;}
.m-b-lg{margin-bottom: 30px;}
.m-b-xl{margin-bottom: 40px;}
.m-b-n-xxs{margin-bottom: -1px}
.m-b-n-xs{margin-bottom: -5px}
.m-b-n-sm{margin-bottom: -10px}
.m-b-n{margin-bottom: -15px}
.m-b-n-md{margin-bottom: -20px}
.m-b-n-lg{margin-bottom: -30px}
.m-b-n-xl{margin-bottom: -40px}
.media-xs{min-width: 50px}
.media-sm{min-width: 80px}
.media-md{min-width: 90px}
.media-lg{min-width: 120px}
.thumb-38 { width: 38px !important; height: 38px; }
.thumb-50 { width: 50px !important; height: 50px; }
.thumb-128 { width: 128px !important; height: 128px; }
.thumb-128-wrapper {
img {
width: 128px !important; height: 128px;
@extend .img-circle;
}
}
.thumb-lg{width: 128px;display: inline-block}
.thumb-md{width: 64px;display: inline-block}
.thumb{width: 50px;display: inline-block}
.thumb-sm{width: 34px;display: inline-block}
.thumb-xs{width: 24px;display: inline-block}
.thumb-wrapper{padding: 2px; border: 1px solid #ddd}
.thumb img,
.thumb-xs img,
.thumb-sm img,
.thumb-md img,
.thumb-lg img,
.thumb-btn img{
// height: auto;
// max-width: 100%;
vertical-align: middle;
}
.img-full{
max-width: 100%;
> img{
max-width: 100%;
}
}
.clear{display:block;overflow: hidden;}
.scroll-x, .scroll-y{overflow:hidden;-webkit-overflow-scrolling:touch;}
.scroll-y{overflow-y:auto;}
.scroll-x{overflow-x:auto;}
.no-touch {
.scroll-x,
.scroll-y{
overflow: hidden;
}
.scroll-x{
&:hover,
&:focus,
&:active{
overflow-x: auto;
}
}
.scroll-y{
&:hover,
&:focus,
&:active{
overflow-y: auto;
}
}
.hover-action{
display: none;
}
.hover:hover {
.hover-action{
display: inherit;
}
}
}
@media screen and (min-width: $screen-lg-min) {
.b-r-lg {border-right: 1px solid $border-color; }
}
/*desktop*/
@media screen and (min-width: $screen-md-min) {
.b-r-md {border-right: 1px solid $border-color; }
.col-lg-2-4{width: 20.000%;float: left;}
.hide-b-md { border: none; }
}
/*phone*/
@media (max-width: 767px) {
.shift{display: none !important;}
.shift.in{display: block !important;}
.row-2 [class*="col"]{width: 50%;float: left}
.row-2 .col-0{clear: none}
.row-2 li:nth-child(odd) { clear: left;margin-left: 0}
.text-center-xs{text-align: center;}
.text-left-xs{text-align: left;}
.pull-none-xs{float: none !important;}
.dropdown-menu.pull-none-xs{left: 0;}
.hidden-xs.show{display: inherit !important;}
.wrapper-lg{padding: 15px;}
}

View File

@ -0,0 +1,27 @@
/*
*= require_self
*= require select2/select2
*= require jasny-bootstrap/dist/css/jasny-bootstrap
*= require angular-growl/build/angular-growl.min.css
*= require angular-xeditable/dist/css/xeditable
*= require redactor
*= require angular-loading-bar/src/loading-bar
*= require font-awesome
*/
@import "app.functions";
@import "compass";
@import "bootstrap_and_overrides";
@import "app.utilities";
@import "app.colors";
@import "app.base";
@import "app.layout";
@import "app.nav";
@import "app.buttons";
@import "app.components";
@import "app.plugins";
@import "app.responsive";

View File

@ -0,0 +1,979 @@
// a flag to toggle asset pipeline / compass integration
// defaults to true if twbs-font-path function is present (no function => twbs-font-path('') parsed as string == right side)
// in Sass 3.3 this can be improved with: function-exists(twbs-font-path)
$bootstrap-sass-asset-helper: (twbs-font-path("") != unquote('twbs-font-path("")')) !default;
//
// Variables
// --------------------------------------------------
//== Colors
//
//## Gray and brand colors for use across Bootstrap.
$gray-darker: lighten(#000, 13.5%) !default; // #222
$gray-dark: lighten(#000, 20%) !default; // #333
$gray: lighten(#000, 33.5%) !default; // #555
$gray-light: lighten(#000, 60%) !default; // #999
$gray-lighter: lighten(#000, 93.5%) !default; // #eee
$brand-primary: #d92227 !default;
$brand-success: #7bca38 !default;
$brand-info: #1d98ec !default;
$brand-warning: #fdde3f !default;
$brand-danger: $brand-primary !default;
// ADD by sleede ------------------
$brand-black: #12131a;
$brand-dark: #222733;
$brand-light: #f4f3f3; //#f1f3f7
$brand-inverse: #433599;
$black-light: #444444;
$bg-gray: #f5f5f5;
$red-dark: #cb1117;
$red: $red-dark;
$red-light: #e13f45;
$yellow: #fd0;
$blue: $brand-info;
$green: $brand-success;
$beige: #e4cd78;
$violet: #bd7ae9;
$border-color: #dddddd;
$header-bg: $bg-gray;
$off-screen-nav-width: 75%;
$nav-primary-height: 36px;
$nav-xs-width: 90px;
$nav-xs-height: 50px;
$header-md-height: 73px;
//------------------------------------------
//== Scaffolding
//
//## Settings for some of the most global styles.
//** Background color for `<body>`.
$body-bg: #fff !default;
//** Global text color on `<body>`.
$text-color: black !default;
//** Global textual link color.
$link-color: #cb1117 !default;
//** Link hover color set via `darken()` function.
$link-hover-color: #840b0f !default;
//** Link hover decoration.
$link-hover-decoration: underline;
//== Typography
//
//## Font, line-height, and color for body text, headings, and more.
// Semibold = 600, Bold = 700, ExtraB = 800
$font-family-sans-serif: "proxima-nova", Helvetica, Arial, sans-serif !default;
$font-proxima-condensed: "proxima-nova-condensed", Helvetica, Arial, sans-serif !default;
$font-family-serif: Georgia, "Times New Roman", Times, serif !default;
$font-felt: "felt-tip-roman", sans-serif;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
$font-family-base: $font-family-sans-serif !default;
$font-family-monospace: $font-family-base;
$font-size-base: 16px !default;
$font-size-large: ceil(($font-size-base * 1.125)) !default; // ~18px
$font-size-medium: ceil(($font-size-base * 0.875)) !default; // ~14px
$font-size-small: ceil(($font-size-base * 0.75)) !default; // ~12px
//add sleede
$font-size-lg: rem-calc(20);
$font-size-md: rem-calc(18);
$font-size-sm: rem-calc(14);
$font-size-xs: rem-calc(12); // ~11px
$font-size-h1: rem-calc(24) !default; // ~24px
$font-size-h2: rem-calc(20) !default; // ~20px
$font-size-h3: rem-calc(18) !default; // ~18px
$font-size-h4: rem-calc(16) !default; // ~16px
$font-size-h5: rem-calc(14) !default; // ~14px
$font-size-h6: rem-calc(12) !default; // ~12px
//** Unit-less `line-height` for use in components like buttons.
$line-height-base: 1.5 !default; // 24/16
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
//** By default, this inherits from the `<body>`.
$headings-font-family: $font-family-base !default;
$headings-font-weight: 400 !default;
$headings-line-height: 1.5 !default;
$headings-color: black !default;
//== Iconography
//
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
//** Load fonts from this directory.
$icon-font-path: "bootstrap/" !default;
//** File name for all font files.
$icon-font-name: "glyphicons-halflings-regular" !default;
//** Element ID within SVG icon file.
$icon-font-svg-id: "glyphicons_halflingsregular" !default;
$icon-css-prefix: fa;
//== Components
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
$padding-base-vertical: 6px !default;
$padding-base-horizontal: 12px !default;
$padding-large-vertical: 10px !default;
$padding-large-horizontal: 16px !default;
$padding-small-vertical: 5px !default;
$padding-small-horizontal: 10px !default;
$padding-xs-vertical: 1px !default;
$padding-xs-horizontal: 5px !default;
$line-height-large: 1.33 !default;
$line-height-small: 1.5 !default;
$border-radius-base: 4px !default;
$border-radius-large: 6px !default;
$border-radius-small: 3px !default;
//** Global color for active items (e.g., navs or dropdowns).
$component-active-color: #fff !default;
//** Global background color for active items (e.g., navs or dropdowns).
$component-active-bg: $gray-lighter !default;
//** Width of the `border` for generating carets that indicator dropdowns.
$caret-width-base: 4px !default;
//** Carets increase slightly in size for larger components.
$caret-width-large: 5px !default;
//== Tables
//
//## Customizes the `.table` component with basic values, each used across all table variations.
//** Padding for `<th>`s and `<td>`s.
$table-cell-padding: 8px !default;
//** Padding for cells in `.table-condensed`.
$table-condensed-cell-padding: 5px !default;
//** Default background color used for all tables.
$table-bg: transparent !default;
//** Background color used for `.table-striped`.
$table-bg-accent: #f9f9f9 !default;
//** Background color used for `.table-hover`.
$table-bg-hover: $bg-gray !default;
$table-bg-active: $table-bg-hover !default;
//** Border color for table and cell borders.
$table-border-color: #ddd !default;
//== Buttons
//
//## For each of Bootstrap's buttons, define text, background and border color.
$btn-font-weight: normal !default;
$btn-default-color: $text-color !default;
$btn-default-bg: lighten($brand-light, 3%) !default;
$btn-default-border: darken($border-color, 8%) !default;
$btn-primary-color: #fff !default;;
$btn-primary-bg: $brand-primary !default;;
$btn-primary-border: $btn-primary-bg !default;;
$btn-success-color: #fff !default;
$btn-success-bg: $brand-success !default;
$btn-success-border: $btn-success-bg !default;
$btn-info-color: #fff !default;
$btn-info-bg: $brand-info !default;
$btn-info-border: $btn-info-bg !default;
$btn-warning-color: #000 !default;
$btn-warning-bg: $brand-warning !default;
$btn-warning-border: $btn-warning-bg !default;
$btn-danger-color: #fff !default;
$btn-danger-bg: $red !default;
$btn-danger-border: $btn-default-border !default;
$btn-link-disabled-color: $gray-light !default;
//== Forms
//
//##
//** `<input>` background color
$input-bg: #fff !default;
//** `<input disabled>` background color
$input-bg-disabled: $gray-lighter !default;
//** Text color for `<input>`s
$input-color: $gray !default;
//** `<input>` border color
$input-border: darken($border-color, 10%) !default;
//** `<input>` border radius
$input-border-radius: $border-radius-base !default;
//** Border color for inputs on focus
$input-border-focus: $brand-warning !default;
//** Large `.form-control` border radius
$input-border-radius-large: $border-radius-large;
//** Small `.form-control` border radius
$input-border-radius-small: $border-radius-small;
//** Placeholder text color
$input-color-placeholder: $gray-light !default;
//** Default `.form-control` height
$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
//** Large `.form-control` height
$input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
//** Small `.form-control` height
$input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
$legend-color: $gray-dark !default;
$legend-border-color: #e5e5e5 !default;
//** Background color for textual input addons
$input-group-addon-bg: $gray-lighter !default;
//** Border color for textual input addons
$input-group-addon-border-color: $input-border !default;
//** Disabled cursor for form controls and buttons.
$cursor-disabled: not-allowed;
$form-group-margin-bottom: 15px !default;
//== Dropdowns
//
//## Dropdown menu container and contents.
//** Background for the dropdown menu.
$dropdown-bg: #fff !default;
//** Dropdown menu `border-color`.
$dropdown-border: rgba(0,0,0,.15) !default;
//** Dropdown menu `border-color` **for IE8**.
$dropdown-fallback-border: #ccc !default;
//** Divider color for between dropdown items.
$dropdown-divider-bg: #e5e5e5 !default;
//** Dropdown link text color.
$dropdown-link-color: $gray-dark !default;
//** Hover color for dropdown links.
$dropdown-link-hover-color: darken($gray-dark, 5%) !default;
//** Hover background for dropdown links.
$dropdown-link-hover-bg: $bg-gray !default;
//** Active dropdown menu item text color.
$dropdown-link-active-color: $component-active-color !default;
//** Active dropdown menu item background color.
$dropdown-link-active-bg: $component-active-bg !default;
//** Disabled dropdown menu item background color.
$dropdown-link-disabled-color: $gray-light !default;
//** Text color for headers within dropdown menus.
$dropdown-header-color: $gray-light !default;
//** Deprecated `$dropdown-caret-color` as of v3.1.0
$dropdown-caret-color: #000 !default;
//-- Z-index master list
//
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
//
// Note: These variables are not generated into the Customizer.
$zindex-navbar: 1000 !default;
$zindex-dropdown: 1000 !default;
$zindex-popover: 1060 !default;
$zindex-tooltip: 1070 !default;
$zindex-navbar-fixed: 1030 !default;
$zindex-modal-background: 1040 !default;
$zindex-modal: 1050 !default;
//== Media queries breakpoints
//
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
// Extra small screen / phone
//** Deprecated `$screen-xs` as of v3.0.1
$screen-xs: 480px !default;
//** Deprecated `$screen-xs-min` as of v3.2.0
$screen-xs-min: $screen-xs !default;
//** Deprecated `$screen-phone` as of v3.0.1
$screen-phone: $screen-xs-min !default;
// Small screen / tablet
//** Deprecated `$screen-sm` as of v3.0.1
$screen-sm: 768px !default;
$screen-sm-min: $screen-sm !default;
//** Deprecated `$screen-tablet` as of v3.0.1
$screen-tablet: $screen-sm-min !default;
// Medium screen / desktop
//** Deprecated `$screen-md` as of v3.0.1
$screen-md: 992px !default;
$screen-md-min: $screen-md !default;
//** Deprecated `$screen-desktop` as of v3.0.1
$screen-desktop: $screen-md-min !default;
// Large screen / wide desktop
//** Deprecated `$screen-lg` as of v3.0.1
$screen-lg: 1200px !default;
$screen-lg-min: $screen-lg !default;
//** Deprecated `$screen-lg-desktop` as of v3.0.1
$screen-lg-desktop: $screen-lg-min !default;
// So media queries don't overlap when required, provide a maximum
$screen-xs-max: ($screen-sm-min - 1) !default;
$screen-sm-max: ($screen-md-min - 1) !default;
$screen-md-max: ($screen-lg-min - 1) !default;
//== Grid system
//
//## Define your custom responsive grid.
//** Number of columns in the grid.
$grid-columns: 12 !default;
//** Padding between columns. Gets divided in half for the left and right.
$grid-gutter-width: 30px !default;
// Navbar collapse
//** Point at which the navbar becomes uncollapsed.
$grid-float-breakpoint: $screen-sm-min !default;
//** Point at which the navbar begins collapsing.
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
$container-tablet: ((720px + $grid-gutter-width)) !default;
//** For `$screen-sm-min` and up.
$container-sm: $container-tablet !default;
// Medium screen / desktop
$container-desktop: ((940px + $grid-gutter-width)) !default;
//** For `$screen-md-min` and up.
$container-md: $container-desktop !default;
// Large screen / wide desktop
$container-large-desktop: ((1140px + $grid-gutter-width)) !default;
//** For `$screen-lg-min` and up.
$container-lg: $container-large-desktop !default;
//== Navbar
//
//##
// Basics of a navbar
$navbar-height: 50px !default;
$navbar-margin-bottom: $line-height-computed !default;
$navbar-border-radius: $border-radius-base !default;
$navbar-padding-horizontal: floor(($grid-gutter-width / 2)) !default;
$navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) !default;
$navbar-collapse-max-height: 340px !default;
$navbar-default-color: #777 !default;
$navbar-default-bg: #f8f8f8 !default;
$navbar-default-border: darken($navbar-default-bg, 6.5%) !default;
// Navbar links
$navbar-default-link-color: #777 !default;
$navbar-default-link-hover-color: #333 !default;
$navbar-default-link-hover-bg: transparent !default;
$navbar-default-link-active-color: #555 !default;
$navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%) !default;
$navbar-default-link-disabled-color: #ccc !default;
$navbar-default-link-disabled-bg: transparent !default;
// Navbar brand label
$navbar-default-brand-color: $navbar-default-link-color !default;
$navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) !default;
$navbar-default-brand-hover-bg: transparent !default;
// Navbar toggle
$navbar-default-toggle-hover-bg: #ddd !default;
$navbar-default-toggle-icon-bar-bg: #888 !default;
$navbar-default-toggle-border-color: #ddd !default;
// Inverted navbar
// Reset inverted navbar basics
$navbar-inverse-color: $gray-light !default;
$navbar-inverse-bg: #222 !default;
$navbar-inverse-border: darken($navbar-inverse-bg, 10%) !default;
// Inverted navbar links
$navbar-inverse-link-color: $gray-light !default;
$navbar-inverse-link-hover-color: #fff !default;
$navbar-inverse-link-hover-bg: transparent !default;
$navbar-inverse-link-active-color: $navbar-inverse-link-hover-color !default;
$navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%) !default;
$navbar-inverse-link-disabled-color: #444 !default;
$navbar-inverse-link-disabled-bg: transparent !default;
// Inverted navbar brand label
$navbar-inverse-brand-color: $navbar-inverse-link-color !default;
$navbar-inverse-brand-hover-color: #fff !default;
$navbar-inverse-brand-hover-bg: transparent !default;
// Inverted navbar toggle
$navbar-inverse-toggle-hover-bg: #333 !default;
$navbar-inverse-toggle-icon-bar-bg: #fff !default;
$navbar-inverse-toggle-border-color: #333 !default;
//== Navs
//
//##
//=== Shared nav styles
$nav-link-padding: 10px 15px !default;
$nav-link-hover-bg: $gray-lighter !default;
$nav-disabled-link-color: $gray-light !default;
$nav-disabled-link-hover-color: $gray-light !default;
$nav-open-link-hover-color: #fff !default;
//== Tabs
$nav-tabs-border-color: #ddd !default;
$nav-tabs-link-hover-border-color: $gray-lighter !default;
$nav-tabs-active-link-hover-bg: $body-bg !default;
$nav-tabs-active-link-hover-color: $gray !default;
$nav-tabs-active-link-hover-border-color: #ddd !default;
$nav-tabs-justified-link-border-color: #ddd !default;
$nav-tabs-justified-active-link-border-color: $body-bg !default;
//== Pills
$nav-pills-border-radius: $border-radius-base !default;
$nav-pills-active-link-hover-bg: $component-active-bg !default;
$nav-pills-active-link-hover-color: $component-active-color !default;
//== Pagination
//
//##
$pagination-color: $link-color !default;
$pagination-bg: #fff !default;
$pagination-border: #ddd !default;
$pagination-hover-color: $link-hover-color !default;
$pagination-hover-bg: $gray-lighter !default;
$pagination-hover-border: #ddd !default;
$pagination-active-color: #fff !default;
$pagination-active-bg: $brand-primary !default;
$pagination-active-border: $brand-primary !default;
$pagination-disabled-color: $gray-light !default;
$pagination-disabled-bg: #fff !default;
$pagination-disabled-border: #ddd !default;
//== Pager
//
//##
$pager-bg: $pagination-bg !default;
$pager-border: $pagination-border !default;
$pager-border-radius: 15px !default;
$pager-hover-bg: $pagination-hover-bg !default;
$pager-active-bg: $pagination-active-bg !default;
$pager-active-color: $pagination-active-color !default;
$pager-disabled-color: $pagination-disabled-color !default;
//== Jumbotron
//
//##
$jumbotron-padding: 30px !default;
$jumbotron-color: inherit !default;
$jumbotron-bg: $gray-lighter !default;
$jumbotron-heading-color: inherit !default;
$jumbotron-font-size: ceil(($font-size-base * 1.5)) !default;
//== Form states and alerts
//
//## Define colors for form feedback states and, by default, alerts.
$state-success-text: #3c763d !default;
$state-success-bg: #dff0d8 !default;
$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%) !default;
$state-info-text: #31708f !default;
$state-info-bg: #d9edf7 !default;
$state-info-border: darken(adjust-hue($state-info-bg, -10), 7%) !default;
$state-warning-text: #8a6d3b !default;
$state-warning-bg: #fcf8e3 !default;
$state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%) !default;
$state-danger-text: #a94442 !default;
$state-danger-bg: #f2dede !default;
$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default;
//== Tooltips
//
//##
//** Tooltip max width
$tooltip-max-width: 200px !default;
//** Tooltip text color
$tooltip-color: white !default;
//** Tooltip background color
$tooltip-bg: #424242 !default;
$tooltip-opacity: .9 !default;
//** Tooltip arrow width
$tooltip-arrow-width: 5px !default;
//** Tooltip arrow color
$tooltip-arrow-color: $tooltip-bg !default;
//add sleede
$tooltip-fallback-color: rgba(0,0,0,.9) !default;
//== Arrow
// add by sleede
//##
$arrow-width: 7px;
$arrow-color: #fff;
$arrow-outer-width: ($arrow-width + 1);
$arrow-outer-color: rgba(0,0,0,.1);
$arrow-outer-fallback-color: #eee;
//== Popovers
//
//##
//** Popover body background color
$popover-bg: #fff !default;
//** Popover maximum width
$popover-max-width: 276px !default;
//** Popover border color
$popover-border-color: rgba(0,0,0,.2) !default;
//** Popover fallback border color
$popover-fallback-border-color: #ccc !default;
//** Popover title background color
$popover-title-bg: darken($popover-bg, 3%) !default;
//** Popover arrow width
$popover-arrow-width: 10px !default;
//** Popover arrow color
$popover-arrow-color: #fff !default;
//** Popover outer arrow width
$popover-arrow-outer-width: ($popover-arrow-width + 1) !default;
//** Popover outer arrow color
$popover-arrow-outer-color: fade_in($popover-border-color, 0.05) !default;
//** Popover outer arrow fallback color
$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default;
//== Labels
//
//##
//** Default label background color
$label-default-bg: $gray-light !default;
//** Primary label background color
$label-primary-bg: $brand-primary !default;
//** Success label background color
$label-success-bg: $brand-success !default;
//** Info label background color
$label-info-bg: $brand-info !default;
//** Warning label background color
$label-warning-bg: $brand-warning !default;
//** Danger label background color
$label-danger-bg: $brand-danger !default;
//** Default label text color
$label-color: #424242 !default;
//** Default text color of a linked label
$label-link-hover-color: $red !default;
//== Modals
//
//##
//** Padding applied to the modal body
$modal-inner-padding: 15px !default;
//** Padding applied to the modal title
$modal-title-padding: 8px !default;
//** Modal title line-height
$modal-title-line-height: $line-height-base !default;
//** Background color of modal content area
$modal-content-bg: #fff !default;
//** Modal content border color
$modal-content-border-color: rgba(0,0,0,.2) !default;
//** Modal content border color **for IE8**
$modal-content-fallback-border-color: #999 !default;
//** Modal backdrop background color
$modal-backdrop-bg: #000 !default;
//** Modal backdrop opacity
$modal-backdrop-opacity: .9 !default;
//** Modal header border color
$modal-header-border-color: #e5e5e5 !default;
//** Modal footer border color
$modal-footer-border-color: $modal-header-border-color !default;
$modal-lg: 600px !default;
$modal-md: 440px !default;
$modal-sm: 340px !default;
//== Alerts
//
//## Define alert colors, border radius, and padding.
$alert-padding: 15px !default;
$alert-border-radius: $border-radius-base !default;
$alert-link-font-weight: bold !default;
$alert-success-bg: $state-success-bg !default;
$alert-success-text: $state-success-text !default;
$alert-success-border: $state-success-border !default;
$alert-info-bg: $state-info-bg !default;
$alert-info-text: $state-info-text !default;
$alert-info-border: $state-info-border !default;
$alert-warning-bg: $state-warning-bg !default;
$alert-warning-text: $state-warning-text !default;
$alert-warning-border: $state-warning-border !default;
$alert-danger-bg: $state-danger-bg !default;
$alert-danger-text: $state-danger-text !default;
$alert-danger-border: $state-danger-border !default;
//== Progress bars
//
//##
//** Background color of the whole progress component
$progress-bg: $bg-gray !default;
//** Progress bar text color
$progress-bar-color: #fff !default;
//** Variable for setting rounded corners on progress bar.
$progress-border-radius: $border-radius-base;
//** Default progress bar color
$progress-bar-bg: $brand-primary !default;
//** Success progress bar color
$progress-bar-success-bg: $brand-success !default;
//** Warning progress bar color
$progress-bar-warning-bg: $brand-warning !default;
//** Danger progress bar color
$progress-bar-danger-bg: $brand-danger !default;
//** Info progress bar color
$progress-bar-info-bg: $brand-info !default;
//== List group
//
//##
//** Background color on `.list-group-item`
$list-group-bg: #fff !default;
//** `.list-group-item` border color
$list-group-border: #ddd !default;
//** List group border radius
$list-group-border-radius: $border-radius-base !default;
//** Background color of single list items on hover
$list-group-hover-bg: $bg-gray !default;
//** Text color of active list items
$list-group-active-color: $brand-info !default;
//** Background color of active list items
$list-group-active-bg: $component-active-bg !default;
//** Border color of active list elements
$list-group-active-border: $list-group-active-bg !default;
//** Text color for content within active list items
$list-group-active-text-color: lighten($list-group-active-bg, 40%) !default;
//** Text color of disabled list items
$list-group-disabled-color: $gray-light !default;
//** Background color of disabled list items
$list-group-disabled-bg: $gray-lighter !default;
//** Text color for content within disabled list items
$list-group-disabled-text-color: $list-group-disabled-color !default;
$list-group-link-color: #555 !default;
$list-group-link-hover-color: $list-group-link-color !default;
$list-group-link-heading-color: #333 !default;
//== Panels
//
//##
$panel-bg: #fff !default;
$panel-body-padding: 15px !default;
$panel-heading-padding: 18px 15px !default;
$panel-footer-padding: $panel-heading-padding !default;
$panel-border-radius: $border-radius-large !default;
// add sleede
$panel-border: $border-color !default;
$panel-heading-bg: #fff !default;
$panel-footer-bg: #fff !default;
//** Border color for elements within panels
$panel-inner-border: #ddd !default;
$panel-footer-bg: $bg-gray !default;
$panel-default-text: $gray-dark !default;
$panel-default-border: #ddd !default;
$panel-default-heading-bg: $bg-gray !default;
$panel-primary-text: #fff !default;
$panel-primary-border: #cbcbcb !default;
$panel-primary-heading-bg: #fff !default;
$panel-success-text: $state-success-text !default;
$panel-success-border: $state-success-border !default;
$panel-success-heading-bg: $state-success-bg !default;
$panel-info-text: $state-info-text !default;
$panel-info-border: $state-info-border !default;
$panel-info-heading-bg: $state-info-bg !default;
$panel-warning-text: $state-warning-text !default;
$panel-warning-border: $state-warning-border !default;
$panel-warning-heading-bg: $state-warning-bg !default;
$panel-danger-text: $state-danger-text !default;
$panel-danger-border: $state-danger-border !default;
$panel-danger-heading-bg: $state-danger-bg !default;
//== Thumbnails
//
//##
//** Padding around the thumbnail image
$thumbnail-padding: 4px !default;
//** Thumbnail background color
$thumbnail-bg: $body-bg !default;
//** Thumbnail border color
$thumbnail-border: #ddd !default;
//** Thumbnail border radius
$thumbnail-border-radius: $border-radius-base !default;
//** Custom text color for thumbnail captions
$thumbnail-caption-color: $text-color !default;
//** Padding around the thumbnail caption
$thumbnail-caption-padding: 9px !default;
//== Wells
//
//##
$well-bg: #fff !default;
$well-border: darken($well-bg, 7%) !default;
//== Badges
//
//##
$badge-color: #fff !default;
//** Linked badge text color on hover
$badge-link-hover-color: #fff !default;
$badge-bg: $gray-light !default;
//** Badge text color in active nav link
$badge-active-color: $link-color !default;
//** Badge background color in active nav link
$badge-active-bg: #fff !default;
$badge-font-weight: bold !default;
$badge-line-height: 1 !default;
$badge-border-radius: 10px !default;
//== Breadcrumbs
//
//##
$breadcrumb-padding-vertical: 8px !default;
$breadcrumb-padding-horizontal: 15px !default;
//** Breadcrumb background color
$breadcrumb-bg: $bg-gray !default;
//** Breadcrumb text color
$breadcrumb-color: #ccc !default;
//** Text color of current page in the breadcrumb
$breadcrumb-active-color: $gray-light !default;
//** Textual separator for between breadcrumb elements
$breadcrumb-separator: "/" !default;
//== Carousel
//
//##
$carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6) !default;
$carousel-control-color: #fff !default;
$carousel-control-width: 15% !default;
$carousel-control-opacity: .5 !default;
$carousel-control-font-size: 20px !default;
$carousel-indicator-active-bg: #fff !default;
$carousel-indicator-border-color: #fff !default;
$carousel-caption-color: #fff !default;
//== Close
//
//##
$close-font-weight: bold !default;
$close-color: #000 !default;
$close-text-shadow: 0 1px 0 #fff !default;
//== Code
//
//##
$code-color: #c7254e !default;
$code-bg: #f9f2f4 !default;
$kbd-color: #fff !default;
$kbd-bg: #333 !default;
$pre-bg: transparent !default;
$pre-color: $gray-dark !default;
$pre-border-color: transparent !default;
$pre-scrollable-max-height: 340px !default;
//== Type
//
//##
//** Horizontal offset for forms and lists.
$component-offset-horizontal: 180px !default;
//** Text muted color
$text-muted: $gray-light !default;
//** Abbreviations and acronyms border color
$abbr-border-color: $gray-light !default;
//** Headings small color
$headings-small-color: $gray-light !default;
//** Blockquote small color
$blockquote-small-color: $gray-light !default;
//** Blockquote font size
$blockquote-font-size: ($font-size-base * 1.25) !default;
//** Blockquote border color
$blockquote-border-color: $gray-lighter !default;
//** Page header border color
$page-header-border-color: $gray-lighter !default;
//** Width of horizontal description list titles
$dl-horizontal-offset: $component-offset-horizontal !default;
//** Horizontal line color.
$hr-border: $gray-lighter !default;
// Core variables and mixins
// @import "bootstrap/variables"; //overrides
@import "bootstrap/mixins";
// Reset and dependencies
@import "bootstrap/normalize";
@import "bootstrap/print";
@import "bootstrap/glyphicons";
// Core CSS
@import "bootstrap/scaffolding";
@import "bootstrap/type";
@import "bootstrap/code";
@import "bootstrap/grid";
@import "bootstrap/tables";
@import "bootstrap/forms";
@import "bootstrap/buttons";
// Components
@import "bootstrap/component-animations";
@import "bootstrap/dropdowns";
@import "bootstrap/button-groups";
@import "bootstrap/input-groups";
@import "bootstrap/navs";
@import "bootstrap/navbar";
// @import "bootstrap/breadcrumbs";
@import "bootstrap/pagination";
// @import "bootstrap/pager";
@import "bootstrap/labels";
@import "bootstrap/badges";
//@import "bootstrap/jumbotron";
@import "bootstrap/thumbnails";
@import "bootstrap/alerts";
@import "bootstrap/progress-bars";
// @import "bootstrap/media";
@import "bootstrap/list-group";
@import "bootstrap/panels";
@import "bootstrap/responsive-embed";
@import "bootstrap/wells";
@import "bootstrap/close";
// Components w/ JavaScript
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
@import "bootstrap/carousel";
// Utility classes
@import "bootstrap/utilities";
@import "bootstrap/responsive-utilities";

View File

@ -0,0 +1,80 @@
<section class="heading b-b">
<div class="row no-gutter b-b">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>Les Stages et ateliers du Fab Lab</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin'])">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.events_new" role="button">Ajouter un évènement</a>
</section>
</div>
</div>
<section class="m-lg">
<div class="row">
<div class="col-md-12">
<div class="col-md-6 m-b">
<select ng-model="selectedTimezone" class="form-control">
<option value="">Tous les évènements</option>
<option value="passed">Les évènements déjà passés</option>
<option value="future">Les évènements à venir</option>
</select>
</div>
<table class="table">
<thead>
<tr>
<th style="width:30%">Titre</th>
<th style="width:30%">Dates</th>
<th style="width:40%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="event in filtered = (events | eventsFilter:selectedTimezone)">
<td>
<a ui-sref="app.public.events_show({id: event.id})">{{ event.title }} </a>
</td>
<td>
<span>Du {{event.start_date | amDateFormat:'DD/MM/YYYY'}}<span class="text-sm font-thin"> au </span>{{event.end_date | amDateFormat:'DD/MM/YYYY'}}</span>
<br/>
<span ng-if="event.all_day == 'true'">Toute la journée</span>
<span ng-if="event.all_day == 'false'">
De {{event.start_date | date:'HH:mm'}}
<span class="text-sm font-thin"> à </span>
{{event.end_date | date:'HH:mm'}}
</span>
</td>
<td>
<div class="buttons">
<button class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
<i class="fa fa-edit"></i> Éditer
</button>
<%#<button class="btn" ng-click="removeEvent(event)">
<i class="fa fa-trash-o"></i>
</button>%>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center">
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive">Charger les stages et ateliers suivants ...</a>
</div>
</div>
</section>
</section>

View File

@ -0,0 +1,9 @@
<div class="form-group" ng-class="{'has-error': userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$invalid}">
<label for="user_group_id" class="col-sm-3 control-label">Type d'utilisateur</label>
<div class="col-sm-9">
<select ng-model="user.group_id" class="form-control" name="user[group_id]" id="user_group_id" ng-options="g.id as g.name for g in groups" required>
</select>
<input type="hidden" name="user[group_id]" ng-value="user.group_id" />
<span class="help-block" ng-show="userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$error.required">Le type d'utilisateur est obligatoire</span>
</div>
</div>

View File

@ -0,0 +1,53 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-md-1 hidden-xs">
<section class="heading-btn">
<a ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-md-8 b-l b-r">
<section class="heading-title">
<h1>Utilisateur : {{ user.name }}</h1>
</section>
</div>
<div class="col-md-3">
<section class="heading-actions wrapper">
<div class="btn btn-lg btn-block btn-default m-t-xs" ng-click="cancel()">
Annuler
</div>
</section>
</div>
</div>
</section>
<div class="row no-gutter ">
<div class="col-sm-12 col-md-12 b-r">
<form role="form" name="userForm" class="form-horizontal col-md-8" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
<ng-include src="'<%= asset_path 'admin/members/_form.html' %>'"></ng-include>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit" value="Valider les modifications" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
</div>
</section>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,71 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1>Liste des membres</h1>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row">
<div class="col-md-5">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
<input type="text" ng-model="searchMember" class="form-control" placeholder="Recherchez un utilisateur">
</div>
</div>
</div>
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new">Ajouter un nouveau membre</button>
<div class="pull-right">
<a class="btn btn-default" ng-href="api/members/export_members.xls" target="_blank">
<i class="fa fa-file-excel-o"></i> Membres
</a>
</div>
<table class="table">
<thead>
<tr>
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">Nom <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='last_name', 'fa fa-sort-alpha-desc': orderMember=='-last_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">Prénom <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='first_name', 'fa fa-sort-alpha-desc': orderMember=='-first_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('email')">Email <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='email', 'fa fa-sort-alpha-desc': orderMember=='-email', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderMember('profile.phone')">Tel. <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderMember=='profile.phone', 'fa fa-sort-numeric-desc': orderMember=='-profile.phone', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderMember('group.name')">Type utilisateur <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='group.name', 'fa fa-sort-alpha-desc': orderMember=='-group.name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:10%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="member in members | filter:searchMember | orderBy:orderMember">
<td class="text-c">{{ member.profile.last_name }}</td>
<td class="text-c">{{ member.profile.first_name }}</td>
<td>{{ member.email }}</td>
<td>{{ member.profile.phone }}</td>
<td class="text-u-c text-sm">{{ member.group.name }}</td>
<td>
<div class="buttons">
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: member.id})">
<i class="fa fa-edit"></i> Éditer
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>

View File

@ -0,0 +1,52 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-md-1 hidden-xs">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-md-8 b-l b-r">
<section class="heading-title">
<h1>Ajouter un membre</h1>
</section>
</div>
<div class="col-md-3">
<section class="heading-actions wrapper">
<div class="btn btn-lg btn-block btn-default m-t-xs" ng-click="cancel()">
Annuler
</div>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class=" col-sm-12 col-md-9 b-r nopadding">
<form role="form" name="userForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
<ng-include src="'<%= asset_path 'admin/members/_form.html' %>'"></ng-include>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit" value="Enregistrer" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
</div>
</section>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,153 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1>Gestion des éléments projets</h1>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row">
<div class="col-md-12">
<tabset justified="true">
<tab heading="Matériaux">
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()">Ajouter un matériau</button>
<table class="table">
<thead>
<tr>
<th style="width:80%">Nom</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="component in components">
<td>
<span editable-text="component.name" e-cols="100" e-name="name" e-form="rowform" e-required>
{{ component.name }}
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveComponent($data, component.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == component">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelComponent(rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm">Éditer</span>
</button>
<button class="btn btn-danger" ng-click="removeComponent($index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</tab>
<tab heading="Thématiques">
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()">Ajouter une nouvelle thématique</button>
<table class="table">
<thead>
<tr>
<th style="width:80%">Nom</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="theme in themes">
<td>
<span editable-text="theme.name" e-name="name" e-form="rowform" e-required>
{{ theme.name }}
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveTheme($data, theme.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == theme">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelTheme(rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm">Éditer</span>
</button>
<button class="btn btn-danger" ng-click="removeTheme($index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</tab>
<tab heading="Licences">
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()">Ajouter une nouvelle licence</button>
<table class="table">
<thead>
<tr>
<th style="width:30%">Nom</th>
<th style="width:50%" class="hidden-xs">Description</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="licence in licences">
<td>
<span editable-textarea="licence.name" e-rows="5" e-cols="100" e-name="name" e-form="rowform" e-required>
{{ licence.name }}
</span>
</td>
<td class="hidden-xs">
<span editable-textarea="licence.description" e-rows="5" e-cols="100" e-name="description" e-form="rowform" e-required>
<div class="text-sm">{{ licence.description }}</div>
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveLicence($data, licence.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == licence">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelLicence(rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm">Éditer</span>
</button>
<button class="btn btn-danger" ng-click="removeLicence($index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</tab>
</tabset>
</div>
</div>
</section>

View File

@ -0,0 +1,22 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-10 b-l">
<section class="heading-title m-l">
<h4 class="m-l text-sm">Tableau de bord</h4>
<ul class="nav-page nav nav-pills text-u-c text-sm">
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard_profile">Mon profil</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard_projects">Mes projets</a></li>
</ul>
</section>
</div>
</div>
</section>

View File

@ -0,0 +1,56 @@
<div>
<ng-include src="'<%= asset_path 'dashboard/nav.html' %>'"></ng-include>
<div class="row no-gutter wrapper">
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small text-center">
<span class="avatar ">
<fab-user-avatar ng-model="user.profile.user_avatar" avatar-class="thumb-50">test</fab-user-avatar>
</span>
<div class="font-sbold m-t-sm">{{user.name}}</div>
<div>{{user.email}}</div>
<div class="text-xs" ng-if="user.last_sign_in_at"><i>Dernière activité le {{user.last_sign_in_at | amDateFormat: 'Do MMMM '}}</i></div>
</div>
<div class="widget-content no-bg auto wrapper">
<div class="m-t">
<h3 class="text-u-c">Projets</h3>
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
<li ng-repeat="p in user.all_projects">
{{p.name}}
</li>
</ul>
<div ng-if="user.all_projects.length == 0">Aucun projet</div>
</div>
</div>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-9">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b">
<h1 class="red text-u-c">Éditer votre profil</h1>
</div>
<form role="form" name="userForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
<div class="widget-content no-bg auto">
<section class="panel panel-default bg-light m">
<div class="panel-body m-r">
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
</div> <!-- ./panel-body -->
</section>
</div>
<div class="panel-footer no-padder">
<input type="submit" value="Valider les modifications" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,80 @@
<div>
<section class="heading">
<div class="row no-gutter">
<ng-include src="'<%= asset_path 'dashboard/nav.html' %>'"></ng-include>
</div>
</section>
<div class="row no-gutter">
<div class="wrapper" ng-if="user.all_projects.length == 0">Vous n'avez aucun projet.</div>
<div class="widget panel b-a m m-t-lg" ng-repeat="project in user.all_projects">
<div class="panel-heading b-b clearfix">
<h4 class="text-u-c font-sbold pull-left">{{project.name}}</h4> <span class="m-l-sm label label-success text-white">{{project.author_id == currentUser.id ? 'Auteur' : 'Collaborateur'}}</span>
<div class="pull-right">
<a class="btn btn-warning bg-white b-2x rounded upper text-sm text-black" ui-sref="app.public.projects_show({id:project.slug})" role="button">Consulter</a>
</div>
</div>
<div class="widget-content bg-light clearfix">
<div class="col-sm-12 col-md-4 col-lg-4">
<div class="widget panel b-a r-n m-t-md">
<div class="panel-heading b-b small">
<h3>Description</h3>
</div>
<div class="widget-content no-bg wrapper text-black-light">
<!-- TODO -->
<!-- <div class="text-sm font-sbold "><i>Par {{project.author.first_name}}</i></div>
<small class="text-xs m-b"><i>posté le {{project.created_at | amDateFormat: 'Do MMMM YYYY'}}</i></small> -->
<div ng-bind-html="project.description | humanize : 180 | toTrusted"></div>
</div>
</div>
</div>
<div class="col-sm-12 col-md-4 col-lg-4">
<div class="widget panel b-a r-n m-t-md">
<div class="panel-heading b-b small">
<h3>Machines et matériaux</h3>
</div>
<div class="widget-content no-bg wrapper">
<h3 class="text-black-light font-sbold"><i class="fa fa-rocket red"></i> Machines :</h3>
<ul class="list-unstyled m-l-md text-black-light">
<li ng-repeat="m in project.machines">{{m.name}}</li>
</ul>
<h3 class="text-black-light font-sbold"><i class="fa fa-cog red"></i> Matériaux :</h3>
<ul class="list-unstyled m-l-md text-black-light">
<li ng-repeat="c in project.components">{{c.name}}</li>
</ul>
</div>
</div>
</div>
<div class="col-sm-12 col-md-4 col-lg-4">
<div class="widget panel b-a r-n m-t-md">
<div class="panel-heading b-b small">
<h3>Les collaborateurs</h3>
</div>
<div class="widget-content list-group-lg no-bg auto wrapper">
<li class="list-group-item no-b clearfix" ng-repeat="collaborator in project.project_users">
<span class="pull-left thumb-sm avatar m-r-lg">
<fab-user-avatar ng-model="collaborator.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
<i class="on b-white bottom" ng-if="collaborator.is_valid"></i>
<i class="off b-white bottom" ng-if="!collaborator.is_valid"></i>
</span>
<span class="clear"><span>{{collaborator.full_name}}</span>
<small class="text-muted clear text-ellipsis text-c">{{collaborator.username}}</small>
</span>
</li>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,216 @@
<div class="row no-gutter">
<div class=" col-sm-12 col-md-12 col-lg-8 nopadding">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
<input name="_method" type="hidden" ng-value="method">
<div class="form-group" ng-class="{'has-error': eventForm['event[title]'].$dirty && eventForm['event[title]'].$invalid}">
<label for="event_title" class="col-sm-3 control-label">Titre *</label>
<div class="col-sm-9">
<input ng-model="event.title" type="text" name="event[title]" class="form-control" id="event_title" placeholder="" required>
<span class="help-block" ng-show="eventForm['event[title]'].$dirty && eventForm['event[title]'].$error.required">Titre est obligatoire</span>
</div>
</div>
<div class="form-group">
<label for="event_image" class="col-sm-3 control-label">Visuel associé</label>
<div class="col-sm-9">
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(event.event_image)">
<div class="fileinput-new thumbnail" style="width: 334px; height: 250px;">
<img src="data:image/png;base64," src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!event.event_image">
</div>
<div class="fileinput-preview fileinput-exists thumbnail" data-trigger="fileinput" style="max-width: 334px;">
<img ng-src="{{ event.event_image }}" alt="" />
</div>
<div>
<span class="btn btn-default btn-file"><span class="fileinput-new">Choisir une image <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists">Modifier</span>
<input type="file" name="event[event_image_attributes][attachment]"></span>
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput"><i class="fa fa-trash-o"></i></a>
</div>
</div>
</div>
</div>
<div class="form-group" ng-class="{'has-error': eventForm['event[description]'].$dirty && eventForm['event[description]'].$invalid}">
<label for="description" class="col-sm-3 control-label">Description *</label>
<div class="col-sm-9">
<textarea ng-model="event.description" rows="16" class="form-control" id="event_description" placeholder="" name="event[description]" required></textarea>
<span class="help-block" ng-show="eventForm['event[description]'].$dirty && eventForm['event[description]'].$error.required">Description est obligatoire</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Pièces jointes</label>
<div class="col-sm-9">
<div ng-repeat="file in event.event_files_attributes" ng-show="!file._destroy">
<input type="hidden" name="event[event_files_attributes][][id]" ng-value="file.id" />
<input type="hidden" name="event[event_files_attributes][][_destroy]" ng-value="file._destroy" />
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
<div class="form-control" data-trigger="fileinput">
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new">Parcourir</span>
<span class="fileinput-exists">Modifier</span><input type="file" name="event[event_files_attributes][][attachment]"></span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
</div>
</div>
<a class="btn btn-default" ng-click="addFile()" role="button">Ajouter un nouveau fichier <i class="fa fa-file-o fa-fw"></i></a>
</div>
</div>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit" ng-value="submitName" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="eventForm.$invalid"/>
</div>
</section>
</div>
<div class="col-sm-12 col-md-12 col-lg-4">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Type d'évènement</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="event[category_ids][]" value="" />
<select ng-model="event.category_ids" class="form-control" name="event[category_ids][]" required ui-select2>
<option value="{{c.id}}" ng-repeat="c in categories">{{c.name}}</option>
</select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Dates et horaires</h3>
</div>
<div class="widget-content no-bg wrapper">
<div class="m-b">
<label class="v-bottom">Toute la journée</label>
<div class="inline v-top">
<label class="checkbox-inline">
<input type="radio" name="event[all_day]" ng-model="event.all_day" value="true" required/> Oui
</label>
<label class="checkbox-inline">
<input type="radio" name="event[all_day]" ng-model="event.all_day" value="false"/> Non
</label>
</div>
</div>
<input type="hidden" name="event[availability_id]" ng-value="event.availability_id" ng-if="event.availability_id">
<div class="m-b">
<label>Date de début</label>
<div class="input-group">
<input type="hidden" name="event[start_date]" ng-value="event.start_date">
<input type="text"
class="form-control"
ng-model="event.start_date"
datepicker-popup="dd/MM/yyyy"
datepicker-options="datePicker.options"
is-open="datePicker.startOpened"
ng-click="toggleStartDatePicker($event)"
required/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="toggleStartDatePicker($event)"><i class="fa fa-calendar"></i></button>
</span>
</div>
</div>
<div class="m-b">
<label>Date de fin</label>
<div class="input-group">
<input type="hidden" name="event[end_date]" ng-value="event.end_date">
<input type="text"
class="form-control"
ng-model="event.end_date"
datepicker-popup="dd/MM/yyyy"
datepicker-options="datePicker.options"
is-open="datePicker.endOpened"
ng-click="toggleEndDatePicker($event)"
required/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="toggleEndDatePicker($event)"><i class="fa fa-calendar"></i></button>
</span>
</div>
</div>
<div class="m-b row" ng-if="event.all_day =='false'">
<div class="col-xs-6">
<label>Heure de début</label>
<div>
<input type="hidden" name="event[start_time]" ng-value="event.start_time">
<timepicker ng-model="event.start_time" hour-step="1" minute-step="1" show-meridian="ismeridian"></timepicker>
</div>
</div>
<div class="col-xs-6">
<label>Heure de fin</label>
<div>
<input type="hidden" name="event[end_time]" ng-value="event.end_time">
<timepicker ng-model="event.end_time" hour-step="1" minute-step="1" show-meridian="ismeridian"></timepicker>
</div>
</div>
</div>
<div ng-if="method == 'post'" class="m-b">
<label>Récurrence</label>
<select ng-model="event.recurrence" class="form-control" name="event[recurrence]">
<option value="{{t.value}}" ng-repeat="t in recurrenceTypes">{{t.label}}</option>
</select>
<div ng-if="event.recurrence != 'none'">
et se terminera le
<div class="input-group">
<input type="hidden" name="event[recurrence_end_at]" ng-value="event.recurrence_end_at">
<input type="text" class="form-control" datepicker-popup="dd/MM/yyyy" ng-model="event.recurrence_end_at" is-open="datePicker.recurrenceEndOpened" ng-required="true"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="toggleRecurrenceEnd($event)"><i class="fa fa-calendar"></i></button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Tarifs et disponibilités</h3>
</div>
<div class="widget-content no-bg wrapper">
<div class="form-group">
<label for="event_amount" class="col-sm-5 control-label">Tarif standard</label>
<div class="col-sm-5">
<div class="input-group">
<input ng-model="event.amount" type="number" name="event[amount]" class="form-control" id="event_amount" required>
<div class="input-group-addon">€</div>
</div>
<span class="help-block">0 = gratuit</span>
</div>
</div>
<div class="form-group">
<label for="event_reduced_amount" class="col-sm-5 control-label">Tarif réduit</label>
<div class="col-sm-5">
<div class="input-group">
<input ng-model="event.reduced_amount" type="number" name="event[reduced_amount]" class="form-control" id="event_reduced_amount">
<div class="input-group-addon">€</div>
</div>
</div>
</div>
<div class="form-group">
<label for="event_nb_total_places" class="col-sm-5 control-label">Places disponibles</label>
<div class="col-sm-6">
<div class="input-group">
<input ng-model="event.nb_total_places" type="number" name="event[nb_total_places]" class="form-control" id="event_nb_total_places">
<div class="input-group-addon"><i class="fa fa-ticket"></i> </div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
<section class="heading-title">
<h1>Editer l'évènement</h1>
</section>
</div>
</div>
</section>
<form role="form" name="eventForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
<ng-include src="'<%= asset_path 'events/_form.html' %>'"></ng-include>
</form>
</div>

View File

@ -0,0 +1,61 @@
<section class="heading b-b">
<div class="row no-gutter b-b">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>Les Stages et ateliers du Fab Lab</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin'])">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.events_new" role="button">Ajouter un évènement</a>
</section>
</div>
</div>
<section class="m-lg">
<div ng-repeat="month in monthOrder">
<h1>{{month.split(',')[0]}}, {{month.split(',')[1]}}</h1>
<div class="row" ng-repeat="event in (eventsGroupByMonth[month].length/3 | array)">
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="event in eventsGroupByMonth[month].slice(3*$index, 3*$index + 3)" ng-click="showEvent(event)">
<a class="block bg-white img-full p-sm p-l-m box-h-m event b b-light-dark" ui-sref="app.public.events_show({id: event.id})">
<div class="pull-left half-w m-t-n-sm">
<h5 class="text-xs">{{event.categories[0].name}}</h5>
<h4 class="m-n text-sm clear l-n">{{event.title}}</h4>
<h3 class="m-n">{{event.start_date | amDateFormat:'DD/MM'}}<span class="text-sm font-thin"> au </span>{{event.end_date | amDateFormat:'DD/MM'}}</h3>
<h6 class="m-n" ng-if="event.amount">Plein tarif: {{event.amount}}€ <span ng-if="event.reduced_amount > 0">/ Tarif réduit: {{event.reduced_amount}}€</span></h6>
</div>
<!-- Event Image -->
<div class="pull-right crop-130">
<img class="pull-right img-responsive" ng-src="{{event.event_image}}" title="{{event.title}}" ng-if="event.event_image">
<img class="pull-right img-responsive" src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!event.event_image">
</div>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center m-t-md">
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive">Charger les stages et ateliers suivants ...</a>
</div>
</div>
</section>
</section>

View File

@ -0,0 +1,25 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
<section class="heading-title">
<h1>Ajouter un évènement</h1>
</section>
</div>
</div>
</section>
<form role="form" name="eventForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
<ng-include src="'<%= asset_path 'events/_form.html' %>'"></ng-include>
</form>
</div>

View File

@ -0,0 +1,100 @@
<div>
<ui-view autoscroll='true'></ui-view>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{ event.title }} <span class="v-middle badge text-xs bg-formation">{{event.categories[0].name}}</span></h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized('admin')">
<section class="heading-actions wrapper">
<a ui-sref="app.admin.events_edit({id: event.id})" ng-if="isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> éditer</a>
<a ng-click="deleteEvent(event)" ng-if="isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-8">
<div class="article wrapper-lg">
<div class="article-thumbnail" ng-if="event.event_image">
<img ng-src="{{event.event_image}}" alt="{{event.title}}" class="img-responsive">
</div>
<h3>Description de l'évènement</h3>
<p ng-bind-html="event.description | breakFilter"></p>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-4">
<section class="widget panel b-a m" ng-if="event.event_files_attributes">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{event.event_files_attributes.length}}</span>
<h3>Documents à télécharger</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="file in event.event_files_attributes" class="list-group-item no-b clearfix">
<a target="_blank" ng-href="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
</li>
</ul>
</section>
<section class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Informations</h3>
</div>
<div class="panel-content wrapper">
<h5>{{event.categories[0].name}}</h5>
<dl class="text-sm">
<dt><i class="fa fa-calendar"></i> Dates :</dt>
<dd>Début: <span class="text-u-l">{{event.start_date | amDateFormat:'DD/MM/YYYY'}}</span><br>Fin: <span class="text-u-l">{{event.end_date | amDateFormat:'DD/MM/YYYY'}}</span></dd>
<dt><i class="fa fa-clock-o"></i> Horaires :</dt>
<dd ng-if="event.all_day == 'true'"><span>Toute la journée</span></dd>
<dd ng-if="event.all_day == 'false'">De <span class="text-u-l">{{event.start_date | amDateFormat:'HH:mm'}}</span> à <span class="text-u-l">{{event.end_date | amDateFormat:'HH:mm'}}</span></dd>
</dl>
<div class="text-sm" ng-if="event.amount">
<div>Plein tarif : <span>{{ event.amount }} €</span></div>
<div ng-if="event.reduced_amount > 0">Tarif réduit* : {{ event.reduced_amount }} €</div>
</div>
<div class="text-sm m-b" ng-if="event.nb_total_places">
<div>Places disponibles: <span class="font-sbold">{{event.nb_total_places}}</span></div>
</div>
<div class="text-sm m-b" ng-if="!event.nb_total_places">
<div><span class="badge font-sbold">Entrée libre</span></div>
</div>
</div>
</section>
<div ng-if="event.reduced_amount" class="alert alert-warning text-sm m" role="alert">
* Tarif réduit si vous avez moins de 25 ans, que vous êtes étudiant ou demandeur d'emploi.
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,126 @@
<div>
<div class="row wrapper">
<div class="col-lg-8">
<h4 class="text-sm m-t-sm">Les derniers projets documentés</h4>
<carousel interval="5000" disable-animation="true">
<slide class="h480 cover r" ng-repeat="p in last_projects" active="p.active" style="background-image:url({{p.project_image}});">
<div class="carousel-caption">
<h1 class="title"><a ui-sref="app.public.projects_show({id:p.slug})">{{p.name}}</a></h1>
</div>
</slide>
</carousel>
</div>
<div class="col-lg-4 m-t-lg">
<section class="widget panel b-a m-t-sm" ng-if="last_tweets">
<div class="panel-heading b-b small">
<div class="pull-right text-xs align">
<a href="https://twitter.com/<%= ENV['TWITTER_NAME'] %>" target="_blank">Suivez-nous
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x text-yellow"></i>
<i class="fa fa-twitter fa-stack-1x fa-inverse text-white"></i>
</span>
</a>
</div>
<h2>Les derniers tweets</h2>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="tweet in last_tweets" class="text-sm list-group-item no-b clearfix" ng-bind-html="tweet.text">
</li>
</ul>
</section>
<section class="widget panel b-a" >
<div class="panel-heading small b-b">
<h2>Derniers membres inscrits</h2>
</div>
<div class="row m-n">
<div class="col-md-6 b-b b-r block-link" ng-repeat="member in last_members" ui-sref="app.logged.members_show({id:member.slug})">
<div class="padder-v">
<span class="avatar avatar-block text-center">
<fab-user-avatar ng-model="member.profile.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
<a ><span class="user-name m-l-sm text-black m-t-xs">{{member.name}}</span></a>
</span>
</div>
</div>
</div>
<div class="m-t-sm m-b-sm text-center" ng-if="!isAuthenticated()">
<button href="#" ng-click="signup($event)" class="btn btn-warning-full width-70 font-sbold rounded text-sm">Créer un compte</button>
</div>
<div class="m-t-sm m-b-sm text-center" ng-if="isAuthenticated()">
<button href="#" ui-sref="app.logged.members" class="btn btn-warning-full width-70 font-sbold rounded text-sm">Découvrir les membres</button>
</div>
</section>
</div>
<section class="col-lg-12 wrapper">
<h4 class="text-sm m-t-sm">Les prochains ateliers et stages du fablab <a ui-sref="app.public.events_list" class="pull-right"><i class="fa fa-tags"></i> Tous les événements</a></h4>
<div class="row" ng-repeat="event in (upcoming_events.length/3 | array)">
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-4" ng-repeat="event in upcoming_events.slice(3*$index, 3*$index + 3)">
<div class="widget panel panel-default" ui-sref="app.public.events_show({id: event.id})">
<div class="panel-heading picture" style="background-image: url({{event.event_image}});" >
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!event.event_image" class="img-responsive">
</div>
<div class="panel-body" style="heigth:170px;">
<div class="row">
<div class="col-xs-9">
<h1 class="m-b">{{event.title}}</h1>
</div>
<div class="col-xs-3">
<span class="v-middle badge text-xs" ng-class="'bg-{{event.categories[0].name | lowercase}}'">{{event.categories[0].name}}</span>
</div>
</div>
<p>{{event.description | humanize : 500 }}</p>
<hr/>
<div class="row">
<div class="col-sm-6 row m-b-sm">
<i class="fa fa-calendar red col-xs-3 padder-icon"></i>
<h6 class="m-n col-xs-9 ">Du {{event.start_date | amDateFormat:'DD/MM'}}<span class="text-sm font-thin"> au </span>{{event.end_date | amDateFormat:'DD/MM'}}</h6>
</div>
<div class="col-sm-6 row m-b-sm">
<i class="fa fa-clock-o red col-xs-3 padder-icon"></i>
<h6 class="m-n col-xs-9"><span ng-if="event.all_day == 'true'">Toute la journée</span><span ng-if="event.all_day == 'false'">De {{event.start_date | date:'HH:mm'}}<span class="text-sm font-thin"> à </span>{{event.end_date | date:'HH:mm'}}</span></h6>
</div>
<div class="col-sm-12 row m-b">
<i class="fa fa-bookmark red col-xs-1 padder-icon"></i>
<h6 class="m-n col-xs-10">
<span ng-if="!event.nb_total_places">Entrée Libre</span>
<span ng-if="event.nb_total_places && event.amount == 0">Entrée Gratuite</span><span ng-if="event.amount > 0">{{event.amount}} € Plein tarif</span><span ng-if="event.reduced_amount > 0"><br/>{{event.reduced_amount}} € Tarif réduit</span>
<div ng-if="event.nb_free_places == 0"><span class="badge font-sbold bg-red">Événement complet.</span></div>
</h6>
</div>
</div>
<div class="text-center clearfix ">
<div class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm m-b-sm upper text-sm width-70" ui-sref="app.public.events_show({id: event.id})" ><span>Consulter</span></div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>

View File

@ -0,0 +1,81 @@
<form role="form" name="machineForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
<input name="_method" type="hidden" ng-value="method">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
<div class="form-group m-b-lg" ng-class="{'has-error': machineForm['machine[name]'].$dirty && machineForm['machine[name]'].$invalid}">
<label for="name" class="col-sm-2 control-label">Nom *</label>
<div class="col-sm-4">
<input ng-model="machine.name" type="text" name="machine[name]" class="form-control" id="machine_name" placeholder="Nom :" required>
<span class="help-block" ng-show="machineForm['machine[name]'].$dirty && machineForm['machine[name]'].$error.required">Le Nom est obligatoire.</span>
</div>
</div>
<div class="form-group m-b-lg">
<label for="machine_image" class="col-sm-2 control-label">Visuel *</label>
<div class="col-sm-10">
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(machine.machine_image)">
<div class="fileinput-new thumbnail" style="width: 334px; height: 250px;">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!machine.machine_image">
</div>
<div class="fileinput-preview fileinput-exists thumbnail" style="max-width: 334px;">
<img ng-src="{{ machine.machine_image }}" alt="" />
</div>
<div>
<span class="btn btn-default btn-file"><span class="fileinput-new">Ajouter un visuel <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists">Modifier</span>
<input type="file" name="machine[machine_image_attributes][attachment]" ng-model="machine.machine_image" required bs-jasny-fileinput></span>
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput">Supprimer</a>
</div>
</div>
</div>
</div>
<div class="form-group m-b-xl" ng-class="{'has-error': machineForm['machine[description]'].$dirty && machineForm['machine[description]'].$invalid}">
<label for="description" class="col-sm-2 control-label">Description *</label>
<div class="col-sm-10">
<textarea ng-model="machine.description" class="form-control" rows="12" id="machine_description" placeholder="" name="machine[description]" required></textarea>
<span class="help-block" ng-show="machineForm['machine[description]'].$dirty && machineForm['machine[description]'].$error.required">La Description est obligatoire.</span>
</div>
</div>
<div class="form-group m-b-xl" ng-class="{'has-error': machineForm['machine[spec]'].$dirty && machineForm['machine[spec]'].$invalid}">
<label for="spec" class="col-sm-2 control-label">Caractéristiques techniques *</label>
<div class="col-sm-10">
<textarea ng-model="machine.spec" class="form-control" rows="12" id="machine_spec" placeholder="" name="machine[spec]" required></textarea>
<span class="help-block" ng-show="machineForm['machine[spec]'].$dirty && machineForm['machine[spec]'].$error.required">Les Caractéristiques techniques sont obligatoires.</span>
</div>
</div>
<div class="form-group m-b-xl">
<label class="col-sm-2 control-label">Pièces jointes</label>
<div class="col-sm-10">
<div ng-repeat="file in machine.machine_files_attributes" ng-show="!file._destroy">
<input type="hidden" ng-model="file.id" name="machine[machine_files_attributes][][id]" ng-value="file.id" />
<input type="hidden" ng-model="file._destroy" name="machine[machine_files_attributes][][_destroy]" ng-value="file._destroy"/>
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
<div class="form-control" data-trigger="fileinput">
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new">Joindre un fichier</span>
<span class="fileinput-exists">Modifier</span><input type="file" name="machine[machine_files_attributes][][attachment]"></span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
</div>
</div>
<a class="btn btn-default" ng-click="addFile()" role="button">Ajouter une pièce jointe <i class="fa fa-file-o fa-fw"></i></a>
</div>
</div>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit" value="Valider votre machine" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="machineForm.$invalid"/>
</div>
</section>
</form>

View File

@ -0,0 +1,32 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{ machine.name }}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<div class="btn btn-lg btn-block btn-default rounded m-t-xs" ng-click="cancel()">
Annuler
</div>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg nopadding">
<ng-include src="'<%= asset_path 'machines/_form.html' %>'"></ng-include>
</div>
</div>
</div>

View File

@ -0,0 +1,59 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>Les machines du FabLab</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized('admin')">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs" ui-sref="app.admin.machines_new" role="button">Ajouter une machine</a>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row" ng-repeat="machine in (machines.length/3 | array)">
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="machine in machines.slice(3*$index, 3*$index + 3)">
<div class="widget panel panel-default" ng-click="showMachine(machine)">
<div class="panel-heading picture" ng-if="!machine.machine_image">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder class="img-responsive">
</div>
<div class="panel-heading picture" style="background-image:url({{machine.machine_image}})" ng-if="machine.machine_image">
</div>
<div class="panel-body" style="heigth:170px;">
<h1 class="m-b">{{machine.name}}</h1>
<p>{{machine.description | humanize : 140 }}</p>
</div>
<div class="panel-footer no-padder">
<div class="text-center clearfix">
<div class="col-sm-12 no-padder">
<div class="btn btn-default btn-block padder-v no-b red" ng-click="showMachine(machine)">
<i class="fa fa-eye"></i> Consulter
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,29 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-md-1 hidden-xs">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-md-8 b-l b-r">
<section class="heading-title">
<h1>Déclarer une nouvelle machine</h1>
</section>
</div>
</div>
</section>
<div class="row no-gutter" >
<div class="col-md-9 b-r nopadding">
<ng-include src="'<%= asset_path 'machines/_form.html' %>'"></ng-include>
</div>
<div class="col-md-3">
<!-- <button class="btn">TEST</button> -->
</div>
</div>

View File

@ -0,0 +1,85 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
<section class="heading-title">
<h1>{{ machine.name }}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-4 b-t hide-b-md">
<section class="heading-actions wrapper">
<a ui-sref="app.admin.machines_edit({id: machine.id})" ng-if="isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"><i class="fa fa-edit"></i> Éditer</a>
<a ng-click="delete(machine)" ng-if="isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-8 b-r-lg">
<div class="article wrapper-lg" >
<div class="article-thumbnail" ng-if="machine.machine_image">
<img ng-src="{{machine.machine_image}}" alt="{{machine.name}}" class="img-responsive">
</div>
<!-- <h2>{{machine.name}}</h2> -->
<p class="intro" ng-bind-html="machine.description | breakFilter"></p>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-4">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Caractéristiques techniques</h3>
</div>
<div class="widget-content no-bg wrapper">
<h3></h3>
<p><pre>{{machine.spec}}</pre></p>
</div>
</div>
<section class="widget panel b-a m" ng-if="machine.machine_files_attributes">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{machine.machine_files_attributes.length}}</span>
<h3>Fichiers à télécharger</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="file in machine.machine_files_attributes" class="list-group-item no-b clearfix">
<a target="_blank" ng-href="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="machine.machine_projects">
<div class="panel-heading b-b">
<h3>Projets utilisant la machine</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="project in machine.machine_projects" class="list-group-item no-b clearfix">
<a ui-sref="app.public.projects_show({id:project.slug})"><i class="fa"> </i> {{project.name}}</a>
</li>
</ul>
</section>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1>Les membres du Fab Lab</h1>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row">
<div class="col-md-12">
<!-- <button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new">Ajouter un nouveau membre</button> -->
<table class="table">
<thead>
<tr>
<th style="width:15%">Avatar</th>
<th style="width:15%">Utilisateur</th>
<th style="width:15%">Pseudo</th>
<th style="width:15%">Email</th>
<th style="width:10%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="member in members">
<td>
<span class="pull-left thumb-sm avatar m-r">
<fab-user-avatar ng-model="member.profile.user_avatar" avatar-class="thumb-38"></fab-user-avatar>
</span>
</td>
<td class="text-c">{{ member.name }}</td>
<td class="text-u-c text-sm">{{ member.username }}</td>
<td>{{ member.email }}</td>
<td>
<div class="buttons">
<button class="btn btn-default" ui-sref="app.logged.members_show({id: member.slug})">
<i class="fa fa-eye"></i> Consulter
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>

View File

@ -0,0 +1,93 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{user.name}}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a ui-sref="app.logged.members" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs">Liste des membres</a>
</section>
</div>
</div>
</section>
<div class="row no-gutter wrapper">
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="wrapper">
<section class="widget panel no-border bg-black-light text-white lt">
<div class="panel-body">
<div class="row m-t-xl">
<div class="col-xs-12 text-center">
<div class="inline">
<div class="easypiechart easyPieChart" data-percent="75" data-line-width="6" data-bar-color="#fff" data-track-color="#2796de" data-scale-color="false" data-size="140" data-line-cap="butt" data-animate="1000" style="width: 140px; height: 140px; line-height: 140px;">
<div class="thumb-lg avatar thumb-128-wrapper img">
<fab-user-avatar ng-model="user.profile.user_avatar" avatar-class="thumb-140"></fab-user-avatar>
</div>
<canvas width="140" height="140"></canvas></div>
<div class="h4 m-t m-b-xs font-bold text-lt text-white">{{user.name}}</div>
<small class="text-muted m-b">{{user.username}}</small>
</div>
</div>
</div>
<div class="wrapper m-t-xl m-b">
<div class="row m-b">
<div class="col-xs-6 text-right">
<small>Dernière activité</small>
<div class="text-lt font-bold" ng-if="user.last_sign_in_at">le {{user.last_sign_in_at | amDateFormat: 'Do MMMM '}}</div>
</div>
<div class="col-xs-6">
<small>Email</small>
<div class="text-lt font-bold break-word">{{user.email}}</div>
</div>
</div>
<div class="row">
<div class="col-xs-6 text-right">
<small>Logiciels de conception maîtrisés</small>
<div class="text-lt font-bold">{{user.profile.software_mastered}}</div>
</div>
<div class="col-xs-6">
<small>Centres d'intérêts</small>
<div class="text-lt font-bold">{{user.profile.interest}}</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="widget panel b-a m ">
<div class="panel-heading b-b">
<h1 class="red text-u-c">Projets</h1>
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
<li ng-repeat="p in user.all_projects" class="m-t-sm">
<a class="text-u-c" ui-sref="app.public.projects_show({id:p.slug})" role="button">{{p.name}} <span class="m-l-sm label label-success text-white">{{p.author_id == currentUser.id ? 'Auteur' : 'Collaborateur'}}</span></a>
</li>
</ul>
<div ng-if="user.all_projects.length == 0">Aucun projet</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,91 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1>Centre de notifications</h1>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row">
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t-sm m-b" ng-click="markAllAsRead()" ng-disabled="notifications.length == 0">Tout marquer comme lu ({{notifications.length}})</button>
<table class="table">
<thead>
<tr>
<th style="width:10%"></th>
<th style="width:20%">Date</th>
<th style="width:70%">Intitulée</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="notification in notifications" ng-if="notifications.length > 0">
<td>
<button class="btn btn-sm btn-warning" ng-click="markAsRead(notification, $event)">
<i class="fa fa-check"></i>
</button>
</td>
<td>{{ notification.created_at | amDateFormat:'D MMMM YYYY H:mm' }}</td>
<td ng-bind-html="notification.message.description"></td>
</tr>
<tr ng-if="notifications.length == 0">
<td colspan="3">Aucune nouvelle notification.</td>
</tr>
</tbody>
</table>
<h5>Archives</h5>
<table class="table">
<thead>
<tr>
<th style="width:10%"></th>
<th style="width:20%"></th>
<th style="width:70%"></th>
</tr>
</thead>
<tbody>
<tr class="read" ng-repeat="n in notificationsRead | orderBy:'created_at':true" ng-if="notificationsRead.length > 0">
<td>
</td>
<td>{{ n.created_at | amDateFormat:'D MMMM YYYY H:mm' }}</td>
<td ng-bind-html="n.message.description"></td>
</tr>
<tr ng-if="notificationsRead.length == 0">
<td colspan="3">Aucune notification archivée.</td>
</tr>
</tbody>
</table>
<a class="btn btn-default" ng-click="addMoreNotificationsReaded()" ng-if="paginateActive">Charger les notifications suivantes...</a>
</div>
</div>
</section>

View File

@ -0,0 +1,188 @@
<div class="row no-gutter">
<div class=" col-sm-12 col-md-12 col-lg-9 nopadding">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
<input name="_method" type="hidden" ng-value="method">
<div class="form-group" ng-class="{'has-error': projectForm['project[name]'].$dirty && projectForm['project[name]'].$invalid}">
<label for="name" class="col-sm-2 control-label">Nom *</label>
<div class="col-sm-8">
<input ng-model="project.name" type="text" name="project[name]" class="form-control" id="project_name" placeholder="" required>
<span class="help-block" ng-show="projectForm['project[name]'].$dirty && projectForm['project[name]'].$error.required">Nom est obligatoire</span>
</div>
</div>
<div class="form-group">
<label for="project_image" class="col-sm-2 control-label">Illustration</label>
<div class="col-sm-10">
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(project.project_image)">
<div class="fileinput-new thumbnail" style="width: 334px; height: 250px;">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
</div>
<div class="fileinput-preview fileinput-exists thumbnail" data-trigger="fileinput" style="max-width: 334px;">
<img ng-src="{{ project.project_image }}" alt="" />
</div>
<div>
<span class="btn btn-default btn-file"><span class="fileinput-new">Ajouter un visuel <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists">Modifier</span>
<input type="file" name="project[project_image_attributes][attachment]"></span>
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput">Supprimer</a>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Fichier CAO</label>
<div class="col-sm-10">
<div ng-repeat="file in project.project_caos_attributes" ng-show="!file._destroy">
<input type="hidden" name="project[project_caos_attributes][][id]" ng-value="file.id" />
<input type="hidden" name="project[project_caos_attributes][][_destroy]" ng-value="file._destroy" />
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
<div class="form-control" data-trigger="fileinput">
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new">Parcourir</span>
<span class="fileinput-exists">Modifier</span><input type="file" name="project[project_caos_attributes][][attachment]"></span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
</div>
</div>
<a class="btn btn-default" ng-click="addFile()" role="button">Ajouter un nouveau fichier <i class="fa fa-file-o fa-fw"></i></a>
</div>
</div>
<div class="form-group" ng-class="{'has-error': projectForm['project[description]'].$dirty && projectForm['project[description]'].$invalid}">
<label for="description" class="col-sm-2 control-label">Description *</label>
<div class="col-sm-10">
<textarea ng-model="project.description" rows="16" class="" id="project_description" placeholder="" name="project[description]" redactor required></textarea>
<span class="help-block" ng-show="projectForm['project[description]'].$dirty && projectForm['project[description]'].$error.required">Description est obligatoire</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Étapes</label>
<div class="col-sm-10">
<div ng-repeat="step in project.project_steps_attributes" ng-show="!step._destroy">
<div class="m-t-xs m-b-lg">
<span class="label label-warning m-t m-b">Étape {{ $index+1 }}/{{project.project_steps_attributes.length}}</span>
<input type="hidden" name="project[project_steps_attributes][][id]" ng-value="step.id" />
<input type="hidden" name="project[project_steps_attributes][][_destroy]" ng-value="step._destroy" />
<input ng-model="step.title" type="text" name="project[project_steps_attributes][][title]" class="form-control m-b-sm m-t-xs" placeholder="Titre de l'étape" required>
<textarea name="project[project_steps_attributes][][description]" ng-model="step.description" class="m-b-sm form-control" placeholder="" rows="8" redactor></textarea>
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(step.project_step_image)">
<span class="btn btn-default btn-file"><span class="fileinput-new">Ajouter une image</span><span class="fileinput-exists">Modifier l'image</span>
<input type="file" name="project[project_steps_attributes][][project_step_image_attributes][attachment]"></span>
<span class="fileinput-filename">{{step.project_step_image}}</span>
<a class="close fileinput-exists" data-dismiss="fileinput" style="float: none"><i class="fa fa-trash-o"></i></a>
</div>
<div>
<a class="btn btn-sm btn-danger" ng-click="deleteStep(step)" role="button"><i class="fa fa-trash-o"></i> Supprimer l'étape</a>
</div>
</div>
</div>
<a class="btn btn-default m-b" ng-click="addStep()" role="button">Ajouter une nouvelle étape</a>
</div>
</div>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<div ng-show="project.state != 'published'">
<div class="btn btn-lg btn-block btn-valid btn-success text-u-c r-n" publish-project ng-disabled="projectForm.$invalid">
Publier votre projet
</div>
<div class="text-center font-bold text-u-c">ou</div>
</div>
<input type="submit" ng-value="submitName" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="projectForm.$invalid"/>
</div>
</section>
</div>
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Matériaux utilisés</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="project[component_ids][]" value="" />
<select ng-model="project.component_ids" class="form-control form-control-ui-select" name="project[component_ids][]" ui-select2 multiple>
<option value="{{c.id}}" ng-repeat="c in components">{{c.name}}</option>
</select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Machines utilisées</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="project[machine_ids][]" value="" />
<select ng-model="project.machine_ids" class="form-control form-control-ui-select" name="project[machine_ids][]" ui-select2 multiple>
<option value="{{m.id}}" ng-repeat="m in machines">{{m.name}}</option>
</select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Collaborateurs</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="project[user_ids][]" value="" />
<select ng-model="project.user_ids" class="form-control form-control-ui-select" name="project[user_ids][]" ui-select2 multiple>
<option value="{{m.id}}" ng-repeat="m in members">{{m.name}}</option>
</select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Licences Creative Commons</h3>
</div>
<div class="widget-content no-bg wrapper">
<%# TODO: Bug concerne qu'on ne peut pas déselectionner un option %>
<select ng-model="project.licence_id" class="form-control" name="project[licence_id]" ui-select2>
<option value="{{l.id}}" ng-repeat="l in licences">{{l.name}}</option>
</select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Thématiques</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="project[theme_ids][]" value="" />
<select ng-model="project.theme_ids" class="form-control form-control-ui-select" name="project[theme_ids][]" ui-select2 multiple>
<option value="{{t.id}}" ng-repeat="t in themes">{{t.name}}</option>
</select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3>Tags</h3>
</div>
<div class="widget-content no-bg wrapper">
<textarea ng-model="project.tags" class="form-control" id="project_tags" placeholder="" name="project[tags]"></textarea>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,40 @@
<div>
<form role="form" name="projectForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
<section class="heading-title">
<h1>Editer le projet <span class="badge" ng-if="project.state == 'draft'">Brouillon</span></h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-4 b-t hide-b-md">
<section class="heading-actions wrapper">
<!-- <div class="btn btn-lg btn-block btn-default m-t-xs" ng-click="cancel()" ng-if="project.state == 'published'">
Annuler
</div> -->
<input type="submit" ng-value="submitName" class="btn btn-lg btn-warning m-t-xs text-u-c" ng-disabled="projectForm.$invalid"/>
<div class="btn btn-lg btn-valid btn-success m-t-xs text-u-c" publish-project ng-if="project.state == 'draft'" ng-disabled="projectForm.$invalid">
Publier
</div>
</section>
</div>
</div>
</section>
<ng-include src="'<%= asset_path 'projects/_form.html' %>'"></ng-include>
</form>
</div>

View File

@ -0,0 +1,98 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>Les projets du FabLab</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin','member'])">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.logged.projects_new" role="button">Ajouter un projet</a>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="!isAuthenticated()">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.logged.projects_new" role="button">Proposer un projet</a>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row m-b-md">
<div class="col-md-12"><h3 class="m-t-xs">Filtrer les projets</h3></div>
<div class="col-md-3 m-b" ng-show="isAuthenticated()">
<select ng-model="selectedMember" class="form-control">
<option value="">Tous les projets</option>
<option value="0">Mes projets</option>
<option value="1">Les projets auxquels je collabore</option>
</select>
</div>
<div class="col-md-3 m-b">
<select ng-model="selectedMachine" class="form-control" ng-options="m.id as m.name for m in machines">
<option value="">Toutes les machines</option>
</select>
</div>
<div class="col-md-3 m-b">
<select ng-model="selectedTheme" class="form-control" ng-options="t.id as t.name for t in themes">
<option value="">Toutes les thématiques</option>
</select>
</div>
<div class="col-md-3 m-b">
<select ng-model="selectedComponent" class="form-control" ng-options="t.id as t.name for t in components">
<option value="">Tous les matériaux</option>
</select>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="project in filtered = (projects | machineFilter:selectedMachine | projectMemberFilter:selectedMember | themeFilter:selectedTheme | componentFilter:selectedComponent)" ng-click="showProject(project)">
<div class="box-thumb box-thumb-project" style="background-image: url({{project.project_image}});">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
<div class="box-content project-caption">
<h1>{{project.name}}</h1>
</div>
<div class="box-footer">
<div class="btn-group">
<div class="btn btn-default" ui-sref="app.logged.projects_edit({id:project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')">
<i class="fa fa-edit"></i> Éditer
</div>
<div class="btn btn-default" ng-click="showProject(project)">
<i class="fa fa-eye"></i> Consulter
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center">
<a class="btn btn-warning" ng-click="loadMoreProjects()" ng-if="paginateActive">Charger les projets suivants ...</a>
</div>
</div>
</section>

View File

@ -0,0 +1,33 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
<section class="heading-title">
<h1>Ajouter un nouveau projet</h1>
</section>
</div>
<!-- <div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<div class="btn btn-lg btn-block btn-valid btn-info m-t-xs text-u-c" publish-project ng-disabled="projectForm.$invalid">
Publier
</div>
</section>
</div> -->
</div>
</section>
<form role="form" name="projectForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
<ng-include src="'<%= asset_path 'projects/_form.html' %>'"></ng-include>
</form>
</div>

View File

@ -0,0 +1,141 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{ project.name }} <span class="badge" ng-if="project.state == 'draft'">Brouillon</span></h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a ui-sref="app.logged.projects_edit({id: project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> éditer</a>
<a ng-if="!isAuthenticated()" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.logged.projects_new" role="button">Proposer un projet</a>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg">
<div class="article wrapper-lg">
<div class="article-thumbnail" ng-if="project.project_image">
<a href="{{project.project_image}}" target="_blank"><img ng-src="{{project.project_image}}" alt="{{project.name}}" class="img-responsive"></a>
</div>
<h3>Description du projet</h3>
<p ng-bind-html="project.description | toTrusted"></p>
<div class="article-steps">
<div class="row article-step m-b-lg" ng-repeat="step in project.project_steps_attributes">
<div class="col-md-12 m-b-xs">
<h3 class="well well-simple step-title">Étape {{$index+1}} : {{step.title}}</h3>
</div>
<div class="col-md-4" ng-if="step.project_step_image">
<a href="{{step.project_step_image_url}}" target="_blank"><img class="img-responsive m-b" ng-src="{{step.project_step_image_url}}" alt="{{step.title}}" ></a>
</div>
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_image == undefined}">
<p ng-bind-html="step.description | toTrusted"></p>
</div>
</div>
</div>
</div>
<div class="wrapper-lg">
<dir-disqus disqus-shortname="<%= Rails.application.secrets.disqus_shortname %>"
disqus-identifier="project_{{ project.id }}"
disqus-url="{{ project_url }}"
ready-to-bind="{{ contentLoaded }}">
</dir-disqus>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="text-center m-t-lg m-v">
<div class="thumb-lg m-b-xs">
<fab-user-avatar ng-model="project.author.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
</div>
<div><a class="text-sm font-sbold" ui-sref="app.logged.members_show({id: project.author.slug})"><i>Par {{project.author.first_name}}</i></a></div>
<small class="text-xs m-b"><i>posté le {{project.created_at | amDateFormat: 'Do MMMM YYYY'}}</i></small>
</div>
<section class="widget panel b-a m" ng-if="project.project_caos_attributes">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.project_caos_attributes.length}}</span>
<h3>Fichier CAO à télécharger</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="file in project.project_caos_attributes" class="list-group-item no-b clearfix">
<a target="_blank" ng-href="{{file.attachment_url}}" download="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.machines">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.machines.length}}</span>
<h3>Machines et matériaux</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="machine in project.machines" class="list-group-item no-b clearfix">
<a ui-sref="app.public.machines_show({id: machine.id})">{{machine.name}}</a>
</li>
</ul>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="component in project.components" class="list-group-item no-b clearfix">
{{component.name}}
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.project_users.length > 0">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.project_users.length}}</span>
<h3>Les collaborateurs</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li class="list-group-item no-b clearfix block-link" ng-repeat="collaborator in project.project_users" ui-sref="app.logged.members_show({id: collaborator.slug})">
<span class="pull-left thumb-sm avatar m-r">
<fab-user-avatar ng-model="collaborator.user_avatar" avatar-class="thumb-38"></fab-user-avatar>
<i class="on b-white bottom" ng-if="collaborator.is_valid"></i>
<i class="off b-white bottom" ng-if="!collaborator.is_valid"></i>
</span>
<span class="clear"><span>{{collaborator.full_name}}</span>
<small class="text-muted clear text-ellipsis text-c">{{collaborator.username}}</small>
</span>
</li>
</ul>
</section>
</div>
</div>
</div>

View File

@ -0,0 +1,145 @@
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
<input name="_method" type="hidden" ng-value="method">
<input name="user[profile_attributes][id]" type="hidden" ng-value="user.profile.id">
<div class="row m-t">
<div class="col-sm-3 col-sm-offset-1">
<div class="form-group m-t-lg">
<div class="fileinput text-center" data-provides="fileinput" ng-class="fileinputClass(user.profile.user_avatar.attachment_url)">
<div class="fileinput-new thumbnail rounded thumb-128-wrapper" style="width: 140px; height: 140px;">
<img src="<%= image_path("no_avatar.png") %>" class="img-circle">
</div>
<div class="fileinput-preview fileinput-exists thumbnail rounded thumb-128-wrapper" data-trigger="fileinput" style="width: 140px; height: 140px; line-height: 140px;">
<img ng-src="{{ user.profile.user_avatar.attachment_url }}" />
</div>
<div class="m-t-sm">
<input type="hidden" name="user[profile_attributes][user_avatar_attributes][id]" ng-value="user.profile.user_avatar.id">
<input type="hidden" name="user[profile_attributes][user_avatar_attributes][_destroy]" ng-value="true" ng-if="user.profile.user_avatar._destory">
<span class="btn btn-default btn-file" ng-click="user.profile.user_avatar._destory = false"><span class="fileinput-new">Ajouter un avatar</span><span class="fileinput-exists">Modifier</span>
<input type="file" name="user[profile_attributes][user_avatar_attributes][attachment]"></span>
<button class="btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="user.profile.user_avatar._destory = true"><i class="fa fa-trash-o"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-sm-offset-1 col-sm-6">
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$invalid}">
<label class="checkbox-inline btn btn-default">
<input type="radio" name="user[profile_attributes][gender]" ng-model="user.profile.gender" value="true" required/><i class="fa fa-male m-l-sm"></i> Homme
</label>
<label class="checkbox-inline btn btn-default">
<input type="radio" name="user[profile_attributes][gender]" ng-model="user.profile.gender" value="false"/> <i class="fa fa-female m-l-sm"></i> Femme
</label>
<span class="help-block" ng-show="userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$error.required">Le genre est obligatoire</span>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[username]'].$dirty && userForm['user[username]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i></span>
<input ng-model="user.username" type="text" name="user[username]" class="form-control" id="user_username" placeholder="Pseudo" required>
</div>
<span class="help-block" ng-show="userForm['user[username]'].$dirty && userForm['user[username]'].$error.required">Le pseudo est obligatoire</span>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][last_name]'].$dirty && userForm['user[profile_attributes][last_name]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i></span>
<input ng-model="user.profile.last_name" type="text" name="user[profile_attributes][last_name]" class="form-control" id="user_last_name" placeholder="Nom" required>
</div>
<span class="help-block" ng-show="userForm['user[profile_attributes][last_name]'].$dirty && userForm['user[profile_attributes][last_name]'].$error.required">Le nom est obligatoire</span>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][first_name]'].$dirty && userForm['user[profile_attributes][first_name]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i></span>
<input ng-model="user.profile.first_name" type="text" name="user[profile_attributes][first_name]" class="form-control" id="user_first_name" placeholder="Prénom" required>
</div>
<span class="help-block" ng-show="userForm['user[profile_attributes][first_name]'].$dirty && userForm['user[profile_attributes][first_name]'].$error.required">Le prénom est obligatoire</span>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[email]'].$dirty && userForm['user[email]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-envelope"></i> </span>
<input ng-model="user.email" type="email" name="user[email]" class="form-control" id="user_email" placeholder="Adresse email" required>
</div>
<span class="help-block" ng-show="userForm['user[email]'].$dirty && userForm['user[email]'].$error.required">L'email est obligatoire</span>
</div>
<div class="form-group">
<button class="btn btn-warning btn-block" ng-click="change_password = !change_password; $event.stopPropagation(); $event.preventDefault()">Changer de mot de passe</button>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[password]'].$dirty && userForm['user[password]'].$invalid}" ng-if="change_password">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-key"></i> </span>
<input ng-model="user.password" type="password" name="user[password]" class="form-control" id="user_password" placeholder="Nouveau mot de passe" required ng-minlength="8">
</div>
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.required">Le mot de passe est obligatoire</span>
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.minlength">Le mot de passe est trop court (au moins 8 caractères)</span>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$invalid}" ng-if="change_password">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-key"></i> </span>
<input ng-model="user.password_confirmation" type="password" name="user[password_confirmation]" class="form-control" id="user_password_confirmation" placeholder="Confirmation du nouveau mot de passe" required ng-minlength="8">
</div>
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.required">Le mot de passe de confirmation est obligatoire</span>
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.minlength">Le mot de passe de confirmation est trop court (au moins 8 caractères)</span>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> </span>
<input type="text"
id="user_birthday"
class="form-control"
name="user[profile_attributes][birthday]"
ng-model="user.profile.birthday"
datepicker-popup="dd/MM/yyyy"
datepicker-options="datePicker.options"
is-open="datePicker.opened"
placeholder="Date de naissance"
ng-click="openDatePicker($event)"
required/>
</div>
<span class="help-block" ng-show="userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$error.required">La date de naissance est obligatoire</span>
</div>
<div class="form-group">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-map-marker"></i> </span>
<input type="hidden" name="user[profile_attributes][address_attributes][id]" ng-value="user.profile.address.id" />
<input ng-model="user.profile.address.address" type="text" name="user[profile_attributes][address_attributes][address]" class="form-control" id="user_address" placeholder="Adresse">
</div>
</div>
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-phone"></i> </span>
<input ng-model="user.profile.phone" type="text" name="user[profile_attributes][phone]" class="form-control" id="user_phone" placeholder="Numéro de téléphone" required>
</div>
<span class="help-block" ng-show="userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$error.required">Le numéro de téléphone est obligatoire.</span>
</div>
<div class="form-group">
<label for="user_interest">Centres d'intérêts</label>
<textarea ng-model="user.profile.interest" rows="5" name="user[profile_attributes][interest]" class="form-control" id="user_interest" placeholder=""></textarea>
</div>
<div class="form-group">
<label for="user_software_mastered">Logiciels de conception maîtrisés</label>
<textarea ng-model="user.profile.software_mastered" rows="5" name="user[profile_attributes][software_mastered]" class="form-control" id="user_software_mastered" placeholder=""></textarea>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="widget panel b-a m">
<div class="panel-heading b-b small">
<h3 class="panel-title">Sélectionnez un membre</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<select ng-model="ctrl.member_id" class="form-control form-control-ui-select" ui-select2 ng-change="updateMember()">
<option value="{{$index}}" ng-repeat="m in members">{{m.name}}</option>
</select>
{{member}}
</div>
</div>

View File

@ -0,0 +1,4 @@
<img ng-src="<%= image_path("no_avatar.png") %>" class="img-circle" ng-class="avatarClass"
ng-if="!userAvatar || !userAvatar.attachment_url">
<img ng-src="{{userAvatar.attachment_url}}" class="img-circle" ng-class="avatarClass"
ng-if="userAvatar.attachment_url">

Some files were not shown because too many files have changed in this diff Show More