mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-28 09:24:24 +01:00
Merge remote-tracking branch 'LaCasemate/master'
This commit is contained in:
commit
52071c7b83
@ -1 +1 @@
|
||||
2.5.10
|
||||
2.5.11
|
19
CHANGELOG.md
19
CHANGELOG.md
@ -1,5 +1,24 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## v2.5.11 2017 September 7
|
||||
|
||||
- Added tooltip concerning images insertion while configuring the about page
|
||||
- Ability for admins to configure the maximum visibility for availabilities reservation
|
||||
- Administrators isolation in a special group
|
||||
- In login modal, displays an alert if Caps lock key is pressed
|
||||
- Prevent creation of irregular yearly plans (eg. 12 months)
|
||||
- Ability to lock machine, space or training availability slots, to prevent new reservations on them
|
||||
- Fix a bug: admins cannot see all availabilities for spaces in reservation calendar when a user is selected
|
||||
- Fix a bug: missing translation after payment in english and portuguese
|
||||
- Fix a bug: invalid notification when sending monetary coupon to users
|
||||
- Fix a bug: unable to delete group "standard"
|
||||
- Fix a bug: recursive events crossing Daylight Saving Time period changes are shifted by 1 hour
|
||||
- Fix a bug: unable to see availabilities in the public calendar when browsing as a visitor (non-connected)
|
||||
- Updated puma for compatibility with openSSL > 1.0
|
||||
- Documented installation on ArchLinux
|
||||
- [TODO DEPLOY] `rake db:seed` then `rake fablab:fix:migrate_admins_group`
|
||||
- [TODO DEPLOY] `rake fablab:fix:recursive_events_over_DST`
|
||||
|
||||
## v2.5.10 2017 August 16
|
||||
|
||||
- Updated axlsx gem for excel files generation, possible fix for #489
|
||||
|
2
Gemfile
2
Gemfile
@ -43,7 +43,7 @@ group :development do
|
||||
gem 'mailcatcher'
|
||||
gem 'awesome_print'
|
||||
|
||||
gem "puma"
|
||||
gem 'puma'
|
||||
gem 'foreman'
|
||||
|
||||
gem 'capistrano'
|
||||
|
@ -301,8 +301,7 @@ GEM
|
||||
prawn-table (0.2.1)
|
||||
protected_attributes (1.1.3)
|
||||
activemodel (>= 4.0.1, < 5.0)
|
||||
puma (2.11.1)
|
||||
rack (>= 1.1, < 2.0)
|
||||
puma (3.10.0)
|
||||
pundit (1.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (1.6.8)
|
||||
@ -568,4 +567,4 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.3
|
||||
1.15.4
|
||||
|
@ -71,6 +71,7 @@ In you only intend to run fab-manager on your local machine for testing purposes
|
||||
|
||||
1. Install RVM with the ruby version specified in the [.ruby-version file](.ruby-version).
|
||||
For more details about the process, Please read the [official RVM documentation](http://rvm.io/rvm/install).
|
||||
If you're using ArchLinux, you may have to [read this](doc/archlinux_readme.md) before.
|
||||
|
||||
2. Retrieve the project from Git
|
||||
|
||||
|
@ -20,7 +20,7 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ngCooki
|
||||
'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable',
|
||||
'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics',
|
||||
'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64',
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside']).
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock']).
|
||||
config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfigProvider", "AnalyticsProvider", "uibDatepickerPopupConfig", "$provide", "$translateProvider",
|
||||
function($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) {
|
||||
|
||||
|
@ -68,6 +68,7 @@
|
||||
//= require angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat
|
||||
//= require ngFitText/dist/ng-FitText.min
|
||||
//= require angular-aside/dist/js/angular-aside
|
||||
//= require ngCapsLock/ng-caps-lock
|
||||
//= require_tree ./controllers
|
||||
//= require_tree ./services
|
||||
//= require_tree ./directives
|
||||
|
@ -132,6 +132,40 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Mark the selected slot as unavailable for new reservations or allow reservations again on it
|
||||
##
|
||||
$scope.toggleLockReservations = ->
|
||||
# first, define a shortcut to the lock property
|
||||
locked = $scope.availability.lock
|
||||
# then check if we'll allow reservations locking
|
||||
prevent = !locked # if currently locked, allow unlock anyway
|
||||
if (!locked)
|
||||
prevent = false
|
||||
angular.forEach $scope.reservations, (r) ->
|
||||
if r.canceled_at == null
|
||||
prevent = true # if currently unlocked and has any non-cancelled reservation, disallow locking
|
||||
if (!prevent)
|
||||
# open a confirmation dialog
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('admin_calendar.confirmation_required')
|
||||
msg: if locked then _t("admin_calendar.do_you_really_want_to_allow_reservations") else _t("admin_calendar.do_you_really_want_to_block_this_slot")
|
||||
, ->
|
||||
# the admin has confirmed, lock/unlock the slot
|
||||
Availability.lock {id: $scope.availability.id}, {lock: !locked}
|
||||
, (data) -> # success
|
||||
$scope.availability = data
|
||||
growl.success(if locked then _t('admin_calendar.unlocking_success') else _t('admin_calendar.locking_success') )
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
, (error) -> # failed
|
||||
growl.error(if locked then _t('admin_calendar.unlocking_failed') else _t('admin_calendar.locking_failed'))
|
||||
else
|
||||
growl.error(_t('admin_calendar.unlockable_because_reservations'))
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
|
@ -25,7 +25,7 @@ class MembersController
|
||||
|
||||
## Retrieve the profiles groups (eg. students ...)
|
||||
Group.query (groups) ->
|
||||
$scope.groups = groups
|
||||
$scope.groups = groups.filter (g) -> g.slug != 'admins'
|
||||
|
||||
## Retrieve the list the available trainings
|
||||
Training.query().$promise.then (data)->
|
||||
|
@ -13,7 +13,7 @@ class PlanController
|
||||
|
||||
|
||||
## groups list
|
||||
$scope.groups = groups
|
||||
$scope.groups = groups.filter (g) -> g.slug != 'admins'
|
||||
|
||||
## users with role 'partner', notifiables for a partner plan
|
||||
$scope.partners = partners.users
|
||||
|
@ -17,7 +17,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.plans = plans
|
||||
|
||||
## List of groups (eg. normal, student ...)
|
||||
$scope.groups = groups
|
||||
$scope.groups = groups.filter (g) -> g.slug != 'admins'
|
||||
|
||||
## Associate free machine hours with subscriptions
|
||||
$scope.machineCredits = machineCreditsPromise
|
||||
|
@ -66,7 +66,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
|
||||
$scope.moveDelay =
|
||||
name: 'booking_move_delay'
|
||||
value: parseInt(settingsPromise.booking_move_delay)
|
||||
value: parseInt(settingsPromise.booking_move_delay, 10)
|
||||
|
||||
$scope.enableCancel =
|
||||
name: 'booking_cancel_enable'
|
||||
@ -74,7 +74,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
|
||||
$scope.cancelDelay =
|
||||
name: 'booking_cancel_delay'
|
||||
value: parseInt(settingsPromise.booking_cancel_delay)
|
||||
value: parseInt(settingsPromise.booking_cancel_delay, 10)
|
||||
|
||||
$scope.enableReminder =
|
||||
name: 'reminder_enable'
|
||||
@ -82,7 +82,15 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
|
||||
$scope.reminderDelay =
|
||||
name: 'reminder_delay'
|
||||
value: parseInt(settingsPromise.reminder_delay)
|
||||
value: parseInt(settingsPromise.reminder_delay, 10)
|
||||
|
||||
$scope.visibilityYearly =
|
||||
name: 'visibility_yearly'
|
||||
value: parseInt(settingsPromise.visibility_yearly, 10)
|
||||
|
||||
$scope.visibilityOthers =
|
||||
name: 'visibility_others'
|
||||
value: parseInt(settingsPromise.visibility_others, 10)
|
||||
|
||||
|
||||
|
||||
|
@ -256,6 +256,9 @@ Application.Controllers.controller "ShowMachineController", ['$scope', '$state',
|
||||
$state.go('app.public.machines_list')
|
||||
, (error)->
|
||||
growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current machine
|
||||
##
|
||||
@ -412,6 +415,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", '$stat
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
|
@ -8,7 +8,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## list of groups
|
||||
$scope.groups = groupsPromise
|
||||
$scope.groups = groupsPromise.filter (g) -> g.slug != 'admins'
|
||||
|
||||
## default : do not show the group changing form
|
||||
## group ID of the current/selected user
|
||||
@ -18,7 +18,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
for group in groupsPromise
|
||||
for group in $scope.groups
|
||||
groupObj = { id: group.id, name: group.name, plans: [] }
|
||||
for plan in plansPromise
|
||||
groupObj.plans.push(plan) if plan.group_id == group.id
|
||||
|
@ -1156,7 +1156,9 @@ angular.module('application.router', ['ui.router']).
|
||||
'fablab_name',
|
||||
'name_genre',
|
||||
'reminder_enable',
|
||||
'reminder_delay'
|
||||
'reminder_delay',
|
||||
'visibility_yearly',
|
||||
'visibility_others'
|
||||
]").$promise
|
||||
]
|
||||
cguFile: ['CustomAsset', (CustomAsset) ->
|
||||
|
@ -24,4 +24,7 @@ Application.Services.factory 'Availability', ["$resource", ($resource)->
|
||||
isArray: true
|
||||
update:
|
||||
method: 'PUT'
|
||||
lock:
|
||||
method: 'PUT'
|
||||
url: '/api/availabilities/:id/lock'
|
||||
]
|
||||
|
@ -360,6 +360,10 @@ body.container{
|
||||
}
|
||||
|
||||
|
||||
.reservations-locked {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.reservation-canceled {
|
||||
color: #606060;
|
||||
border-radius: 0.2em;
|
||||
|
@ -42,11 +42,12 @@
|
||||
</a>
|
||||
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
|
||||
</div>
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<div class="widget-content no-bg auto wrapper" ng-class="{'reservations-locked': availability.lock}">
|
||||
<ul class="list-unstyled" ng-if="reservations.length > 0">
|
||||
<li ng-repeat="r in reservations" class="m-b-xs" ng-class="{'reservation-canceled':r.canceled_at}">
|
||||
{{r.user.name}}
|
||||
@ -55,25 +56,42 @@
|
||||
<span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
|
||||
<div ng-show="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
|
||||
<div class="m-t" ng-show="availability.lock"><i class="fa fa-ban"/> <span class="m-l-xs" translate>{{ 'admin_calendar.reservations_locked' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="m in machines" class="m-b-xs" ng-show="availability.machine_ids.indexOf(m.id) > -1">
|
||||
{{m.name}}
|
||||
<span class="btn btn-warning btn-xs" ng-click="removeMachine(m)" ng-if="availability.machine_ids.length > 1"><i class="fa fa-times red"></i></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.actions' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<button class="btn btn-default" ng-click="toggleLockReservations()">
|
||||
<span ng-hide="availability.lock">
|
||||
<i class="fa fa-stop" />
|
||||
<span class="m-l-xs" translate>{{ 'admin_calendar.block_reservations' }}</span>
|
||||
</span>
|
||||
<span ng-show="availability.lock">
|
||||
<i class="fa fa-play" />
|
||||
<span class="m-l-xs" translate>{{ 'admin_calendar.allow_reservations' }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="m in machines" class="m-b-xs" ng-show="availability.machine_ids.indexOf(m.id) > -1">
|
||||
{{m.name}}
|
||||
<span class="btn btn-warning btn-xs" ng-click="removeMachine(m)" ng-if="availability.machine_ids.length > 1"><i class="fa fa-times red"></i></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</section>
|
@ -23,7 +23,7 @@
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<div class="buttons" ng-show="!rowform.$visible" ng-hide="group.slug === 'admins'">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
|
@ -44,13 +44,13 @@
|
||||
<tr>
|
||||
<th style="width:20%"><a href="" ng-click="setOrder('name')">{{ 'name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': order == 'name', 'fa fa-sort-alpha-desc': order == '-name', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrder('calls_count')">{{ 'calls_count' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'calls_count', 'fa fa-sort-numeric-desc': order == '-calls_count', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrder('calls_count')">{{ 'calls_count' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'calls_count', 'fa fa-sort-numeric-desc': order == '-calls_count', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
|
||||
<th style="width:20%"><a href="">{{ 'token' | translate }}</a></th>
|
||||
|
||||
<th style="width:20%"><a href="" ng-click="setOrder('created_at')">{{ 'created_at' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'created_at', 'fa fa-sort-numeric-desc': order == '-created_at', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
|
||||
<th style="width:30%"></th>
|
||||
<th style="width:25%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -17,6 +17,7 @@
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.drag_and_drop_to_insert_images' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutBodySetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-2">
|
||||
|
@ -22,7 +22,34 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'settings.ability_for_the_users_to_move_their_reservations' }}</h3>
|
||||
<h3 class="m-l m-t-lg" translate>{{ 'settings.max_visibility' }}</h3>
|
||||
<form class="col-md-4" name="visibilityYearlyForm">
|
||||
<label for="yearlySubscribers" class="control-label m-r" translate>{{ 'settings.visibility_for_yearly_members' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="yearlySubscribers" ng-model="visibilityYearly.value" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(visibilityYearly)" ng-disabled="visibilityYearlyForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
<form class="col-md-4 col-md-offset-2" name="visibilityOthersForm">
|
||||
<label for="others" class="control-label m-r" translate>{{ 'settings.visibility_for_other_members' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="others" ng-model="visibilityOthers.value" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(visibilityOthers)" ng-disabled="visibilityOthersForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l m-t-lg" translate>{{ 'settings.ability_for_the_users_to_move_their_reservations' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableMove" class="control-label m-r" translate>{{ 'settings.reservations_shifting' }}</label>
|
||||
<input bs-switch
|
||||
@ -51,7 +78,7 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'settings.ability_for_the_users_to_cancel_their_reservations' }}</h3>
|
||||
<h3 class="m-l m-t-lg" translate>{{ 'settings.ability_for_the_users_to_cancel_their_reservations' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableCancel" class="control-label m-r" translate>{{ 'settings.reservations_cancelling' }}</label>
|
||||
<input bs-switch
|
||||
|
@ -22,7 +22,12 @@
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{getUserGroup().name}}</span>
|
||||
</uib-alert>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm" ng-click="group.change = !group.change" ng-show="!user.subscribed_plan.name" translate>{{ 'i_want_to_change_group' }}</button>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm"
|
||||
ng-click="group.change = !group.change"
|
||||
ng-hide="user.subscribed_plan.name || user.role === 'admin'"
|
||||
translate>
|
||||
{{ 'i_want_to_change_group' }}
|
||||
</button>
|
||||
</div>
|
||||
<div ng-show="group.change">
|
||||
<select class="form-control" ng-options="g.id as g.name for g in groups" ng-model="userGroup"></select>
|
||||
|
@ -42,6 +42,10 @@
|
||||
ng-minlength="8"/>
|
||||
</div>
|
||||
<a href="#" ng-click="openResetPassword($event)" class="text-xs">{{ 'password_forgotten' | translate }}</a>
|
||||
<div class="alert alert-warning m-t-sm m-b-none text-xs p-sm" ng-show='isCapsLockOn' role="alert">
|
||||
<i class="fa fa-warning"></i>
|
||||
{{ 'caps_lock_is_on' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -12,8 +12,8 @@ class API::AdminsController < API::ApiController
|
||||
@admin = User.new(admin_params.merge(password: generated_password))
|
||||
@admin.send :set_slug
|
||||
|
||||
# we associate any random group to the admin as it is mandatory for users but useless for admins
|
||||
@admin.group = Group.first
|
||||
# we associate the admin group to prevent linking any other 'normal' group (which won't be deletable afterwards)
|
||||
@admin.group = Group.find_by(slug: 'admins')
|
||||
|
||||
# if the authentication is made through an SSO, generate a migration token
|
||||
unless AuthProvider.active.providable_type == DatabaseProvider.name
|
||||
|
@ -2,7 +2,8 @@ class API::AvailabilitiesController < API::ApiController
|
||||
include FablabConfiguration
|
||||
|
||||
before_action :authenticate_user!, except: [:public]
|
||||
before_action :set_availability, only: [:show, :update, :destroy, :reservations]
|
||||
before_action :set_availability, only: [:show, :update, :destroy, :reservations, :lock]
|
||||
before_action :define_max_visibility, only: [:machine, :trainings, :spaces]
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@ -20,22 +21,32 @@ class API::AvailabilitiesController < API::ApiController
|
||||
def public
|
||||
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
|
||||
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
|
||||
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
|
||||
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user)
|
||||
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
|
||||
|
||||
# request for 1 single day
|
||||
if in_same_day(start_date, end_date)
|
||||
# trainings, events
|
||||
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: %w(training event))
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
.where(lock: false)
|
||||
# machines
|
||||
@machine_availabilities = Availability.includes(:tags, :machines).where(available_type: 'machines')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
.where(lock: false)
|
||||
@machine_slots = []
|
||||
@machine_availabilities.each do |a|
|
||||
a.machines.each do |machine|
|
||||
if params[:m] and params[:m].include?(machine.id.to_s)
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: machine, title: machine.name)
|
||||
slot = Slot.new(
|
||||
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
|
||||
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
||||
availability_id: a.id,
|
||||
availability: a,
|
||||
machine: machine,
|
||||
title: machine.name
|
||||
)
|
||||
slot = verify_machine_is_reserved(slot, @reservations, current_user, '')
|
||||
@machine_slots << slot
|
||||
end
|
||||
@ -46,6 +57,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
# spaces
|
||||
@space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
.where(lock: false)
|
||||
|
||||
if params[:s]
|
||||
@space_availabilities.where(available_id: params[:s])
|
||||
@ -56,7 +68,14 @@ class API::AvailabilitiesController < API::ApiController
|
||||
space = a.spaces.first
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: space, title: space.name)
|
||||
slot = Slot.new(
|
||||
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
|
||||
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
||||
availability_id: a.id,
|
||||
availability: a,
|
||||
space: space,
|
||||
title: space.name
|
||||
)
|
||||
slot = verify_space_is_reserved(slot, @reservations, current_user, '')
|
||||
@space_slots << slot
|
||||
end
|
||||
@ -68,11 +87,12 @@ class API::AvailabilitiesController < API::ApiController
|
||||
else
|
||||
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
.where(lock: false)
|
||||
@availabilities.each do |a|
|
||||
if a.available_type == 'training' or a.available_type == 'event'
|
||||
a = verify_training_event_is_reserved(a, @reservations, current_user)
|
||||
elsif a.available_type == 'space'
|
||||
a.is_reserved = is_reserved_availability(a, current_user.id)
|
||||
a.is_reserved = is_reserved_availability(a, current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -124,16 +144,27 @@ class API::AvailabilitiesController < API::ApiController
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND available_type = 'machines'", Time.now)
|
||||
@availabilities = @machine.availabilities.includes(:tags)
|
||||
.where("end_at > ? AND available_type = 'machines'", Time.now)
|
||||
.where(lock: false)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if is_subscription_year(@user)
|
||||
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end_at = @visi_max_other
|
||||
end_at = @visi_max_year if is_subscription_year(@user)
|
||||
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at)
|
||||
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
.where(lock: false)
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: @machine, title: '')
|
||||
slot = Slot.new(
|
||||
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
|
||||
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
||||
availability_id: a.id,
|
||||
availability: a,
|
||||
machine: @machine,
|
||||
title: ''
|
||||
)
|
||||
slot = verify_machine_is_reserved(slot, @reservations, current_user, @current_user_role)
|
||||
@slots << slot
|
||||
end
|
||||
@ -166,12 +197,17 @@ class API::AvailabilitiesController < API::ApiController
|
||||
# who made the request?
|
||||
# 1) an admin (he can see all future availabilities)
|
||||
if current_user.is_admin?
|
||||
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines]).where('availabilities.start_at > ?', Time.now)
|
||||
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines])
|
||||
.where('availabilities.start_at > ?', Time.now)
|
||||
.where(lock: false)
|
||||
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if can_show_slot_plus_three_months(@user)
|
||||
@availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines]).where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end_at = @visi_max_year
|
||||
end_at = @visi_max_year if can_show_slot_plus_three_months(@user)
|
||||
@availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines])
|
||||
.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at)
|
||||
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
.where(lock: false)
|
||||
end
|
||||
|
||||
# finally, we merge the availabilities with the reservations
|
||||
@ -189,18 +225,32 @@ class API::AvailabilitiesController < API::ApiController
|
||||
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
|
||||
@space = Space.friendly.find(params[:space_id])
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND available_type = 'space'", Time.now)
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id)
|
||||
.includes(:slots, user: [:profile]).references(:slots, :user)
|
||||
.where('slots.start_at > ?', Time.now)
|
||||
if current_user.is_admin?
|
||||
@availabilities = @space.availabilities.includes(:tags)
|
||||
.where("end_at > ? AND available_type = 'space'", Time.now)
|
||||
.where(lock: false)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if is_subscription_year(@user)
|
||||
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end_at = @visi_max_other
|
||||
end_at = @visi_max_year if is_subscription_year(@user)
|
||||
@availabilities = @space.availabilities.includes(:tags)
|
||||
.where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at)
|
||||
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
.where(lock: false)
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: @space, title: '')
|
||||
slot = Slot.new(
|
||||
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
|
||||
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
||||
availability_id: a.id,
|
||||
availability: a,
|
||||
space: @space,
|
||||
title: ''
|
||||
)
|
||||
slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role)
|
||||
@slots << slot
|
||||
end
|
||||
@ -234,6 +284,15 @@ class API::AvailabilitiesController < API::ApiController
|
||||
end
|
||||
end
|
||||
|
||||
def lock
|
||||
authorize @availability
|
||||
if @availability.update_attributes(lock: lock_params)
|
||||
render :show, status: :ok, location: @availability
|
||||
else
|
||||
render json: @availability.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def set_availability
|
||||
@availability = Availability.find(params[:id])
|
||||
@ -244,14 +303,22 @@ class API::AvailabilitiesController < API::ApiController
|
||||
:machines_attributes => [:id, :_destroy])
|
||||
end
|
||||
|
||||
def is_reserved_availability(availability, user_id)
|
||||
reserved_slots = []
|
||||
availability.slots.each do |s|
|
||||
if s.canceled_at.nil?
|
||||
reserved_slots << s
|
||||
def lock_params
|
||||
params.require(:lock)
|
||||
end
|
||||
|
||||
def is_reserved_availability(availability, user)
|
||||
if user
|
||||
reserved_slots = []
|
||||
availability.slots.each do |s|
|
||||
if s.canceled_at.nil?
|
||||
reserved_slots << s
|
||||
end
|
||||
end
|
||||
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user.id
|
||||
else
|
||||
false
|
||||
end
|
||||
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user_id
|
||||
end
|
||||
|
||||
def is_reserved(start_at, reservations)
|
||||
@ -371,4 +438,9 @@ class API::AvailabilitiesController < API::ApiController
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def define_max_visibility
|
||||
@visi_max_year = Setting.find_by(name: 'visibility_yearly').value.to_i.months.since
|
||||
@visi_max_other = Setting.find_by(name: 'visibility_others').value.to_i.months.since
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,12 @@ class API::GroupsController < API::ApiController
|
||||
before_action :authenticate_user!, except: :index
|
||||
|
||||
def index
|
||||
@groups = Group.all
|
||||
if current_user and current_user.is_admin?
|
||||
@groups = Group.all
|
||||
else
|
||||
@groups = Group.where.not(slug: 'admins')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def create
|
||||
|
3
app/exceptions/invoice_total_different_error.rb
Normal file
3
app/exceptions/invoice_total_different_error.rb
Normal file
@ -0,0 +1,3 @@
|
||||
# Raised when total of reservation isn't equal to the total of stripe's invoice
|
||||
class InvoiceTotalDifferentError < StandardError
|
||||
end
|
@ -1,3 +0,0 @@
|
||||
# Raised when total of reservation isnt equal total of strip's invoice
|
||||
class InvoiceTotalDiffrentError < StandardError
|
||||
end
|
3
app/exceptions/locked_error.rb
Normal file
3
app/exceptions/locked_error.rb
Normal file
@ -0,0 +1,3 @@
|
||||
# Raised when reserving on a locked availability
|
||||
class LockedError < StandardError
|
||||
end
|
@ -80,6 +80,19 @@ module ApplicationHelper
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Apply a correction for a future DateTime due to change in Daylight Saving Time (DST) period
|
||||
# @param reference {ActiveSupport::TimeWithZone}
|
||||
# @param datetime {DateTime}
|
||||
# Inspired by https://stackoverflow.com/a/12065605
|
||||
##
|
||||
def dst_correction(reference, datetime)
|
||||
res = datetime.in_time_zone(reference.time_zone.tzinfo.name)
|
||||
res = res - 1.hour if res.dst? && !reference.dst?
|
||||
res = res + 1.hour if reference.dst? && !res.dst?
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
## inspired by gems/actionview-4.2.5/lib/action_view/helpers/translation_helper.rb
|
||||
|
@ -1,5 +1,6 @@
|
||||
class Event < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
include ApplicationHelper
|
||||
|
||||
has_one :event_image, as: :viewable, dependent: :destroy
|
||||
accepts_nested_attributes_for :event_image, allow_destroy: true
|
||||
@ -87,8 +88,10 @@ class Event < ActiveRecord::Base
|
||||
r.events.each do |date|
|
||||
days_diff = availability.end_at.day - availability.start_at.day
|
||||
start_at = DateTime.new(date.year, date.month, date.day, availability.start_at.hour, availability.start_at.min, availability.start_at.sec, availability.start_at.zone)
|
||||
start_at = dst_correction(availability.start_at,start_at)
|
||||
end_date = date + days_diff.days
|
||||
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, availability.end_at.hour, availability.end_at.min, availability.end_at.sec, availability.end_at.zone)
|
||||
end_at = dst_correction(availability.start_at,end_at)
|
||||
if event_image
|
||||
ei = EventImage.new(attachment: event_image.attachment)
|
||||
end
|
||||
|
@ -27,6 +27,8 @@ class Plan < ActiveRecord::Base
|
||||
|
||||
validates :amount, :group, :base_name, presence: true
|
||||
validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
|
||||
validates :interval_count, numericality: { less_than: 12 }, if: Proc.new {|plan| plan.interval == 'month'}
|
||||
validates :interval_count, numericality: { less_than: 52 }, if: Proc.new {|plan| plan.interval == 'week'}
|
||||
validates :interval, inclusion: { in: %w(year month week) }
|
||||
validates :base_name, :slug, presence: true
|
||||
|
||||
|
@ -47,6 +47,13 @@ class Reservation < ActiveRecord::Base
|
||||
plan = nil
|
||||
end
|
||||
|
||||
# check that none of the reserved availabilities was locked
|
||||
slots.each do |slot|
|
||||
if slot.availability.lock
|
||||
raise LockedError
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
case reservable
|
||||
|
||||
@ -260,7 +267,7 @@ class Reservation < ActiveRecord::Base
|
||||
# this function only check reservation total is equal strip invoice total when
|
||||
# pay only reservation not reservation + subscription
|
||||
#if !is_equal_reservation_total_and_stp_invoice_total(stp_invoice, coupon_code)
|
||||
#raise InvoiceTotalDiffrentError
|
||||
#raise InvoiceTotalDifferentError
|
||||
#end
|
||||
stp_invoice.pay
|
||||
card.delete if card
|
||||
|
@ -32,7 +32,9 @@ class Setting < ActiveRecord::Base
|
||||
reminder_enable
|
||||
reminder_delay
|
||||
event_explications_alert
|
||||
space_explications_alert )
|
||||
space_explications_alert
|
||||
visibility_yearly
|
||||
visibility_others )
|
||||
}
|
||||
|
||||
after_update :update_stylesheet if :value_changed?
|
||||
|
@ -1,5 +1,5 @@
|
||||
class AvailabilityPolicy < ApplicationPolicy
|
||||
%w(index? show? create? update? destroy? reservations? export?).each do |action|
|
||||
%w(index? show? create? update? destroy? reservations? export? lock?).each do |action|
|
||||
define_method action do
|
||||
user.is_admin?
|
||||
end
|
||||
|
@ -6,11 +6,12 @@ json.array!(@availabilities) do |availability|
|
||||
json.available_type availability.available_type
|
||||
json.machine_ids availability.machine_ids
|
||||
json.training_ids availability.training_ids
|
||||
json.backgroundColor 'white'
|
||||
json.backgroundColor !availability.lock ? 'white' : '#f5f5f5'
|
||||
json.borderColor availability_border_color(availability)
|
||||
json.tag_ids availability.tag_ids
|
||||
json.tags availability.tags do |t|
|
||||
json.id t.id
|
||||
json.name t.name
|
||||
end
|
||||
json.lock availability.lock
|
||||
end
|
||||
|
@ -11,3 +11,4 @@ json.tags @availability.tags do |t|
|
||||
json.id t.id
|
||||
json.name t.name
|
||||
end
|
||||
json.lock @availability.lock
|
||||
|
@ -1,5 +1,11 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.enjoy_a_discount_of_PERCENT_with_code_CODE',
|
||||
PERCENT: notification.attached_object.percent_off,
|
||||
CODE: notification.attached_object.code)
|
||||
if notification.attached_object.type == 'percent_off'
|
||||
json.description t('.enjoy_a_discount_of_PERCENT_with_code_CODE',
|
||||
PERCENT: notification.attached_object.percent_off,
|
||||
CODE: notification.attached_object.code)
|
||||
else
|
||||
json.description t('.enjoy_a_discount_of_AMOUNT_with_code_CODE',
|
||||
AMOUNT: number_to_currency(notification.attached_object.amount_off / 100.00),
|
||||
CODE: notification.attached_object.code)
|
||||
end
|
||||
json.url notification_url(notification, format: :json)
|
||||
|
@ -15,7 +15,7 @@
|
||||
<% end %>
|
||||
|
||||
<%
|
||||
# we must tell the use if he could use the code just once or many times (in case we won't specify)
|
||||
# we must tell the user if he can use the code just once or many times (== maximum wasn't specified)
|
||||
usages = 999 # just a number > 1
|
||||
if @attached_object.validity_per_user == 'once'
|
||||
usages = 1
|
||||
|
@ -54,7 +54,8 @@
|
||||
"messageformat": "=0.1.8",
|
||||
"moment-timezone": "~0.5.0",
|
||||
"ngFitText": "~4.1.1",
|
||||
"angular-aside": "^1.3.2"
|
||||
"angular-aside": "^1.3.2",
|
||||
"ngCapsLock": "ng-caps-lock#^1.0.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": ">=1.10.2",
|
||||
|
@ -48,6 +48,17 @@ en:
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation
|
||||
you_should_select_at_least_a_machine: "You should select at least one machine on this slot."
|
||||
export_is_running_you_ll_be_notified_when_its_ready: "Export is running. You'll be notified when it's ready."
|
||||
actions: "Actions"
|
||||
block_reservations: "Block reservations"
|
||||
do_you_really_want_to_block_this_slot: "Do you really want to block new reservations on this slot? It will become invisible to users."
|
||||
locking_success: "Slot successfully locked, it won't appear any longer in the user calendar"
|
||||
locking_failed: "An error occurred. Slot locking has failed"
|
||||
allow_reservations: "Allow reservations"
|
||||
do_you_really_want_to_allow_reservations: "Do you really want to allow booking again on this slot? It will become visible for the users."
|
||||
unlocking_success: "Slot successfully unlocked, it will appear again in the user calendar"
|
||||
unlocking_failed: "An error occurred. Slot unlocking has failed"
|
||||
reservations_locked: "Booking is blocked"
|
||||
unlockable_because_reservations: "Unable to block booking on this slot because some uncancelled reservations exist on it."
|
||||
|
||||
project_elements:
|
||||
# management of the projects' components
|
||||
@ -568,12 +579,16 @@ en:
|
||||
title_of_the_about_page: "Title of the About page"
|
||||
shift_enter_to_force_carriage_return: "SHIFT + ENTER to force carriage return"
|
||||
input_the_main_content: "Input the main content"
|
||||
drag_and_drop_to_insert_images: "Drap and drop to insert images"
|
||||
input_the_fablab_contacts: "Input the FabLab contacts"
|
||||
reservations: "Reservations"
|
||||
reservations_parameters: "Reservations parameters"
|
||||
confine_the_booking_agenda: "Confine the booking agenda"
|
||||
opening_time: "Opening time"
|
||||
closing_time: "Closing time"
|
||||
max_visibility: "Maximum visibility (in months)"
|
||||
visibility_for_yearly_members: "For currently running subscriptions, at least 1 year long"
|
||||
visibility_for_other_members: "For all other members"
|
||||
ability_for_the_users_to_move_their_reservations: "Ability for the users to move their reservations"
|
||||
reservations_shifting: "Reservations shifting"
|
||||
prior_period_(hours): "Prior period (hours)"
|
||||
@ -608,6 +623,8 @@ en:
|
||||
reminder_enable: "reservation reminding enabling"
|
||||
reminder_delay: "delay before sending the reminder"
|
||||
default_value_is_24_hours: "If the field is leaved empty: 24 hours."
|
||||
visibility_yearly: "maximum visibility for annual subscribers"
|
||||
visibility_others: "maximum visibility for other members"
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Create new API client"
|
||||
|
@ -48,6 +48,17 @@ fr:
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation
|
||||
you_should_select_at_least_a_machine: "Vous devriez sélectionne au moins une machine pour ce créneau."
|
||||
export_is_running_you_ll_be_notified_when_its_ready: "L'export est en cours. Vous serez notifié lorsqu'il sera prêt."
|
||||
actions: "Actions"
|
||||
block_reservations: "Bloquer les réservations"
|
||||
do_you_really_want_to_block_this_slot: "Êtes vous sur de vouloir bloquer les nouvelles réservations sur ce créneau ? Il deviendra alors invisible pour les utilisateurs."
|
||||
locking_success: "Le créneau a bien été verrouillé, il n'apparaitra plus dans le calendrier utilisateur"
|
||||
locking_failed: "Une erreur est survenue. Le verrouillage du créneau a échoué"
|
||||
allow_reservations: "Autoriser les réservations"
|
||||
do_you_really_want_to_allow_reservations: "Êtes vous sur de vouloir autoriser de nouveau la prise de réservations sur ce créneau ? Il deviendra alors visible pour les utilisateurs."
|
||||
unlocking_success: "Le créneau a bien été déverrouillé, il apparaîtra de nouveau dans le calendrier utilisateur"
|
||||
unlocking_failed: "Une erreur est survenue. Le déverrouillage du créneau a échoué"
|
||||
reservations_locked: "Réservations bloquées"
|
||||
unlockable_because_reservations: "Impossible de bloquer les réservations sur ce créneau car il existe des réservations non annulées sur celui-ci."
|
||||
|
||||
project_elements:
|
||||
# gestion des éléments constituant les projets
|
||||
@ -575,12 +586,16 @@ fr:
|
||||
title_of_the_about_page: "Titre page A propos"
|
||||
shift_enter_to_force_carriage_return: "MAJ. + ENTRÉE pour forcer le retour à la ligne"
|
||||
input_the_main_content: "Saisir le contenu principal"
|
||||
drag_and_drop_to_insert_images: "Faites un glisser-déposer pour insérer des images"
|
||||
input_the_fablab_contacts: "Saisir les Contacts du FabLab"
|
||||
reservations: "Réservations"
|
||||
reservations_parameters: "Paramètres des réservations"
|
||||
confine_the_booking_agenda: "Borner l'agenda de réservation"
|
||||
opening_time: "Heure d'ouverture"
|
||||
closing_time: "Heure de fermeture"
|
||||
max_visibility: "Visibilité maximum (en mois)"
|
||||
visibility_for_yearly_members: "Pour les abonnements en cours d'au moins 1 an"
|
||||
visibility_for_other_members: "Pour tous les autres membres"
|
||||
ability_for_the_users_to_move_their_reservations: "Possibilité pour l'utilisateur de déplacer ses réservations"
|
||||
reservations_shifting: "Déplacement des réservations"
|
||||
prior_period_(hours): "Délai préalable (en heures)"
|
||||
@ -615,6 +630,8 @@ fr:
|
||||
reminder_enable: "l'activation du rappel de réservation"
|
||||
reminder_delay: "délai avant envoi de la notification de rappel"
|
||||
default_value_is_24_hours: "Si aucune valeur n'est renseignée : 24 heures."
|
||||
visibility_yearly: "la visibilité maximum pour les abonnées annuels"
|
||||
visibility_others: "la visibilité maximum pour les autres membres"
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Créer un compte client"
|
||||
|
@ -48,6 +48,17 @@ pt:
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Não é possível deletar o slot {{START}} - {{END}} porque já foi reservado por um membro" # angular interpolation
|
||||
you_should_select_at_least_a_machine: "Você deveria selecionar a última máquina neste slot."
|
||||
export_is_running_you_ll_be_notified_when_its_ready: "A Exportação está em andamento. Você será notificado quando terminar."
|
||||
actions: "Ações"
|
||||
block_reservations: "Impedir reservas"
|
||||
do_you_really_want_to_block_this_slot: "Você realmente quer bloquear novas reservas neste slot? Isso se tornará invisível para os usuários."
|
||||
locking_success: "Slot bloqueado com êxito, ela não aparecerá mais tempo no calendário do usuário"
|
||||
locking_failed: "Um erro ocorreu. O bloqueio do slot falhou"
|
||||
allow_reservations: "Permitir a reserva"
|
||||
do_you_really_want_to_allow_reservations: "Você realmente quer permitir a reserva novamente neste slot? Será visível para os usuários."
|
||||
unlocking_success: "Slot desbloqueado com sucesso, ele aparecerá novamente no calendário do usuário"
|
||||
unlocking_failed: "Um erro ocorreu. O desbloqueio do slot falhou"
|
||||
reservations_locked: "Reserva é bloqueado"
|
||||
unlockable_because_reservations: "Não é possível bloquear a reserva neste slot porque existem algumas reservas não cancelados nele."
|
||||
|
||||
project_elements:
|
||||
# management of the projects' components
|
||||
@ -568,12 +579,16 @@ pt:
|
||||
title_of_the_about_page: "Título da página sobre"
|
||||
shift_enter_to_force_carriage_return: "SHIFT + ENTER para forçar o retorno"
|
||||
input_the_main_content: "Introduza o conteúdo principal"
|
||||
drag_and_drop_to_insert_images: "Arrastar e soltar para inserir imagens"
|
||||
input_the_fablab_contacts: "Insira os contatos do FabLab"
|
||||
reservations: "Reservas"
|
||||
reservations_parameters: "Parâmetros das reservas"
|
||||
confine_the_booking_agenda: "Confine a agenda de reserva"
|
||||
opening_time: "Horário de abertura"
|
||||
closing_time: "Horário de fechamento"
|
||||
max_visibility: "Visibilidade máxima (em meses)"
|
||||
visibility_for_yearly_members: "Para inscrições atuais de pelo menos 1 ano"
|
||||
visibility_for_other_members: "Para todos os outros membros"
|
||||
ability_for_the_users_to_move_their_reservations: "Habilidade para os usuários mover suas reservas"
|
||||
reservations_shifting: "Mudança de reservas"
|
||||
prior_period_(hours): "Período anterior (horas)"
|
||||
@ -608,6 +623,8 @@ pt:
|
||||
reminder_enable: "Recordar reserva ativo"
|
||||
reminder_delay: "Atraso antes de enviar o lembrete"
|
||||
default_value_is_24_hours: "Se o campo estiver vazio: 24 horas."
|
||||
visibility_yearly: "visibilidade máxima para assinantes anuais"
|
||||
visibility_others: "visibilidade máxima para outros membros"
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Criar novo cliente de API"
|
||||
|
@ -97,6 +97,7 @@ en:
|
||||
not_registered_to_the_fablab: "Not registered to the Fablab?"
|
||||
create_an_account: "Create an account"
|
||||
wrong_email_or_password: "Wrong e-mail or password."
|
||||
caps_lock_is_on: "Caps lock key is on."
|
||||
|
||||
# forgotten password modal
|
||||
your_email_address_is_unknown: "Your e-mail address is unknown."
|
||||
|
@ -97,6 +97,7 @@ fr:
|
||||
not_registered_to_the_fablab: "Vous n'êtes pas inscrit au FAB LAB ?"
|
||||
create_an_account: "Créer un compte"
|
||||
wrong_email_or_password: "Adresse courriel ou mot de passe incorrect."
|
||||
caps_lock_is_on: "La touche de verrouillage des majuscules est activée."
|
||||
|
||||
# mot de passe oublié
|
||||
your_email_address_is_unknown: "Votre adresse de courriel est inconnue."
|
||||
|
@ -97,6 +97,7 @@ pt:
|
||||
not_registered_to_the_fablab: "Ainda não registrado no Fablab?"
|
||||
create_an_account: "Criar conta"
|
||||
wrong_email_or_password: "E-mail ou senha incorretos."
|
||||
caps_lock_is_on: "A tecla Caps Lock está ativada."
|
||||
|
||||
# forgotten password modal
|
||||
your_email_address_is_unknown: "Seu e-mail não está cadastrado."
|
||||
|
@ -424,7 +424,7 @@ en:
|
||||
_subscription: "subscription"
|
||||
cost_of_the_subscription: "Cost of the subscription"
|
||||
confirm_and_pay: "Confirm and pay"
|
||||
you_have_settled_the_following_machine_hours: "You have settled the following {TYPE, select, Machine{machine hours} Training{training} other{elements}}:" # messageFormat interpolation
|
||||
you_have_settled_the_following_TYPE: "You have settled the following {TYPE, select, Machine{machine hours} Training{training} other{elements}}:" # messageFormat interpolation
|
||||
you_have_settled_a_: "You have settled a"
|
||||
total_: "TOTAL :"
|
||||
thank_you_your_payment_has_been_successfully_registered: "Thank you. Your payment has been successfully registered !"
|
||||
|
@ -424,7 +424,7 @@ pt:
|
||||
_subscription: "inscrição"
|
||||
cost_of_the_subscription: "Custo da inscrição"
|
||||
confirm_and_pay: "Confirmar e pagar"
|
||||
you_have_settled_the_following_machine_hours: "Você liquidou o seguinte {TYPE, select, Machine{horas máquina} Training{training} other{elements}}:" # messageFormat interpolation
|
||||
you_have_settled_the_following_TYPE: "Você liquidou o seguinte {TYPE, select, Machine{horas máquina} Training{training} other{elements}}:" # messageFormat interpolation
|
||||
you_have_settled_a_: "Você tem liquidado:"
|
||||
total_: "TOTAL :"
|
||||
thank_you_your_payment_has_been_successfully_registered: "Obrigado. Seu pagamento foi registrado com sucesso !"
|
||||
|
@ -306,6 +306,7 @@ en:
|
||||
download_here: "Download here"
|
||||
notify_member_about_coupon:
|
||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Enjoy a discount of %{PERCENT}% with code %{CODE}"
|
||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Enjoy a discount of %{AMOUNT} with code %{CODE}"
|
||||
|
||||
statistics:
|
||||
# statistics tools for admins
|
||||
@ -356,3 +357,7 @@ en:
|
||||
# initial price's category for events, created to replace the old "reduced amount" property
|
||||
reduced_fare: "Reduced fare"
|
||||
reduced_fare_if_you_are_under_25_student_or_unemployed: "Reduced fare if you are under 25, student or unemployed."
|
||||
|
||||
group:
|
||||
# name of the user's group for administrators
|
||||
admins: 'Administrators'
|
@ -306,6 +306,7 @@ fr:
|
||||
download_here: "Téléchargez ici"
|
||||
notify_member_about_coupon:
|
||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Bénéficiez d'une remise de %{PERCENT} % avec le code %{CODE}"
|
||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Bénéficiez d'une remise de %{AMOUNT} avec le code %{CODE}"
|
||||
|
||||
statistics:
|
||||
# outil de statistiques pour les administrateurs
|
||||
@ -355,4 +356,8 @@ fr:
|
||||
price_category:
|
||||
# catégorie initiale de prix pour les évènements, en remplacement de l'ancienne propriété "montant réduit"
|
||||
reduced_fare: "Tarif réduit"
|
||||
reduced_fare_if_you_are_under_25_student_or_unemployed: "Tarif réduit si vous avez moins de 25 ans, que vous êtes étudiant ou demandeur d'emploi."
|
||||
reduced_fare_if_you_are_under_25_student_or_unemployed: "Tarif réduit si vous avez moins de 25 ans, que vous êtes étudiant ou demandeur d'emploi."
|
||||
|
||||
group:
|
||||
# nom du groupe utilisateur pour les administrateurs
|
||||
admins: 'Administrateurs'
|
@ -306,6 +306,7 @@ pt:
|
||||
download_here: "Baixe aqui"
|
||||
notify_member_about_coupon:
|
||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Desfrute de um desconto de %{PERCENT}% com o código %{CODE}"
|
||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Desfrute de um desconto de %{AMOUNT} com o código %{CODE}"
|
||||
|
||||
statistics:
|
||||
# statistics tools for admins
|
||||
@ -356,3 +357,7 @@ pt:
|
||||
# initial price's category for events, created to replace the old "reduced amount" property
|
||||
reduced_fare: "Tarifa reduzida"
|
||||
reduced_fare_if_you_are_under_25_student_or_unemployed: "Tarifa reduzida se tiver menos de 25 anos, estudante ou desempregado."
|
||||
|
||||
group:
|
||||
# name of the user's group for administrators
|
||||
admins: 'Administradores'
|
@ -85,6 +85,7 @@ Rails.application.routes.draw do
|
||||
get 'reservations', on: :member
|
||||
get 'public', on: :collection
|
||||
get '/export_index', action: 'export_availabilities', on: :collection
|
||||
put ':id/lock', action: 'lock', on: :collection
|
||||
end
|
||||
|
||||
resources :groups, only: [:index, :create, :update, :destroy]
|
||||
|
5
db/migrate/20170906100906_add_lock_to_availability.rb
Normal file
5
db/migrate/20170906100906_add_lock_to_availability.rb
Normal file
@ -0,0 +1,5 @@
|
||||
class AddLockToAvailability < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :availabilities, :lock, :boolean, default: false
|
||||
end
|
||||
end
|
@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20170227114634) do
|
||||
ActiveRecord::Schema.define(version: 20170906100906) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@ -79,6 +79,7 @@ ActiveRecord::Schema.define(version: 20170227114634) do
|
||||
t.datetime "updated_at"
|
||||
t.integer "nb_total_places"
|
||||
t.boolean "destroying", default: false
|
||||
t.boolean "lock", default: false
|
||||
end
|
||||
|
||||
create_table "availability_tags", force: :cascade do |t|
|
||||
|
18
db/seeds.rb
18
db/seeds.rb
@ -82,9 +82,13 @@ if Group.count == 0
|
||||
])
|
||||
end
|
||||
|
||||
unless Group.find_by(slug: 'admins')
|
||||
Group.create! name: I18n.t('group.admins'), slug: 'admins'
|
||||
end
|
||||
|
||||
# Create the default admin if none exists yet
|
||||
if Role.where(name: 'admin').joins(:users).count === 0
|
||||
admin = User.new(username: 'admin', email: ENV["ADMIN_EMAIL"], password: ENV["ADMIN_PASSWORD"], password_confirmation: Rails.application.secrets.admin_password, group_id: Group.first.id, profile_attributes: {first_name: 'admin', last_name: 'admin', gender: true, phone: '0123456789', birthday: Time.now})
|
||||
admin = User.new(username: 'admin', email: ENV["ADMIN_EMAIL"], password: ENV["ADMIN_PASSWORD"], password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id, profile_attributes: {first_name: 'admin', last_name: 'admin', gender: true, phone: '0123456789', birthday: Time.now})
|
||||
admin.add_role 'admin'
|
||||
admin.save!
|
||||
end
|
||||
@ -397,6 +401,18 @@ unless Setting.find_by(name: 'reminder_delay').try(:value)
|
||||
setting.save
|
||||
end
|
||||
|
||||
unless Setting.find_by(name: 'visibility_yearly').try(:value)
|
||||
setting = Setting.find_or_initialize_by(name: 'visibility_yearly')
|
||||
setting.value = '3'
|
||||
setting.save
|
||||
end
|
||||
|
||||
unless Setting.find_by(name: 'visibility_others').try(:value)
|
||||
setting = Setting.find_or_initialize_by(name: 'visibility_others')
|
||||
setting.value = '1'
|
||||
setting.save
|
||||
end
|
||||
|
||||
if StatisticCustomAggregation.count == 0
|
||||
# available reservations hours for machines
|
||||
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: 2)
|
||||
|
13
doc/archlinux_readme.md
Normal file
13
doc/archlinux_readme.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Specific instructions concerning installation on ArchLinux
|
||||
|
||||
## Ruby 2.3.0
|
||||
Ruby 2.3.0 has a known issue with openSSL version > 1.0 (which is ArchLinux default).
|
||||
To overpass this problem, you must install ruby with special indication of the openSSL installation to use.
|
||||
|
||||
```bash
|
||||
sudo pacman -S gcc5
|
||||
rvm pkg install openssl
|
||||
CC=gcc-5 rvm install 2.3.0 -C --with-openssl-dir=$HOME/.rvm/usr
|
||||
```
|
||||
|
||||
There's also an issue with openSSL and `puma` but this is fixed by using puma version > 3.
|
@ -47,5 +47,31 @@ namespace :fablab do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
task migrate_admins_group: :environment do
|
||||
admins = Group.find_by(slug: 'admins')
|
||||
User.all.each do |user|
|
||||
if user.is_admin?
|
||||
user.group = admins
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
task recursive_events_over_DST: :environment do
|
||||
include ApplicationHelper
|
||||
groups = Event.group(:recurrence_id).count
|
||||
groups.keys.each do |recurrent_event_id|
|
||||
initial_event = Event.find(recurrent_event_id)
|
||||
Event.where(recurrence_id: recurrent_event_id).where.not(id: recurrent_event_id).each do |event|
|
||||
availability = event.availability
|
||||
if initial_event.availability.start_at.hour != availability.start_at.hour
|
||||
availability.start_at = dst_correction(initial_event.availability.start_at, availability.start_at)
|
||||
availability.end_at = dst_correction(initial_event.availability.end_at, availability.end_at)
|
||||
availability.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
23
vendor/assets/components/ngCapsLock/.bower.json
vendored
Normal file
23
vendor/assets/components/ngCapsLock/.bower.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "ngCapsLock",
|
||||
"version": "1.0.2",
|
||||
"main": "./ng-caps-lock.js",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
"Fábio Rodrigues <fabio.info@gmail.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular": ">=1.1"
|
||||
},
|
||||
"homepage": "https://github.com/FabioMR/ng-caps-lock",
|
||||
"_release": "1.0.2",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "1.0.2",
|
||||
"commit": "f28192faa10107db6b5b7db7a77109d36b16c0b9"
|
||||
},
|
||||
"_source": "https://github.com/FabioMR/ng-caps-lock.git",
|
||||
"_target": "^1.0.2",
|
||||
"_originalSource": "ng-caps-lock",
|
||||
"_direct": true
|
||||
}
|
21
vendor/assets/components/ngCapsLock/LICENSE
vendored
Normal file
21
vendor/assets/components/ngCapsLock/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 FabioMR
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
25
vendor/assets/components/ngCapsLock/README.md
vendored
Normal file
25
vendor/assets/components/ngCapsLock/README.md
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# ngCapsLock
|
||||
|
||||
ngCapsLock is a module for [AngularJS](http://angularjs.org/) to detect if caps-lock is on/off.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
* Download ngCapsLock or install it with [Bower](http://bower.io/) via `bower install ng-caps-lock`
|
||||
* Include the script tag on your page after the AngularJS script tags
|
||||
|
||||
<script type='text/javascript' src='path/to/angular.min.js'></script>
|
||||
<script type='text/javascript' src='path/to/ng-caps-lock.min.js'></script>
|
||||
|
||||
* Ensure that your application module specifies `ngCapsLock` as a dependency:
|
||||
|
||||
angular.module('myApp', ['ngCapsLock']);
|
||||
|
||||
* Use the property `isCapsLockOn` on a `ng-show` directive.
|
||||
|
||||
<p class="caps-lock-alert" ng-show='isCapsLockOn'>Caps lock is on</p>
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
ngCapsLock is licensed under the MIT license. See the LICENSE file for more details.
|
13
vendor/assets/components/ngCapsLock/bower.json
vendored
Normal file
13
vendor/assets/components/ngCapsLock/bower.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "ngCapsLock",
|
||||
"version": "1.0.2",
|
||||
"main": "./ng-caps-lock.js",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
"Fábio Rodrigues <fabio.info@gmail.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular": ">=1.1"
|
||||
},
|
||||
"homepage": "https://github.com/FabioMR/ng-caps-lock"
|
||||
}
|
61
vendor/assets/components/ngCapsLock/ng-caps-lock.js
vendored
Normal file
61
vendor/assets/components/ngCapsLock/ng-caps-lock.js
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular.module('ngCapsLock', []).run(['$rootScope', '$document', '$timeout', function ($rootScope, $document, $timeout) {
|
||||
|
||||
var bindingForAppleDevice = function () {
|
||||
$document.bind("keydown", function (event) {
|
||||
if (event.keyCode === 20) { setCapsLockOn(true); }
|
||||
});
|
||||
|
||||
$document.bind("keyup", function (event) {
|
||||
if (event.keyCode === 20) { setCapsLockOn(false); }
|
||||
});
|
||||
|
||||
$document.bind("keypress", function (event) {
|
||||
var code = event.charCode || event.keyCode;
|
||||
var shift = event.shiftKey;
|
||||
|
||||
if (code > 96 && code < 123) { setCapsLockOn(false); }
|
||||
if (code > 64 && code < 91 && !shift) { setCapsLockOn(true); }
|
||||
});
|
||||
};
|
||||
|
||||
var bindingForOthersDevices = function () {
|
||||
var isKeyPressed = true;
|
||||
|
||||
$document.bind("keydown", function (event) {
|
||||
if (!isKeyPressed && event.keyCode === 20) {
|
||||
isKeyPressed = true;
|
||||
if ($rootScope.isCapsLockOn != null) { setCapsLockOn(!$rootScope.isCapsLockOn); }
|
||||
}
|
||||
});
|
||||
|
||||
$document.bind("keyup", function (event) {
|
||||
if (event.keyCode === 20) { isKeyPressed = false; }
|
||||
});
|
||||
|
||||
$document.bind("keypress", function (event) {
|
||||
var code = event.charCode || event.keyCode;
|
||||
var shift = event.shiftKey;
|
||||
|
||||
if (code > 96 && code < 123) { setCapsLockOn(shift); }
|
||||
if (code > 64 && code < 91) { setCapsLockOn(!shift); }
|
||||
});
|
||||
};
|
||||
|
||||
if (/Mac|iPad|iPhone|iPod/.test(navigator.platform)) {
|
||||
bindingForAppleDevice();
|
||||
} else {
|
||||
bindingForOthersDevices();
|
||||
}
|
||||
|
||||
var setCapsLockOn = function (isOn) {
|
||||
$timeout(function () {
|
||||
$rootScope.isCapsLockOn = isOn;
|
||||
});
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
}());
|
1
vendor/assets/components/ngCapsLock/ng-caps-lock.min.js
vendored
Normal file
1
vendor/assets/components/ngCapsLock/ng-caps-lock.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){"use strict";angular.module("ngCapsLock",[]).run(["$rootScope","$document","$timeout",function(n,o,e){var i=function(){o.bind("keydown",function(n){20===n.keyCode&&c(!0)}),o.bind("keyup",function(n){20===n.keyCode&&c(!1)}),o.bind("keypress",function(n){var o=n.charCode||n.keyCode,e=n.shiftKey;o>96&&123>o&&c(!1),o>64&&91>o&&!e&&c(!0)})},t=function(){var e=!0;o.bind("keydown",function(o){e||20!==o.keyCode||(e=!0,null!=n.isCapsLockOn&&c(!n.isCapsLockOn))}),o.bind("keyup",function(n){20===n.keyCode&&(e=!1)}),o.bind("keypress",function(n){var o=n.charCode||n.keyCode,e=n.shiftKey;o>96&&123>o&&c(e),o>64&&91>o&&c(!e)})};/Mac|iPad|iPhone|iPod/.test(navigator.platform)?i():t();var c=function(o){e(function(){n.isCapsLockOn=o})}}])}();
|
Loading…
Reference in New Issue
Block a user