mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Remove legacy frontend
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
web: bundle exec rails server -p 3000 -b ${HOST:="127.0.0.1"} --environment ${RAILS_ENV:="development"}
|
||||
legacy: cd frontend && RAILS_ENV=${RAILS_ENV:="development"} npm run legacy-webpack-watch
|
||||
angular: npm run serve
|
||||
worker: bundle exec rake jobs:work
|
||||
|
||||
@@ -34,17 +34,4 @@ module AngularHelper
|
||||
options[:class] = options.fetch(:class, '') + ' op-angular-component'
|
||||
tag(component, options)
|
||||
end
|
||||
|
||||
def activate_angular_js(type = :div, options = {}, &block)
|
||||
content_for(:header_tags) do
|
||||
javascript_include_tag 'bundles/openproject-legacy-app'
|
||||
end
|
||||
|
||||
if block_given?
|
||||
merged_options = options.merge('ng-app': 'OpenProjectLegacy')
|
||||
content_tag(type, merged_options, &block)
|
||||
else
|
||||
'ng-app="OpenProjectLegacy"'.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,11 +27,9 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<section data-augmented-model-wrapper
|
||||
data-modal-initialize-now="true"
|
||||
data-modal-class-name="registration-modal modal-wrapper -highlight">
|
||||
<% @user ||= User.new %>
|
||||
<%= render partial: '/account/register' %>
|
||||
</section>
|
||||
<% end %>
|
||||
<section data-augmented-model-wrapper
|
||||
data-modal-initialize-now="true"
|
||||
data-modal-class-name="registration-modal modal-wrapper -highlight">
|
||||
<% @user ||= User.new %>
|
||||
<%= render partial: '/account/register' %>
|
||||
</section>
|
||||
|
||||
@@ -29,7 +29,7 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
|
||||
<% entries = @texts_by_type[tab[:name]] || [] %>
|
||||
<% if entries.any? %>
|
||||
<%= activate_angular_js :div, class: 'generic-table--container' do %>
|
||||
<div class="generic-table--container">
|
||||
<div class="generic-table--results-container">
|
||||
<table class="generic-table">
|
||||
<colgroup>
|
||||
@@ -91,7 +91,7 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= no_results_box %>
|
||||
<% end %>
|
||||
|
||||
@@ -33,38 +33,36 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
<%= toolbar title: l(:label_profile) %>
|
||||
<%= error_messages_for 'user' %>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<%= password_confirmation_form_for @user,
|
||||
as: :user,
|
||||
url: { action: 'update_account' },
|
||||
builder: ::TabularFormBuilder,
|
||||
lang: current_language,
|
||||
html: { id: 'my_account_form', class: '-wide-labels' } do |f| %>
|
||||
<section class="form--section">
|
||||
<div class="form--field">
|
||||
<label class="form--label" for="username">
|
||||
<%= User.human_attribute_name(:login) %>
|
||||
</label>
|
||||
<div class="form--field-container">
|
||||
<%= @user.login %>
|
||||
</div>
|
||||
<%= password_confirmation_form_for @user,
|
||||
as: :user,
|
||||
url: { action: 'update_account' },
|
||||
builder: ::TabularFormBuilder,
|
||||
lang: current_language,
|
||||
html: { id: 'my_account_form', class: '-wide-labels' } do |f| %>
|
||||
<section class="form--section">
|
||||
<div class="form--field">
|
||||
<label class="form--label" for="username">
|
||||
<%= User.human_attribute_name(:login) %>
|
||||
</label>
|
||||
<div class="form--field-container">
|
||||
<%= @user.login %>
|
||||
</div>
|
||||
<div class="form--field -required"><%= f.text_field :firstname, required: true, container_class: '-middle' %></div>
|
||||
<div class="form--field -required"><%= f.text_field :lastname, required: true, container_class: '-middle' %></div>
|
||||
<div class="form--field -required"><%= f.text_field :mail, required: true, container_class: '-middle' %></div>
|
||||
</div>
|
||||
<div class="form--field -required"><%= f.text_field :firstname, required: true, container_class: '-middle' %></div>
|
||||
<div class="form--field -required"><%= f.text_field :lastname, required: true, container_class: '-middle' %></div>
|
||||
<div class="form--field -required"><%= f.text_field :mail, required: true, container_class: '-middle' %></div>
|
||||
|
||||
<%= fields_for :pref, @user.pref, builder: TabularFormBuilder, lang: current_language do |pref_fields| %>
|
||||
<div class="form--field"><%= pref_fields.check_box :hide_mail %></div>
|
||||
<% end %>
|
||||
<%= fields_for :pref, @user.pref, builder: TabularFormBuilder, lang: current_language do |pref_fields| %>
|
||||
<div class="form--field"><%= pref_fields.check_box :hide_mail %></div>
|
||||
<% end %>
|
||||
|
||||
<%= call_hook(:view_my_account, user: @user, form: f) %>
|
||||
<%= call_hook(:view_my_account, user: @user, form: f) %>
|
||||
|
||||
<%= render partial: 'customizable/field',
|
||||
collection: @user.custom_field_values.select(&:editable?),
|
||||
as: :value,
|
||||
locals: { form: f, input_size: :middle } %>
|
||||
</section>
|
||||
<%= render partial: 'customizable/field',
|
||||
collection: @user.custom_field_values.select(&:editable?),
|
||||
as: :value,
|
||||
locals: { form: f, input_size: :middle } %>
|
||||
</section>
|
||||
|
||||
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-checkmark' %>
|
||||
<% end %>
|
||||
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-checkmark' %>
|
||||
<% end %>
|
||||
|
||||
@@ -36,22 +36,21 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
<%= initialize_hide_sections_with [{key: 'notified_projects'}], active_sections %>
|
||||
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<div class="form--field">
|
||||
<%= styled_label_tag "user_mail_notification", t(:'user.settings.mail_notifications') %>
|
||||
<div class="form--field-container">
|
||||
<div class="form--select-container">
|
||||
<show-section-dropdown opt-value="selected" hide-sec-with-name="notified_projects">
|
||||
<%= styled_select_tag 'user[mail_notification]',
|
||||
options_for_select(user_mail_notification_options(@user),
|
||||
@user.mail_notification),
|
||||
container_class: '-wide' %>
|
||||
<%= styled_select_tag 'user[mail_notification]',
|
||||
options_for_select(user_mail_notification_options(@user), @user.mail_notification),
|
||||
container_class: '-wide' %>
|
||||
<show-section-dropdown opt-value="selected"
|
||||
hide-sec-with-name="notified_projects">
|
||||
</show-section-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hide-section section-name="notified_projects">
|
||||
<section class="hide-section" data-section-name="notified_projects">
|
||||
<div id="notified-projects" class="form--field -no-label">
|
||||
<div class="form--field-container -vertical">
|
||||
<% @user.projects.each do |project| %>
|
||||
@@ -68,7 +67,7 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
<%= t(:'user.settings.mail_project_explanaition') %>
|
||||
</div>
|
||||
</div>
|
||||
</hide-section>
|
||||
</section>
|
||||
|
||||
<div class="form--field">
|
||||
<%= styled_label_tag 'self_notified', t(:'user.settings.mail_self_notified') %>
|
||||
@@ -78,4 +77,3 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -28,43 +28,41 @@ See docs/COPYRIGHT.rdoc for more details.
|
||||
++#%>
|
||||
<% html_title(l(:label_administration), "#{ l('account.deletion_info.heading', name: @user.name)}") -%>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<%= labelled_tabular_form_for(
|
||||
:user,
|
||||
url: user_path(@user),
|
||||
html: {
|
||||
method: :delete, class: 'confirm_required form danger-zone',
|
||||
data: password_confirmation_data_attribute
|
||||
}) do %>
|
||||
<div class='wiki'>
|
||||
<section class="form--section">
|
||||
<h3 class="form--section-title">
|
||||
<%= l('account.deletion_info.heading', name: "<em>#{h(@user.name)}</em>").html_safe %>
|
||||
</h3>
|
||||
<%= labelled_tabular_form_for(
|
||||
:user,
|
||||
url: user_path(@user),
|
||||
html: {
|
||||
method: :delete, class: 'confirm_required request-for-confirmation form danger-zone',
|
||||
data: password_confirmation_data_attribute
|
||||
}) do %>
|
||||
<div class='wiki'>
|
||||
<section class="form--section">
|
||||
<h3 class="form--section-title">
|
||||
<%= l('account.deletion_info.heading', name: "<em>#{h(@user.name)}</em>").html_safe %>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<%= l("account.deletion_info.login_consequences.#{User.current == @user ? 'self' : 'other'}") %>
|
||||
</p>
|
||||
<p>
|
||||
<%= l("account.deletion_info.login_consequences.#{User.current == @user ? 'self' : 'other'}") %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%= l("account.deletion_info.data_consequences.#{User.current == @user ? 'self' : 'other'}") %>
|
||||
</p>
|
||||
<p class="danger-zone--warning">
|
||||
<span class="icon icon-error"></span>
|
||||
<span><%= l("account.deletion_info.info.#{User.current == @user ? 'self' : 'other'}") %></span>
|
||||
</p>
|
||||
<p>
|
||||
<%= l("account.deletion_info.login_verification.#{User.current == @user ? 'self' : 'other'}",
|
||||
name: "<em class=\"danger-zone--expected-value\">#{h(@user.login)}</em>").html_safe %>
|
||||
</p>
|
||||
<div class="danger-zone--verification">
|
||||
<input type="text" name="login_verification"/>
|
||||
<%= styled_button_tag '', class: '-highlight', disabled: true do
|
||||
concat content_tag :i, '', class: 'button--icon icon-delete'
|
||||
concat content_tag :span, l(:button_delete), class: 'button--text'
|
||||
end %>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<% end %>
|
||||
<p>
|
||||
<%= l("account.deletion_info.data_consequences.#{User.current == @user ? 'self' : 'other'}") %>
|
||||
</p>
|
||||
<p class="danger-zone--warning">
|
||||
<span class="icon icon-error"></span>
|
||||
<span><%= l("account.deletion_info.info.#{User.current == @user ? 'self' : 'other'}") %></span>
|
||||
</p>
|
||||
<p>
|
||||
<%= l("account.deletion_info.login_verification.#{User.current == @user ? 'self' : 'other'}",
|
||||
name: "<em class=\"danger-zone--expected-value\">#{h(@user.login)}</em>").html_safe %>
|
||||
</p>
|
||||
<div class="danger-zone--verification">
|
||||
<input type="text" name="login_verification"/>
|
||||
<%= styled_button_tag '', class: '-highlight', disabled: true do
|
||||
concat content_tag :i, '', class: 'button--icon icon-delete'
|
||||
concat content_tag :span, l(:button_delete), class: 'button--text'
|
||||
end %>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -22,15 +22,10 @@ try 'npm install --no-shrinkwrap >> log/setup_dev.log'
|
||||
echo "Linking plugin modules"
|
||||
try 'bundle exec rake openproject:plugins:register_frontend >> log/setup_dev.log'
|
||||
|
||||
printf "Building legacy webpack bundle ... "
|
||||
try 'npm run legacy-webpack >> log/setup_dev.log'
|
||||
echo "done."
|
||||
|
||||
echo "---------------------------------------"
|
||||
echo "Done. Now start the following services"
|
||||
echo '- Rails server `RAILS_ENV=development ./bin/rails s`'
|
||||
echo '- Angular CLI: `npm run serve`'
|
||||
echo '- (Optional, only if you work on frontend/legacy): `npm run legacy-webpack-watch`'
|
||||
echo ""
|
||||
echo 'You can also run `foreman start -f Procfile.dev` to run all the above on a single terminal.'
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
API Handling
|
||||
============
|
||||
|
||||
In general, the OpenProject Frontend uses _all_ of the existing working APIs to provide its functionality, as the current working version for `APIv3` is not feature complete.
|
||||
|
||||
The documentation for these APIs and their capabilities:
|
||||
|
||||
- [APIv3](http://opf.github.io/apiv3-doc/)
|
||||
|
||||
To get a feeling for which API is used at which point, please refer to the `PathHelper` located at `./frontend/app/helpers/path-helper.js`. It is used throughout the application to centralize knowledge about paths.
|
||||
|
||||
## HAL
|
||||
|
||||
While having a `PathHelper` certainly helps, the long-term idea is to leverage the [HAL](http://stateless.co/hal_specification.html)-capabilities of the APIv3 (thereby leaving `v2` behind) to let any client discover the paths available for a resource by inspecting the responses from any given call.
|
||||
|
||||
__Note:__ All responses from the APIv3 are thereby of `Content-Type: application/hal+json` and not just `Content-Type: application/json`. Some developer client tools sometimes get confused with that and will not interpret the formatting correctly.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
// calling a project
|
||||
|
||||
{
|
||||
"_type": "Project",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "/api/v3/projects/1",
|
||||
"title": "Lorem"
|
||||
},
|
||||
"createWorkPackage": {
|
||||
"href": "/api/v3/projects/1/work_packages/form",
|
||||
"method": "post"
|
||||
},
|
||||
"createWorkPackageImmediate": {
|
||||
"href": "/api/v3/projects/1/work_packages",
|
||||
"method": "post"
|
||||
},
|
||||
"categories": { "href": "/api/v3/projects/1/categories" },
|
||||
"types": { "href": "/api/v3/projects/1/types" },
|
||||
"versions": { "href": "/api/v3/projects/1/versions" }
|
||||
},
|
||||
"id": 1,
|
||||
"identifier": "project_identifier",
|
||||
"name": "Project example",
|
||||
"description": "Lorem ipsum dolor sit amet"
|
||||
}
|
||||
```
|
||||
|
||||
The `Project` structure contains links to ressources associated. Given the knowledge about `_links`, one may easily infer the path from the response:
|
||||
|
||||
```javascript
|
||||
// some magic to retrieve an object, note that the services used are examplary
|
||||
// and can not be found in the actual codebase
|
||||
ProjectsService.getProject('project_identifier').then(function(project) {
|
||||
var pathToVersions = project._links.versions.href;
|
||||
// the VersionsService has knowledge about pathToVersions in its
|
||||
// forProject method
|
||||
VersionsService.forProject(project).then(function(versions) {
|
||||
// versions should be the result of the call to pathtoVersions
|
||||
console.log(versions);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This is in principle a very good concept to delegate responsibility of inference to the client and absolves the client of having to know each path in the application in advance.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# Additional information on Legacy frontend
|
||||
|
||||
The legacy bundle is only used from Rails to add functionality to specific parts of the application.
|
||||
|
||||
## Loading and bootstrapping the legacy AngularJS frontend
|
||||
|
||||
To bootstrap the AngularJS frontend from Rails, use the `activate_angular_js` helper block:
|
||||
|
||||
```html
|
||||
<!-- @see ./app/helpers/angular_helper.rb -->
|
||||
<%= activate_angular_js do %>
|
||||
<persistent-toggle identifier="repository.checkout_instructions">
|
||||
<div>
|
||||
Something rendered from Rails ...
|
||||
</div>
|
||||
</persistent-toggle>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
The legacy frontend with AngularJS can be bootstrapped _with_ content contained within. This is not possible in Angular,
|
||||
since the root component needs to be empty (or will be emptied during bootstrap).
|
||||
|
||||
## Passing information and configuration from Rails to Angular
|
||||
|
||||
There are three ways of passing information from Rails to `AngularJS`:
|
||||
|
||||
1. Using tag attributes written directly to the DOM by the rendering process of Rails as in the example before.
|
||||
|
||||
2. Using the `gon` gem
|
||||
|
||||
This is included by all layouts in `<head>`:
|
||||
|
||||
```js
|
||||
<%= nonced_javascript_tag do %>
|
||||
<%= include_gon(need_tag: false) -%>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
`gon` will provide arbitrary settings from Rails to all JavaScript functionality, including `AngularJS`. In an `angular` context a `ConfigurationService` is provided for picking up the settings.
|
||||
@@ -7,16 +7,6 @@ from the previous frontend that cannot be converted to Angular. (Mainly because
|
||||
- **The Angular frontend** is located at `frontend/src` and uses the Angular CLI to compile and serve locally.
|
||||
|
||||
|
||||
## Legacy frontend
|
||||
|
||||
When developing, the legacy bundle can be watched with `npm run legacy-webpack-watch` in separate tab.
|
||||
It will result in a single output bundle at `app/assets/javascripts/bundles/openproject-legacy-app.js`.
|
||||
That bundle is loaded manually by templates in Rails whenever a legacy directive is used with `<%= activate_angular_js %>`.
|
||||
|
||||
For production, this bundle is also produced when running the `rake assets:precompile` task.
|
||||
|
||||
For more information, see [LEGACY](./LEGACY.md).
|
||||
|
||||
## Angular frontend
|
||||
|
||||
When developing, `npm run serve` will open a proxy server (webpack-dev-server) that will serve assets from memory.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# OpenProject Legacy AngularJS Frontend
|
||||
|
||||
These directives are remains of the AngularJS frontend used solely for the purpose of
|
||||
retaining functionality of embedded components in Rails templates.
|
||||
@@ -1,94 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
|
||||
import {ExpressionService} from "../../common/expression.service";
|
||||
require('angular');
|
||||
|
||||
var angularDragula:any = require('angular-dragula');
|
||||
export const opTemplatesModule = angular.module('openproject.templates', []);
|
||||
export const openprojectLegacyModule = angular.module('OpenProjectLegacy', [
|
||||
angularDragula(angular)
|
||||
]);
|
||||
|
||||
// Bootstrap app
|
||||
|
||||
openprojectLegacyModule
|
||||
.config([
|
||||
'$compileProvider',
|
||||
'$httpProvider',
|
||||
function($compileProvider:any, $httpProvider:any) {
|
||||
|
||||
// Disable debugInfo outside development mode
|
||||
$compileProvider.debugInfoEnabled(window.OpenProject.environment === 'development');
|
||||
|
||||
$httpProvider.defaults.headers.common['X-CSRF-TOKEN'] = jQuery(
|
||||
'meta[name=csrf-token]').attr('content');
|
||||
$httpProvider.defaults.headers.common['X-Authentication-Scheme'] = 'Session';
|
||||
// Add X-Requested-With for request.xhr?
|
||||
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
// prepend a given base path to requests performed via $http
|
||||
//
|
||||
$httpProvider.interceptors.push(function($q:ng.IQService) {
|
||||
return {
|
||||
'request': function(config:any) {
|
||||
// OpenProject can run in a subpath e.g. https://mydomain/open_project.
|
||||
// We append the path found as the base-tag value to all http requests
|
||||
// to the server except:
|
||||
// * when the path is already appended
|
||||
// * when we are getting a template
|
||||
if (!config.url.match('(^/templates|\\.html$|^' + window.appBasePath + ')')) {
|
||||
config.url = window.appBasePath + (config.url as string);
|
||||
}
|
||||
|
||||
return config || $q.when(config);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
])
|
||||
.run([
|
||||
'$rootScope',
|
||||
function($rootScope:any) {
|
||||
// Set the escaping target of opening double curly braces
|
||||
// This is what returned by rails-angular-xss when it discoveres double open curly braces
|
||||
// See https://github.com/opf/rails-angular-xss for more information.
|
||||
$rootScope.DOUBLE_LEFT_CURLY_BRACE = ExpressionService.UNESCAPED_EXPRESSION;
|
||||
|
||||
// Mark the bootstrap has run for testing purposes.
|
||||
document.body.classList.add('__ng-bootstrap-has-run');
|
||||
|
||||
$rootScope.$on('$stateChangeError',
|
||||
function(event:JQueryEventObject){
|
||||
event.preventDefault();
|
||||
// transitionTo() promise will be rejected with
|
||||
// a 'transition prevented' error
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var _ = require('lodash');
|
||||
var autoprefixer = require('autoprefixer');
|
||||
|
||||
var browsersListConfig = fs.readFileSync(path.join(__dirname, '..', 'browserslist'), 'utf8');
|
||||
var browsersList = _.filter(browsersListConfig.split('\n'), function (entry) {
|
||||
return entry && entry.charAt(0) !== '#';
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
autoprefixer({
|
||||
browsers: browsersList, cascade: false
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"noEmitOnError": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
// Increase strictness
|
||||
// Enable strict once "use strict" errors can be resolved
|
||||
// "strict": true;
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictFunctionTypes": true,
|
||||
// Causes lots of errors in linked angularjs properties
|
||||
"strictPropertyInitialization": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"preserveSymlinks": true,
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"../node_modules/@types",
|
||||
"./typings/open-project-legacy.typings.d.ts"
|
||||
],
|
||||
"paths": {
|
||||
"core-app/*": ["./app/*"],
|
||||
"core-components/*": ["./app/components/*"]
|
||||
}
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
|
||||
|
||||
/// <reference path="../../node_modules/@types/angular/index.d.ts" />
|
||||
/// <reference path="../../node_modules/@types/lodash/index.d.ts" />
|
||||
|
||||
import * as TAngular from 'angular';
|
||||
import * as TLodash from 'lodash';
|
||||
import {InputState, State} from "reactivestates";
|
||||
import {GlobalI18n} from "../../src/app/modules/common/i18n/i18n.service";
|
||||
|
||||
export interface IPluginContext {
|
||||
classes:any;
|
||||
services:any;
|
||||
bootstrap(element:HTMLElement):void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
appBasePath:string;
|
||||
OpenProject:{
|
||||
guardedLocalStorage(key:string, newValue?:string):string|void,
|
||||
environment:string,
|
||||
getPluginContext():Promise<IPluginContext>,
|
||||
pluginContext:InputState<IPluginContext>
|
||||
};
|
||||
}
|
||||
const angular:typeof TAngular;
|
||||
const _:typeof TLodash;
|
||||
const I18n:GlobalI18n;
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
var _ = require('lodash');
|
||||
//var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
var CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
var mode = 'production';
|
||||
var production = true;
|
||||
if (process.env['RAILS_ENV'] == 'development') {
|
||||
mode = 'development';
|
||||
production = false;
|
||||
}
|
||||
|
||||
var debug_output = (!production || !!process.env['OP_FRONTEND_DEBUG_OUTPUT']);
|
||||
|
||||
var node_root = path.resolve(__dirname, '..', 'node_modules');
|
||||
var output_root = path.resolve(__dirname, '..', '..', 'app', 'assets', 'javascripts');
|
||||
var bundle_output = path.resolve(output_root, 'bundles');
|
||||
|
||||
var loaders = [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
logLevel: 'info',
|
||||
configFile: path.resolve(__dirname, 'tsconfig.json')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'postcss-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.png$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: '100000',
|
||||
mimetype: 'image/png'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.gif$/,
|
||||
use: ['file-loader']
|
||||
},
|
||||
{
|
||||
test: /\.jpg$/,
|
||||
use: ['file-loader']
|
||||
},
|
||||
];
|
||||
|
||||
loaders.push({
|
||||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ngtemplate-loader',
|
||||
options: {
|
||||
module: 'OpenProjectLegacy',
|
||||
relativeTo: path.resolve(__dirname, './app')
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
minimize: false
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
function getLegacyWebpackConfig() {
|
||||
var config = {
|
||||
mode: mode,
|
||||
|
||||
devtool: 'source-map',
|
||||
|
||||
context: path.resolve(__dirname, 'app'),
|
||||
|
||||
entry: {
|
||||
'legacy-app': './openproject-legacy-app'
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: 'openproject-[name].js',
|
||||
path: bundle_output,
|
||||
publicPath: '/assets/bundles/'
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: loaders
|
||||
},
|
||||
|
||||
resolve: {
|
||||
// Don't map symlinks from dynamically linked plugins to their real paths
|
||||
symlinks: false,
|
||||
|
||||
modules: [
|
||||
path.resolve(__dirname, '..', 'node_modules')
|
||||
],
|
||||
|
||||
extensions: ['.ts', '.tsx', '.js'],
|
||||
|
||||
// Allow empty import without extension
|
||||
// enforceExtension: true,
|
||||
|
||||
alias: {
|
||||
'angular': path.resolve(node_root, 'angular', 'angular.min.js'),
|
||||
'angular-dragula': path.resolve(node_root, 'angular-dragula', 'dist', 'angular-dragula.min.js'),
|
||||
'core-app': path.resolve(__dirname, 'app'),
|
||||
'core-components': path.resolve(__dirname, 'app', 'components'),
|
||||
}
|
||||
},
|
||||
|
||||
externals: {
|
||||
"I18n": "I18n",
|
||||
"_": "_",
|
||||
},
|
||||
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
common: {
|
||||
name: "common",
|
||||
chunks: "initial",
|
||||
minChunks: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [
|
||||
// Define modes for debug output
|
||||
new webpack.DefinePlugin({
|
||||
DEBUG: !!debug_output,
|
||||
PRODUCTION: !!production
|
||||
}),
|
||||
|
||||
// Clean the output directory
|
||||
new CleanWebpackPlugin(['bundles'], {
|
||||
root: output_root,
|
||||
verbose: true,
|
||||
exclude: ['openproject-vendors.js']
|
||||
}),
|
||||
|
||||
// new BundleAnalyzerPlugin(),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: "openproject-[name].css",
|
||||
chunkFilename: "[id].css"
|
||||
}),
|
||||
]
|
||||
};
|
||||
|
||||
if (production) {
|
||||
console.log("Applying webpack.optimize plugins for production.");
|
||||
// Add compression and optimization plugins
|
||||
// to the webpack build.
|
||||
config.plugins.push(
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
// Let loaders know that we're in minification mode
|
||||
minimize: true
|
||||
})
|
||||
);
|
||||
|
||||
config.optimization.minimizer = [
|
||||
// we specify a custom UglifyJsPlugin here to get source maps in production
|
||||
new UglifyJsPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
uglifyOptions: {
|
||||
compress: true,
|
||||
mangle: false,
|
||||
ecma: 5,
|
||||
},
|
||||
sourceMap: true
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = getLegacyWebpackConfig;
|
||||
@@ -134,14 +134,12 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "./scripts/link_plugin_placeholder.js",
|
||||
"build": "ng build --prod && npm run legacy-webpack",
|
||||
"build": "ng build --prod",
|
||||
"preserve": "./scripts/link_plugin_placeholder.js",
|
||||
"serve": "node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --public-host http://localhost:4200",
|
||||
"pretest": "./scripts/link_plugin_placeholder.js",
|
||||
"test": "ng test --watch=false",
|
||||
"tslint_typechecks": "./node_modules/.bin/tslint -p . -c tslint_typechecks.json",
|
||||
"generate-typings": "tsc -d -p src/tsconfig.app.json",
|
||||
"legacy-webpack": "./node_modules/.bin/webpack --colors --config legacy/webpack.config.js",
|
||||
"legacy-webpack-watch": "RAILS_ENV=development ./node_modules/.bin/webpack --config legacy/webpack.config.js --display-error-details --watch --colors --cache --debug"
|
||||
"generate-typings": "tsc -d -p src/tsconfig.app.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ export class WorkPackageNotificationService {
|
||||
public retrieveError(response:unknown):ErrorResource|unknown {
|
||||
// we try to detect what we got, this may either be an HttpErrorResponse,
|
||||
// some older XHR response object or a string
|
||||
let errorBody:any;
|
||||
let errorBody:any = response;
|
||||
|
||||
// Angular http response have an error body attribute
|
||||
if (response instanceof HttpErrorResponse) {
|
||||
@@ -131,7 +131,7 @@ export class WorkPackageNotificationService {
|
||||
return this.halResourceService.createHalResourceOfClass(ErrorResource, errorBody);
|
||||
}
|
||||
|
||||
return response;
|
||||
return errorBody;
|
||||
}
|
||||
|
||||
protected handleErrorResponse(errorResource:any, workPackage?:WorkPackageResource) {
|
||||
|
||||
@@ -1,61 +1,56 @@
|
||||
// //-- copyright
|
||||
// // OpenProject is a project management system.
|
||||
// // Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
||||
// //
|
||||
// // This program is free software; you can redistribute it and/or
|
||||
// // modify it under the terms of the GNU General Public License version 3.
|
||||
// //
|
||||
// // OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// // Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// // Copyright (C) 2010-2013 the ChiliProject Team
|
||||
// //
|
||||
// // This program is free software; you can redistribute it and/or
|
||||
// // modify it under the terms of the GNU General Public License
|
||||
// // as published by the Free Software Foundation; either version 2
|
||||
// // of the License, or (at your option) any later version.
|
||||
// //
|
||||
// // This program is distributed in the hope that it will be useful,
|
||||
// // but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// // GNU General Public License for more details.
|
||||
// //
|
||||
// // You should have received a copy of the GNU General Public License
|
||||
// // along with this program; if not, write to the Free Software
|
||||
// // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
// //
|
||||
// // See docs/COPYRIGHT.rdoc for more details.
|
||||
// //++
|
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// import {HideSectionService} from "./hide-section.service";
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// export class ShowSectionDropdownComponent {
|
||||
// public optValue:string; // value of option for which hide-section should be visible
|
||||
// public hideSecWithName:string; // section-name of hide-section
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// constructor(protected HideSectionService:HideSectionService,
|
||||
// private $element:ng.IAugmentedJQuery) {
|
||||
// }
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// $onInit() {
|
||||
// this.$element.change(event => {
|
||||
// let selectedOption = jQuery("option:selected", event.target);
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// if (selectedOption.val() !== this.optValue) {
|
||||
// this.HideSectionService.hide(this.hideSecWithName);
|
||||
// }
|
||||
// else {
|
||||
// this.HideSectionService.show({key: this.hideSecWithName, label: ""});
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// openprojectLegacyModule.component('showSectionDropdown', {
|
||||
// template: '<ng-transclude></ng-transclude>',
|
||||
// transclude: true,
|
||||
// controller: ShowSectionDropdownComponent,
|
||||
// bindings: {
|
||||
// optValue: "@",
|
||||
// hideSecWithName: "@"
|
||||
// }
|
||||
// });
|
||||
// See docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {HideSectionService} from "./hide-section.service";
|
||||
import {Component, ElementRef, OnInit} from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: 'show-section-dropdown',
|
||||
template: ''
|
||||
})
|
||||
export class ShowSectionDropdownComponent implements OnInit {
|
||||
public optValue:string; // value of option for which hide-section should be visible
|
||||
public hideSecWithName:string; // section-name of hide-section
|
||||
|
||||
constructor(private HideSectionService:HideSectionService,
|
||||
private elementRef:ElementRef) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const target = jQuery(this.elementRef.nativeElement).prev();
|
||||
target.on('change', event => {
|
||||
let selectedOption = jQuery("option:selected", event.target);
|
||||
|
||||
if (selectedOption.val() !== this.optValue) {
|
||||
this.HideSectionService.hide(this.hideSecWithName);
|
||||
} else {
|
||||
this.HideSectionService.show(this.hideSecWithName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ import {HideSectionLinkComponent} from "core-app/modules/common/hide-section/hid
|
||||
import {HideSectionService} from "core-app/modules/common/hide-section/hide-section.service";
|
||||
import {RemoteFieldUpdaterComponent} from 'core-app/modules/common/remote-field-updater/remote-field-updater.component';
|
||||
import {AutofocusDirective} from "core-app/modules/common/autofocus/autofocus.directive";
|
||||
import {ShowSectionDropdownComponent} from "core-app/modules/common/hide-section/show-section-dropdown.component";
|
||||
|
||||
export function bootstrapModule(injector:Injector) {
|
||||
return () => {
|
||||
@@ -242,6 +243,7 @@ export function bootstrapModule(injector:Injector) {
|
||||
PersistentToggleComponent,
|
||||
AutocompleteSelectDecorationComponent,
|
||||
HideSectionLinkComponent,
|
||||
ShowSectionDropdownComponent,
|
||||
AddSectionDropdownComponent,
|
||||
RemoteFieldUpdaterComponent,
|
||||
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ export class RemoteFieldUpdaterComponent implements OnInit {
|
||||
// special cases where the tab code is not correctly recognized (undefined).
|
||||
// Thus the focus is kept on the first element of the result list.
|
||||
let keyCodesArray = [keyCodes.TAB, keyCodes.ENTER, keyCodes.SHIFT];
|
||||
if (event.which && keyCodesArray.indexOf(event.which) === -1) {
|
||||
if (event.type === 'change' || (event.which && keyCodesArray.indexOf(event.which) === -1)) {
|
||||
this.updater();
|
||||
}
|
||||
}, 500));
|
||||
@@ -106,7 +106,7 @@ export class RemoteFieldUpdaterComponent implements OnInit {
|
||||
// Replace the given target
|
||||
this.target.html(response);
|
||||
} else {
|
||||
_.each(response.data, (val:string, selector:string) => {
|
||||
_.each(response, (val:string, selector:string) => {
|
||||
jQuery('#' + selector).html(val);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,3 +31,7 @@ table.list.members
|
||||
|
||||
.progress-bar .inner-progress.done
|
||||
background-color: #E1B9B9
|
||||
|
||||
.budget-row-template,
|
||||
.subform-row-template
|
||||
display: none
|
||||
|
||||
@@ -187,7 +187,7 @@ class CostObjectsController < ApplicationController
|
||||
cost_type = CostType.where(id: params[:cost_type_id]).first
|
||||
|
||||
if cost_type && params[:units].present?
|
||||
volume = BigDecimal.new(Rate.clean_currency(params[:units])) rescue 0.0
|
||||
volume = BigDecimal(Rate.clean_currency(params[:units])) rescue 0.0
|
||||
@costs = (volume * cost_type.rate_at(params[:fixed_date]).rate rescue 0.0)
|
||||
@unit = volume == 1.0 ? cost_type.unit : cost_type.unit_plural
|
||||
else
|
||||
|
||||
@@ -49,7 +49,7 @@ module CostlogHelper
|
||||
value = value.strip
|
||||
value.gsub!(t(:currency_delimiter), '') if value.include?(t(:currency_delimiter)) && value.include?(t(:currency_separator))
|
||||
value.gsub(',', '.')
|
||||
BigDecimal.new(value)
|
||||
BigDecimal(value)
|
||||
end
|
||||
|
||||
def to_currency_with_empty(rate)
|
||||
|
||||
@@ -52,11 +52,11 @@ class VariableCostObject < CostObject
|
||||
end
|
||||
|
||||
def material_budget
|
||||
@material_budget ||= material_budget_items.visible_costs.inject(BigDecimal.new('0.0000')) { |sum, i| sum += i.costs }
|
||||
@material_budget ||= material_budget_items.visible_costs.inject(BigDecimal('0.0000')) { |sum, i| sum += i.costs }
|
||||
end
|
||||
|
||||
def labor_budget
|
||||
@labor_budget ||= labor_budget_items.visible_costs.inject(BigDecimal.new('0.0000')) { |sum, i| sum += i.costs }
|
||||
@labor_budget ||= labor_budget_items.visible_costs.inject(BigDecimal('0.0000')) { |sum, i| sum += i.costs }
|
||||
end
|
||||
|
||||
def spent
|
||||
@@ -66,7 +66,7 @@ class VariableCostObject < CostObject
|
||||
def spent_material
|
||||
@spent_material ||= begin
|
||||
if cost_entries.blank?
|
||||
BigDecimal.new('0.0000')
|
||||
BigDecimal('0.0000')
|
||||
else
|
||||
cost_entries.visible_costs(User.current, project).sum("CASE
|
||||
WHEN #{CostEntry.table_name}.overridden_costs IS NULL THEN
|
||||
@@ -80,7 +80,7 @@ class VariableCostObject < CostObject
|
||||
def spent_labor
|
||||
@spent_labor ||= begin
|
||||
if time_entries.blank?
|
||||
BigDecimal.new('0.0000')
|
||||
BigDecimal('0.0000')
|
||||
else
|
||||
time_entries.visible_costs(User.current, project).sum("CASE
|
||||
WHEN #{TimeEntry.table_name}.overridden_costs IS NULL THEN
|
||||
|
||||
@@ -36,10 +36,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
</div>
|
||||
|
||||
<% if @cost_object.kind == "VariableCostObject" -%>
|
||||
<%= activate_angular_js do %>
|
||||
<%= render partial: 'cost_objects/subform/material_budget_subform' %>
|
||||
<%= render partial: 'cost_objects/subform/labor_budget_subform' %>
|
||||
<% end %>
|
||||
<%- end %>
|
||||
|
||||
<div style="clear: both;"> </div>
|
||||
|
||||
@@ -74,7 +74,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% total_budget = BigDecimal.new("0"); labor_budget = BigDecimal.new("0"); material_budget = BigDecimal.new("0"); spent = BigDecimal.new("0") %>
|
||||
<% total_budget = BigDecimal("0"); labor_budget = BigDecimal("0"); material_budget = BigDecimal("0"); spent = BigDecimal("0") %>
|
||||
<% cost_objects.each do |cost_object| %>
|
||||
<tr id="cost_object-<%= cost_object.id %>" class="<%= cost_object.css_classes %>">
|
||||
<td><%= link_to cost_object.id, cost_object_path(cost_object.id) %></td>
|
||||
|
||||
@@ -75,7 +75,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
<%= cost_form.hidden_field :budget, value: material_budget_item.budget %>
|
||||
<% end %>
|
||||
<cost-unit-subform obj-id="<%= obj_id %>" obj-name="<%= "#{name_prefix}[budget]" %>">
|
||||
<a id="<%= obj_id %>" class="costs--edit-planned-costs-btn" role="button" class="icon-context icon-edit" title="<%= t(:help_click_to_edit) %>">
|
||||
<a id="<%= obj_id %>" class="costs--edit-planned-costs-btn icon-context icon-edit" role="button" title="<%= t(:help_click_to_edit) %>">
|
||||
<%= number_to_currency(material_budget_item.budget || material_budget_item.calculated_costs(@cost_object.fixed_date)) %>
|
||||
</a>
|
||||
</cost-unit-subform>
|
||||
|
||||
@@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
<% template_object = @cost_object.material_budget_items.build(cost_type: CostType.default) %>
|
||||
|
||||
<costs-budget-subform item-count="<%= @cost_object.material_budget_items.length %>"
|
||||
update-url="<%= url_for(action: :update_material_budget_item, project_id: @project.id) %>">
|
||||
update-url="<%= url_for(action: :update_material_budget_item, project_id: @project.id) %>">
|
||||
<fieldset id="material_budget_items_fieldset" class="form--fieldset -collapsible">
|
||||
<legend class="form--fieldset-legend"><%= VariableCostObject.human_attribute_name(:material_budget) %></legend>
|
||||
<div class="generic-table--container">
|
||||
|
||||
@@ -31,7 +31,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
<%= toolbar title: CostType.model_name.human %>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<costs-subform item-count="<%= @cost_type.rates.length %>">
|
||||
<%= labelled_tabular_form_for @cost_type do |f| %>
|
||||
<%= error_messages_for 'cost_type' %>
|
||||
@@ -112,4 +111,3 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
</div>
|
||||
<% end %>
|
||||
</costs-subform>
|
||||
<% end %>
|
||||
|
||||
@@ -89,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
<% if @cost_entry.cost_type.nil? %>
|
||||
<%= f.text_field :units, size: 6, required: true, container_class: '-slim' %>
|
||||
<% else %>
|
||||
<% suffix = @cost_enngtry.units == 1 ? @cost_entry.cost_type.unit : @cost_entry.cost_type.unit_plural %>
|
||||
<% suffix = @cost_entry.units == 1 ? @cost_entry.cost_type.unit : @cost_entry.cost_type.unit_plural %>
|
||||
<%= f.text_field :units,
|
||||
size: 6,
|
||||
required: true,
|
||||
@@ -116,7 +116,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
<br /><em><%= t(:help_override_rate) %></em>
|
||||
<% end %>
|
||||
</cost-unit-subform>
|
||||
</span>ng
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form--field">
|
||||
|
||||
@@ -29,7 +29,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
<p><strong><%= t(:label_current_default_rate) %>:</strong> <%= number_to_currency(default_rate.rate)%></p>
|
||||
<% end %>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<costs-subform item-count="<%= @rates.count %>">
|
||||
<%= labelled_tabular_form_for @user, url: {action: 'update', project_id: @project}, method: :put do |f| %>
|
||||
<%= back_url_hidden_field_tag %>
|
||||
@@ -90,4 +89,3 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
</div>
|
||||
<% end %>
|
||||
</costs-subform>
|
||||
<% end %>
|
||||
|
||||
@@ -22,8 +22,8 @@ OpenProject::Application.routes.draw do
|
||||
resources :cost_entries, controller: 'costlog', only: [:new, :create]
|
||||
|
||||
resources :cost_objects, only: [:new, :create, :index] do
|
||||
get :update_labor_budget_item, on: :collection
|
||||
get :update_material_budget_item, on: :collection
|
||||
match :update_labor_budget_item, on: :collection, via: %i[get post]
|
||||
match :update_material_budget_item, on: :collection, via: %i[get post]
|
||||
end
|
||||
|
||||
resources :hourly_rates, only: [:show, :edit, :update] do
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {PluginContextService} from "core-app/services/plugin-context.service";
|
||||
|
||||
/*eslint no-eval: "error"*/
|
||||
export class CostBudgetSubformController {
|
||||
|
||||
// Container for rows
|
||||
private container: ng.IAugmentedJQuery;
|
||||
|
||||
// Template for new rows to insert, is rendered with INDEX placeholder
|
||||
private rowTemplate: string;
|
||||
|
||||
// Current row index
|
||||
public rowIndex: number;
|
||||
|
||||
// subform item count as output by rails
|
||||
public itemCount: string;
|
||||
|
||||
// Updater URL for the rows contained here
|
||||
public updateUrl: string;
|
||||
|
||||
constructor(public $element:ng.IAugmentedJQuery,
|
||||
public $http:ng.IHttpService,
|
||||
public pluginContext:PluginContextService,
|
||||
private $scope:ng.IScope,
|
||||
private $compile:any) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function costsBudgetSubform():any {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
updateUrl: '@',
|
||||
itemCount: '@'
|
||||
},
|
||||
link: (scope:ng.IScope,
|
||||
element:ng.IAugmentedJQuery,
|
||||
attr:ng.IAttributes,
|
||||
ctrl:any) => {
|
||||
const template = element.find('.budget-row-template');
|
||||
ctrl.rowTemplate = template[0].outerHTML;
|
||||
template.remove();
|
||||
},
|
||||
bindToController: true,
|
||||
controller: CostBudgetSubformController,
|
||||
controllerAs: '$ctrl'
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('OpenProjectLegacy').directive('costsBudgetSubform', costsBudgetSubform);
|
||||
@@ -31,15 +31,20 @@ import {HttpClient} from '@angular/common/http';
|
||||
import {WorkPackageNotificationService} from "core-app/components/wp-edit/wp-notification.service";
|
||||
|
||||
@Injectable()
|
||||
export class CostSubformAugmentService {
|
||||
export class CostBudgetSubformAugmentService {
|
||||
|
||||
constructor(private wpNotifications:WorkPackageNotificationService,
|
||||
private http:HttpClient) {
|
||||
}
|
||||
|
||||
listen() {
|
||||
jQuery('costs-budget-subform').each((i, match) => {
|
||||
let el = jQuery(match);
|
||||
|
||||
const container = el.find('.budget-item-container');
|
||||
const template:string = el.find('.budget-row-template')[0].outerHTML;
|
||||
const templateEl = el.find('.budget-row-template');
|
||||
templateEl.detach();
|
||||
const template = templateEl[0].outerHTML;
|
||||
let rowIndex = parseInt(el.attr('item-count') as string);
|
||||
|
||||
// Refresh row on changes
|
||||
@@ -57,7 +62,10 @@ export class CostSubformAugmentService {
|
||||
// Add new row handler
|
||||
el.find('.budget-add-row').click((evt) => {
|
||||
evt.preventDefault();
|
||||
container.append(template.replace(/INDEX/g, rowIndex.toString()));
|
||||
let row = jQuery(template.replace(/INDEX/g, rowIndex.toString()));
|
||||
row.show();
|
||||
row.removeClass('budget-row-template');
|
||||
container.append(row);
|
||||
rowIndex += 1;
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -36,7 +36,10 @@ export class CostSubformAugmentService {
|
||||
let el = jQuery(match);
|
||||
|
||||
const container = el.find('.subform-container');
|
||||
const template = el.find('.subform-row-template')[0].outerHTML;
|
||||
|
||||
const templateEl = el.find('.subform-row-template');
|
||||
templateEl.detach();
|
||||
const template = templateEl[0].outerHTML;
|
||||
let rowIndex = parseInt(el.attr('item-count')!);
|
||||
|
||||
el.on('click', '.delete-row-button,.delete-budget-item', (evt:any) => {
|
||||
@@ -47,7 +50,10 @@ export class CostSubformAugmentService {
|
||||
// Add new row handler
|
||||
el.find('.add-row-button,.wp-inline-create--add-link').click((evt:any) => {
|
||||
evt.preventDefault();
|
||||
container.append(template.replace(/INDEX/g, rowIndex.toString()));
|
||||
let row = jQuery(template.replace(/INDEX/g, rowIndex.toString()));
|
||||
row.show();
|
||||
row.removeClass('subform-row-template');
|
||||
container.append(row);
|
||||
rowIndex += 1;
|
||||
|
||||
container.find('.costs-date-picker').datepicker();
|
||||
|
||||
@@ -34,7 +34,7 @@ export class PlannedCostsFormAugment {
|
||||
|
||||
static listen() {
|
||||
jQuery(document).on('click', '.costs--edit-planned-costs-btn', (evt) => {
|
||||
const form = jQuery(evt.target).closest('cost-unit-subform');
|
||||
const form = jQuery(evt.target as any).closest('cost-unit-subform') as JQuery;
|
||||
new PlannedCostsFormAugment(form);
|
||||
});
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export class PlannedCostsFormAugment {
|
||||
constructor(public $element:JQuery) {
|
||||
this.objId = this.$element.attr('obj-id')!;
|
||||
this.objName = this.$element.attr('obj-name')!;
|
||||
this.obj = jQuery(this.objId);
|
||||
this.obj = jQuery(`#${this.objId}`) as any;
|
||||
|
||||
this.makeEditable('#' + this.objId, this.objName);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export class PlannedCostsFormAugment {
|
||||
<section class="form--section" id="${id}_section">
|
||||
<div class="form--field">
|
||||
<div class="form--field-container">
|
||||
<div id="${id}_cancel" class="form--field-affix -transparent icon icon-close"></div>';
|
||||
<div id="${id}_cancel" class="form--field-affix -transparent icon icon-close"></div>
|
||||
<div id="${id}_editor" class="form--text-field-container">
|
||||
<input id="${id}_edit" class="form--text-field" name="${name}" value="${value}" class="currency" type="text" />
|
||||
</div>
|
||||
|
||||
@@ -32,8 +32,9 @@ import {BudgetResource} from './hal/resources/budget-resource';
|
||||
import {multiInput} from 'reactivestates';
|
||||
import {CostSubformAugmentService} from "./augment/cost-subform.augment.service";
|
||||
import {PlannedCostsFormAugment} from "core-app/modules/plugins/linked/openproject-costs/augment/planned-costs-form";
|
||||
import {CostBudgetSubformAugmentService} from "core-app/modules/plugins/linked/openproject-costs/augment/cost-budget-subform.augment.service";
|
||||
|
||||
export function initializeCostsPlugin() {
|
||||
export function initializeCostsPlugin(injector:Injector) {
|
||||
return () => {
|
||||
window.OpenProject.getPluginContext().then((pluginContext:OpenProjectPluginContext) => {
|
||||
pluginContext.services.editField.extendFieldType('select', ['Budget']);
|
||||
@@ -78,6 +79,9 @@ export function initializeCostsPlugin() {
|
||||
// Augment previous cost-subforms
|
||||
new CostSubformAugmentService();
|
||||
PlannedCostsFormAugment.listen();
|
||||
|
||||
const budgetSubform = injector.get(CostBudgetSubformAugmentService);
|
||||
budgetSubform.listen();
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -86,6 +90,7 @@ export function initializeCostsPlugin() {
|
||||
@NgModule({
|
||||
providers: [
|
||||
{ provide: APP_INITIALIZER, useFactory: initializeCostsPlugin, deps: [Injector], multi: true },
|
||||
CostBudgetSubformAugmentService,
|
||||
],
|
||||
})
|
||||
export class PluginModule {
|
||||
|
||||
@@ -37,7 +37,6 @@ See doc/COPYRIGHT.md for more details.
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<div id="invisible-grid" class="widget-boxes project-overview">
|
||||
<% top_fields.each do |f| %>
|
||||
<%= rendered_field f %>
|
||||
@@ -48,4 +47,3 @@ See doc/COPYRIGHT.md for more details.
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -73,8 +73,5 @@ See doc/COPYRIGHT.md for more details.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<overview-page-layout>
|
||||
</overview-page-layout>
|
||||
<% end %>
|
||||
<overview-page-layout></overview-page-layout>
|
||||
|
||||
|
||||
@@ -18,23 +18,21 @@
|
||||
#++
|
||||
|
||||
module ReportingEngine::Patches::BigDecimalPatch
|
||||
module BigDecimal
|
||||
::BigDecimal.send :include, self
|
||||
class BigDecimal
|
||||
def to_d; self end
|
||||
end
|
||||
|
||||
module Integer
|
||||
::Integer.send :include, self
|
||||
class Integer
|
||||
def to_d; to_f.to_d end
|
||||
end
|
||||
|
||||
module String
|
||||
::String.send :include, self
|
||||
def to_d; ::BigDecimal.new(self) end
|
||||
class String
|
||||
def to_d
|
||||
BigDecimal self
|
||||
end
|
||||
end
|
||||
|
||||
module NilClass
|
||||
::NilClass.send :include, self
|
||||
class NilClass
|
||||
def to_d; 0 end
|
||||
end
|
||||
end
|
||||
|
||||
-2
@@ -26,7 +26,6 @@
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<% breadcrumb_paths(t(:label_my_account), t('two_factor_authentication.label_two_factor_authentication')) -%>
|
||||
<%= toolbar title: t('two_factor_authentication.label_two_factor_authentication') do %>
|
||||
<li class="toolbar-item">
|
||||
@@ -65,4 +64,3 @@
|
||||
class: 'button'
|
||||
end %>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
-2
@@ -31,7 +31,6 @@
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<%= activate_angular_js do %>
|
||||
<section class="admin--edit-section">
|
||||
<%= toolbar title: t('two_factor_authentication.label_devices') do %>
|
||||
<% unless devices.empty? %>
|
||||
@@ -58,4 +57,3 @@
|
||||
<%= cell ::TwoFactorAuthentication::Devices::TableCell, devices, admin_table: true || @user != User.current %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
+1
-3
@@ -6,9 +6,7 @@
|
||||
"test": "cd frontend && npm test && cd ..",
|
||||
"tslint_typechecks": "cd frontend && npm run tslint_typechecks && cd ..",
|
||||
"serve": "cd frontend && npm run serve",
|
||||
"serve-public": "cd frontend && ./node_modules/.bin/ng serve --host 0.0.0.0",
|
||||
"legacy-webpack": "cd frontend && npm run legacy-webpack && cd ..",
|
||||
"legacy-webpack-watch": "cd frontend && npm run legacy-webpack-watch"
|
||||
"serve-public": "cd frontend && ./node_modules/.bin/ng serve --host 0.0.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
|
||||
Reference in New Issue
Block a user