From 74493eb5d3223503af8d36da05a7cc9dd7dd6734 Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Mon, 5 Nov 2018 08:47:49 +0100 Subject: [PATCH 001/100] introduce grid_blocks --- app/assets/stylesheets/content/_grid.sass | 6 + app/assets/stylesheets/content/_index.sass | 1 + app/controllers/my_controller.rb | 112 +----------------- app/views/my/page.html.erb | 28 +---- config/routes.rb | 4 - frontend/src/app/angular4-modules.ts | 16 ++- .../app/components/grid/grid.component.html | 4 + .../src/app/components/grid/grid.component.ts | 41 +++++++ .../wp-assigned-to-me.component.html | 12 ++ .../wp-assigned-to-me.component.ts | 14 +++ .../routing/my-page/my-page.component.html | 3 + .../routing/my-page/my-page.component.ts | 8 ++ .../components/routing/ui-router.config.ts | 8 +- .../common/path-helper/apiv3/apiv3-paths.ts | 4 + .../apiv3/grids/apiv3-grids-paths.ts | 42 +++++++ .../hal/dm-services/grid-dm.service.ts | 43 +++++++ .../app/modules/hal/openproject-hal.module.ts | 4 +- .../modules/hal/resources/grid-resource.ts | 32 +++++ lib/api/v3/grids/grid_representer.rb | 75 ++++++++++++ lib/api/v3/grids/grids_api.rb | 46 +++++++ lib/api/v3/root.rb | 1 + lib/api/v3/utilities/path_helper.rb | 4 + .../lib/api/v3/grids/grid_representer_spec.rb | 81 +++++++++++++ spec/lib/api/v3/utilities/path_helper_spec.rb | 6 + .../macros/child_pages_macro_spec.rb | 1 - .../api/v3/grids/grids_resource_spec.rb | 68 +++++++++++ spec/routing/grids_spec.rb | 0 27 files changed, 523 insertions(+), 141 deletions(-) create mode 100644 app/assets/stylesheets/content/_grid.sass create mode 100644 frontend/src/app/components/grid/grid.component.html create mode 100644 frontend/src/app/components/grid/grid.component.ts create mode 100644 frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.html create mode 100644 frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts create mode 100644 frontend/src/app/components/routing/my-page/my-page.component.html create mode 100644 frontend/src/app/components/routing/my-page/my-page.component.ts create mode 100644 frontend/src/app/modules/common/path-helper/apiv3/grids/apiv3-grids-paths.ts create mode 100644 frontend/src/app/modules/hal/dm-services/grid-dm.service.ts create mode 100644 frontend/src/app/modules/hal/resources/grid-resource.ts create mode 100644 lib/api/v3/grids/grid_representer.rb create mode 100644 lib/api/v3/grids/grids_api.rb create mode 100644 spec/lib/api/v3/grids/grid_representer_spec.rb create mode 100644 spec/requests/api/v3/grids/grids_resource_spec.rb create mode 100644 spec/routing/grids_spec.rb diff --git a/app/assets/stylesheets/content/_grid.sass b/app/assets/stylesheets/content/_grid.sass new file mode 100644 index 0000000000..27fd5fcea8 --- /dev/null +++ b/app/assets/stylesheets/content/_grid.sass @@ -0,0 +1,6 @@ +.grid--container + display: grid + + +//.grid--item + diff --git a/app/assets/stylesheets/content/_index.sass b/app/assets/stylesheets/content/_index.sass index 36d401b014..3d7fb48e97 100644 --- a/app/assets/stylesheets/content/_index.sass +++ b/app/assets/stylesheets/content/_index.sass @@ -71,6 +71,7 @@ @import content/search @import content/contextual @import content/tooltip +@import content/grid @import content/menus/_project_autocompletion @import content/editor/index diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb index 7c081bd661..932b1e2184 100644 --- a/app/controllers/my_controller.rb +++ b/app/controllers/my_controller.rb @@ -37,7 +37,7 @@ class MyController < ApplicationController before_action :require_login before_action :check_password_confirmation, only: [:account], - if: ->() { request.patch? } + if: -> { request.patch? } menu_item :account, only: [:account] menu_item :settings, only: [:settings] @@ -45,32 +45,8 @@ class MyController < ApplicationController menu_item :access_token, only: [:access_token] menu_item :mail_notifications, only: [:mail_notifications] - DEFAULT_BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_work_packages, - 'workpackagesresponsiblefor' => :label_responsible_for_work_packages, - 'issuesreportedbyme' => :label_reported_work_packages, - 'issueswatched' => :label_watched_work_packages, - 'news' => :label_news_latest, - 'calendar' => :label_calendar, - 'timelog' => :label_spent_time - }.freeze - - DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'], - 'right' => ['issuesreportedbyme'] - }.freeze - - DRAG_AND_DROP_CONTAINERS = ['top', 'left', 'right'] - - verify xhr: true, - only: [:add_block, :remove_block, :order_blocks] - - def self.available_blocks - @available_blocks ||= DEFAULT_BLOCKS.merge(Redmine::Views::MyPage::Block.additional_blocks) - end - # Show user's page def index - @user = User.current - @blocks = get_current_layout render action: 'page', layout: 'no_menu' end alias :page :index @@ -89,7 +65,7 @@ class MyController < ApplicationController # Manage user's password def password - @user = User.current # required by "my" layout + @user = User.current # required by "my" layout @username = @user.login redirect_if_password_change_not_allowed_for(@user) end @@ -98,7 +74,7 @@ class MyController < ApplicationController def change_password return render_404 if OpenProject::Configuration.disable_password_login? - @user = User.current # required by "my" layout + @user = User.current # required by "my" layout @username = @user.login return if redirect_if_password_change_not_allowed_for(@user) if @user.check_password?(params[:password], update_legacy: false) @@ -162,88 +138,6 @@ class MyController < ApplicationController redirect_to action: 'access_token' end - # User's page layout configuration - def page_layout - @user = User.current - @blocks = get_current_layout - @block_options = [] - - # Pass block url to frontend - gon.my_order_blocks_url = my_order_blocks_url; - - # We track blocks that will show up on the page. This is in order to have - # them disabled in the blocks-to-add-to-page dropdown. - blocks_on_page = get_current_layout.values.flatten - - MyController.available_blocks.each do |block, value| - @block_options << [t("my.blocks.#{value}", default: [value, value.to_s.humanize]), block.dasherize, disabled: blocks_on_page.include?(block)] - end - end - - # Add a block to the user's page at the top. - # params[:block] : id of the block to add - # - # Responds with a HTML block. - def add_block - @block = params[:block].to_s.underscore - - unless MyController.available_blocks.keys.include? @block - render plain: I18n.t(:error_invalid_selected_value), status: 400 - return - end - - @user = User.current - layout = get_current_layout - - # Remove if already present in a group. - DRAG_AND_DROP_CONTAINERS.each { |f| (layout[f] ||= []).delete @block } - - # Add it on top. - layout['top'].unshift @block - - # Save user preference. - @user.pref[:my_page_layout] = layout - @user.pref.save - - render layout: false - end - - # Remove a block from the user's `my` page. - # params[:block] : id of the block to remove - # - # Responds with a JS layout. - def remove_block - @block = params[:block].to_s.underscore - @user = User.current - - # Remove block in all groups. - layout = get_current_layout - DRAG_AND_DROP_CONTAINERS.each { |f| (layout[f] ||= []).delete @block } - - # Save user preference. - @user.pref[:my_page_layout] = layout - @user.pref.save - - head 200, content_type: "text/html" - end - - def order_blocks - @user = User.current - - layout = get_current_layout - - # A nil +params[source_ordered_children]+ means all elements within - # +params['source']+ were dragged out elsewhere. - layout[params['source']] = params['source_ordered_children'] || [] - - layout[params['target']] = params['target_ordered_children'] - - @user.pref[:my_page_layout] = layout - @user.pref.save - - head :ok - end - def default_breadcrumb l(:label_my_account) end diff --git a/app/views/my/page.html.erb b/app/views/my/page.html.erb index 3f93982671..04b70c0906 100644 --- a/app/views/my/page.html.erb +++ b/app/views/my/page.html.erb @@ -26,27 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See docs/COPYRIGHT.rdoc for more details. ++#%> -<% breadcrumb_paths(l(:label_my_page)) %> -<%= toolbar title: l(:label_my_page) do %> -
  • - <%= link_to({ action: 'page_layout' }, accesskey: accesskey(:edit), class: 'button my-page--personalize-button') do %> - <%= op_icon('button--icon icon-settings') %> - <%= l(:label_personalize_page) %> - <% end %> -
  • -<% end %> - -
    -
    - <%= render partial: 'block_container', locals: { edit: false, blocks: @blocks['top'] } %> -
    -
    - <% %w(left right).each do |position| %> -
    - <%= render partial: 'block_container', locals: { edit: false, blocks: @blocks[position] } %> -
    - <% end %> -
    -
    - -<% html_title(l(:label_my_page)) -%> + +<% html_title(t(:label_my_page)) -%> + + diff --git a/config/routes.rb b/config/routes.rb index a92091efb9..3fb92f73ca 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -511,10 +511,6 @@ OpenProject::Application.routes.draw do end scope controller: 'my' do - post '/my/add_block', action: 'add_block' - post '/my/remove_block', action: 'remove_block' - post '/my/order_blocks', action: 'order_blocks' - get '/my/page_layout', action: 'page_layout' get '/my/password', action: 'password' post '/my/change_password', action: 'change_password' get '/my/page', action: 'page' diff --git a/frontend/src/app/angular4-modules.ts b/frontend/src/app/angular4-modules.ts index cf346b1d75..8f227693e5 100644 --- a/frontend/src/app/angular4-modules.ts +++ b/frontend/src/app/angular4-modules.ts @@ -226,6 +226,10 @@ import {FullCalendarModule} from 'ng-fullcalendar'; import {WorkPackagesCalendarController} from "core-components/wp-calendar/wp-calendar.component"; import {WorkPackagesEmbeddedCalendarEntryComponent} from "core-components/wp-table/embedded/wp-embedded-calendar-entry.component"; import {WorkPackageBreadcrumbParentComponent} from './components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.component'; +import {MyPageComponent} from "core-components/routing/my-page/my-page.component"; +import {GridComponent} from "core-components/grid/grid.component"; +import {GridDmService} from "core-app/modules/hal/dm-services/grid-dm.service"; +import {WidgetWpAssignedToMeComponent} from "core-components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component"; @NgModule({ imports: [ @@ -516,7 +520,14 @@ import {WorkPackageBreadcrumbParentComponent} from './components/work-packages/w // Work package calendars WorkPackagesCalendarController, - WorkPackagesEmbeddedCalendarEntryComponent + WorkPackagesEmbeddedCalendarEntryComponent, + + // MyPage + MyPageComponent, + + //GridBlocks + GridComponent, + WidgetWpAssignedToMeComponent ], entryComponents: [ WorkPackagesBaseComponent, @@ -616,6 +627,9 @@ import {WorkPackageBreadcrumbParentComponent} from './components/work-packages/w // Work package graphs on version page WorkPackageByVersionGraphComponent, + + // MyPage + MyPageComponent ] }) export class OpenProjectModule { diff --git a/frontend/src/app/components/grid/grid.component.html b/frontend/src/app/components/grid/grid.component.html new file mode 100644 index 0000000000..fb91b3d438 --- /dev/null +++ b/frontend/src/app/components/grid/grid.component.html @@ -0,0 +1,4 @@ +
    +
    +
    +
    diff --git a/frontend/src/app/components/grid/grid.component.ts b/frontend/src/app/components/grid/grid.component.ts new file mode 100644 index 0000000000..c94cc5d428 --- /dev/null +++ b/frontend/src/app/components/grid/grid.component.ts @@ -0,0 +1,41 @@ +import {Component, OnInit, AfterViewInit} from "@angular/core"; +import {GridDmService} from "core-app/modules/hal/dm-services/grid-dm.service"; +import {GridResource} from "core-app/modules/hal/resources/grid-resource"; + +@Component({ + templateUrl: './grid.component.html', + selector: 'grid' +}) +export class GridComponent implements OnInit, AfterViewInit { + public gridItems = []; + + constructor(readonly gridDm:GridDmService) {} + + ngOnInit() { + this.gridDm.load().then((grid:GridResource) => { + + console.log('done'); + }); + } + + //public get actions() { + // return _.flatten(this.hookService.call('customActions', this.workPackage)); + //} + + ngAfterViewInit() { + setTimeout(() => { + this.actions.forEach((action:CustomActionResource) => { + this.createComponent(action); + }); + }); + } + + createComponent(spec:any) { + const factory = this.resolver.resolveComponentFactory(spec.component); + + this.componentRef = this.actionContainer.createComponent(factory); + + this.componentRef.instance.workPackage = this.workPackage; + this.componentRef.instance.action = spec.action; + } +} diff --git a/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.html b/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.html new file mode 100644 index 0000000000..54f6d571b1 --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.html @@ -0,0 +1,12 @@ +
    + +

    + + Work packages assigned to me +

    + + + +
    + diff --git a/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts b/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts new file mode 100644 index 0000000000..e60a3b937b --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts @@ -0,0 +1,14 @@ +import {Component} from "@angular/core"; + +@Component({ + templateUrl: './wp-assigned-to-me.component.html', + + //TODO: remove selector as the widgets will be generated in code + selector: 'widget-wp-assigned-to-me' +}) + +export class WidgetWpAssignedToMeComponent { + public queryProps = '{"columns[]":["id","project","type","subject"],"filters":"[{\"assignee\":{\"operator\":\"=\",\"values\":[\"me\"]}},{\"status\":{\"operator\":\"o\",\"values\":[]}}]"}'; + public configuration = {"actionsColumnEnabled":false,"columnMenuEnabled":false,"contextMenuEnabled":false}; +} + diff --git a/frontend/src/app/components/routing/my-page/my-page.component.html b/frontend/src/app/components/routing/my-page/my-page.component.html new file mode 100644 index 0000000000..6c7b61d6c4 --- /dev/null +++ b/frontend/src/app/components/routing/my-page/my-page.component.html @@ -0,0 +1,3 @@ +

    My page

    + + diff --git a/frontend/src/app/components/routing/my-page/my-page.component.ts b/frontend/src/app/components/routing/my-page/my-page.component.ts new file mode 100644 index 0000000000..36c17c3e51 --- /dev/null +++ b/frontend/src/app/components/routing/my-page/my-page.component.ts @@ -0,0 +1,8 @@ +import {Component} from "@angular/core"; + +@Component({ + templateUrl: './my-page.component.html' +}) +export class MyPageComponent { + +} diff --git a/frontend/src/app/components/routing/ui-router.config.ts b/frontend/src/app/components/routing/ui-router.config.ts index 48e5498372..80554a6906 100644 --- a/frontend/src/app/components/routing/ui-router.config.ts +++ b/frontend/src/app/components/routing/ui-router.config.ts @@ -45,6 +45,7 @@ import {Injector} from "@angular/core"; import {WorkPackagesBaseComponent} from "core-components/routing/main/work-packages-base.component"; import {wpBaseAppSelector} from "./main/work-packages-base.component"; import {WorkPackagesCalendarComponent} from "core-components/routing/wp-calendar/wp.calendar.component"; +import {MyPageComponent} from "core-components/routing/my-page/my-page.component"; const panels = { get overview() { @@ -197,7 +198,12 @@ export const OPENPROJECT_ROUTES:StateDeclaration[] = [ _.extend(panels.activity, {name: 'work-packages.list.details.activity'}), _.extend(panels.activityDetails, {name: 'work-packages.list.details.activity.details'}), _.extend(panels.relations, {name: 'work-packages.list.details.relations'}), - _.extend(panels.watchers, {name: 'work-packages.list.details.watchers'}) + _.extend(panels.watchers, {name: 'work-packages.list.details.watchers'}), + { + name: 'my_page', + url: baseUrl + '/my/page', + component: MyPageComponent, + } ]; export function initializeUiRouterConfiguration(injector:Injector) { diff --git a/frontend/src/app/modules/common/path-helper/apiv3/apiv3-paths.ts b/frontend/src/app/modules/common/path-helper/apiv3/apiv3-paths.ts index 90331a87a9..ff544267ef 100644 --- a/frontend/src/app/modules/common/path-helper/apiv3/apiv3-paths.ts +++ b/frontend/src/app/modules/common/path-helper/apiv3/apiv3-paths.ts @@ -37,6 +37,7 @@ import {Apiv3QueriesPaths} from 'core-app/modules/common/path-helper/apiv3/queri import {Apiv3ProjectPaths} from 'core-app/modules/common/path-helper/apiv3/projects/apiv3-project-paths'; import {ApiV3FilterBuilder} from "core-components/api/api-v3/api-v3-filter-builder"; import {Apiv3TypesPaths} from "core-app/modules/common/path-helper/apiv3/types/apiv3-types-paths"; +import {Apiv3GridsPaths} from "core-app/modules/common/path-helper/apiv3/grids/apiv3-grids-paths"; export class ApiV3Paths { // Base path @@ -78,6 +79,9 @@ export class ApiV3Paths { // /api/v3/help_texts public readonly help_texts = new SimpleResourceCollection(this.apiV3Base, 'help_texts'); + // /api/v3/grid_blocks + public readonly grids = new Apiv3GridsPaths(this.apiV3Base); + constructor(readonly appBasePath:string) { } diff --git a/frontend/src/app/modules/common/path-helper/apiv3/grids/apiv3-grids-paths.ts b/frontend/src/app/modules/common/path-helper/apiv3/grids/apiv3-grids-paths.ts new file mode 100644 index 0000000000..99ae72a5ff --- /dev/null +++ b/frontend/src/app/modules/common/path-helper/apiv3/grids/apiv3-grids-paths.ts @@ -0,0 +1,42 @@ +// -- 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 { + SimpleResource, + SimpleResourceCollection +} from 'core-app/modules/common/path-helper/apiv3/path-resources'; + +export class Apiv3GridsPaths extends SimpleResourceCollection { + constructor(basePath:string) { + super(basePath, 'grids'); + } + + public id(gridId:string|number) { + return new SimpleResource(this.path, gridId); + } +} diff --git a/frontend/src/app/modules/hal/dm-services/grid-dm.service.ts b/frontend/src/app/modules/hal/dm-services/grid-dm.service.ts new file mode 100644 index 0000000000..3c98a38f9e --- /dev/null +++ b/frontend/src/app/modules/hal/dm-services/grid-dm.service.ts @@ -0,0 +1,43 @@ +//-- 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 {Injectable} from '@angular/core'; +import {HalResourceService} from 'core-app/modules/hal/services/hal-resource.service'; +import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; +import {GridResource} from "core-app/modules/hal/resources/grid-resource"; + +@Injectable() +export class GridDmService { + constructor(protected halResourceService:HalResourceService, + protected pathHelper:PathHelperService) { + } + + public load():Promise { + return this.halResourceService.get(this.pathHelper.api.v3.grids.id(42).toString()).toPromise(); + } +} diff --git a/frontend/src/app/modules/hal/openproject-hal.module.ts b/frontend/src/app/modules/hal/openproject-hal.module.ts index bf6fe5b1fa..ddc7a75dc2 100644 --- a/frontend/src/app/modules/hal/openproject-hal.module.ts +++ b/frontend/src/app/modules/hal/openproject-hal.module.ts @@ -46,6 +46,7 @@ import {UserDmService} from 'core-app/modules/hal/dm-services/user-dm.service'; import {ProjectDmService} from 'core-app/modules/hal/dm-services/project-dm.service'; import {HalResourceSortingService} from "core-app/modules/hal/services/hal-resource-sorting.service"; import {HalAwareErrorHandler} from "core-app/modules/hal/services/hal-aware-error-handler"; +import {GridDmService} from "core-app/modules/hal/dm-services/grid-dm.service"; @NgModule({ imports: [ @@ -67,7 +68,8 @@ import {HalAwareErrorHandler} from "core-app/modules/hal/services/hal-aware-erro RelationsDmService, ProjectDmService, RootDmService, - TypeDmService + TypeDmService, + GridDmService ] }) export class OpenprojectHalModule { } diff --git a/frontend/src/app/modules/hal/resources/grid-resource.ts b/frontend/src/app/modules/hal/resources/grid-resource.ts new file mode 100644 index 0000000000..9fd29aad28 --- /dev/null +++ b/frontend/src/app/modules/hal/resources/grid-resource.ts @@ -0,0 +1,32 @@ +//-- 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 {HalResource} from 'core-app/modules/hal/resources/hal-resource'; + +export class GridResource extends HalResource { +} diff --git a/lib/api/v3/grids/grid_representer.rb b/lib/api/v3/grids/grid_representer.rb new file mode 100644 index 0000000000..3961b2f650 --- /dev/null +++ b/lib/api/v3/grids/grid_representer.rb @@ -0,0 +1,75 @@ +#-- 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. +#++ + +module API + module V3 + module Grids + class GridRepresenter < ::API::Decorators::Single + link :page do + { + href: my_page_path + } + end + + property :row_count, + exec_context: :decorator + + property :column_count, + exec_context: :decorator + + property :widgets, + exec_context: :decorator + + def _type + 'Grid' + end + + def row_count + 4 + end + + def column_count + 5 + end + + def widgets + [ + { + "_type": "Widget", + "identifier": 'work_packages_assigned_to_me', + "startRow": '2', + "endRow": '4', + "startColumn": '2', + "endColumn": '4' + } + ] + end + end + end + end +end diff --git a/lib/api/v3/grids/grids_api.rb b/lib/api/v3/grids/grids_api.rb new file mode 100644 index 0000000000..cec530980d --- /dev/null +++ b/lib/api/v3/grids/grids_api.rb @@ -0,0 +1,46 @@ +#-- 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. +#++ + +module API + module V3 + module Grids + class GridsAPI < ::API::OpenProjectAPI + resources :grids do + helpers do + end + + route_param :id do + get do + GridRepresenter.new({}, current_user: current_user) + end + end + end + end + end + end +end diff --git a/lib/api/v3/root.rb b/lib/api/v3/root.rb index 763feb06fa..d7e39c7a09 100644 --- a/lib/api/v3/root.rb +++ b/lib/api/v3/root.rb @@ -61,6 +61,7 @@ module API mount ::API::V3::Versions::VersionsAPI mount ::API::V3::WorkPackages::WorkPackagesAPI mount ::API::V3::WikiPages::WikiPagesAPI + mount ::API::V3::Grids::GridsAPI get '/' do RootRepresenter.new({}, current_user: current_user) diff --git a/lib/api/v3/utilities/path_helper.rb b/lib/api/v3/utilities/path_helper.rb index 4682244222..543b60705c 100644 --- a/lib/api/v3/utilities/path_helper.rb +++ b/lib/api/v3/utilities/path_helper.rb @@ -131,6 +131,10 @@ module API "#{root}/custom_options/#{id}" end + def self.grid(id) + "#{root}/grids/#{id}" + end + def self.help_texts "#{root}/help_texts" end diff --git a/spec/lib/api/v3/grids/grid_representer_spec.rb b/spec/lib/api/v3/grids/grid_representer_spec.rb new file mode 100644 index 0000000000..e4eb1f6402 --- /dev/null +++ b/spec/lib/api/v3/grids/grid_representer_spec.rb @@ -0,0 +1,81 @@ +#-- 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. +#++ + +require 'spec_helper' + +describe ::API::V3::Grids::GridRepresenter do + include OpenProject::StaticRouting::UrlHelpers + + let(:current_user) { FactoryBot.build_stubbed(:user) } + let(:representer) { described_class.new(OpenStruct.new, current_user: current_user) } + + context 'generation' do + subject(:generated) { representer.to_json } + + it 'denotes its type' do + is_expected + .to be_json_eql('Grid'.to_json) + .at_path('_type') + end + + it 'identifies the url the grid is stored for' do + is_expected + .to be_json_eql(my_page_path.to_json) + .at_path('_links/page/href') + end + + it 'has a rowCount' do + is_expected + .to be_json_eql(4) + .at_path('rowCount') + end + + it 'has a columnCount' do + is_expected + .to be_json_eql(5) + .at_path('columnCount') + end + + it 'has a list of widgets' do + widgets = [ + { + "_type": "Widget", + "identifier": 'work_packages_assigned_to_me', + "startRow": '2', + "endRow": '4', + "startColumn": '2', + "endColumn": '4' + } + ] + + is_expected + .to be_json_eql(widgets.to_json) + .at_path('widgets') + end + end +end diff --git a/spec/lib/api/v3/utilities/path_helper_spec.rb b/spec/lib/api/v3/utilities/path_helper_spec.rb index 5d140c7a3d..2f319432d4 100644 --- a/spec/lib/api/v3/utilities/path_helper_spec.rb +++ b/spec/lib/api/v3/utilities/path_helper_spec.rb @@ -184,6 +184,12 @@ describe ::API::V3::Utilities::PathHelper do it_behaves_like 'api v3 path', '/projects/42/work_packages/form' end + describe '#grid' do + subject { helper.grid(42) } + + it_behaves_like 'api v3 path', '/grids/42' + end + describe '#message' do subject { helper.message(42) } diff --git a/spec/lib/open_project/macros/child_pages_macro_spec.rb b/spec/lib/open_project/macros/child_pages_macro_spec.rb index 2b4f45b649..62736875c6 100644 --- a/spec/lib/open_project/macros/child_pages_macro_spec.rb +++ b/spec/lib/open_project/macros/child_pages_macro_spec.rb @@ -106,7 +106,6 @@ describe 'OpenProject child pages macro' do login_as(user) end - let(:input) { } subject { format_text(current_page.content, :text) } diff --git a/spec/requests/api/v3/grids/grids_resource_spec.rb b/spec/requests/api/v3/grids/grids_resource_spec.rb new file mode 100644 index 0000000000..09d9dbfb56 --- /dev/null +++ b/spec/requests/api/v3/grids/grids_resource_spec.rb @@ -0,0 +1,68 @@ +#-- 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. +#++ + +require 'spec_helper' +require 'rack/test' + +describe 'API v3 Grids resource', type: :request, content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + let(:current_user) do + FactoryBot.create(:user) + end + + before do + login_as(current_user) + end + + describe '#get' do + subject(:response) { last_response } + let(:get_path) { api_v3_paths.grid(42) } + + before do + get get_path + end + + it 'should respond with 200' do + expect(subject.status).to eq(200) + end + + it 'sends a grid block' do + expect(subject.body) + .to be_json_eql('Grid'.to_json) + .at_path('_type') + end + + it 'identifies the url the grid is stored for' do + expect(subject.body) + .to be_json_eql(my_page_path.to_json) + .at_path('_links/page/href') + end + end +end diff --git a/spec/routing/grids_spec.rb b/spec/routing/grids_spec.rb new file mode 100644 index 0000000000..e69de29bb2 From fed78e4f3e9db8c7be1cb5761e8508ee7b9c0bdf Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Sun, 11 Nov 2018 20:51:41 +0100 Subject: [PATCH 002/100] have 3 widgets on my page --- frontend/src/app/angular4-modules.ts | 41 +++++++++++- .../app/components/grid/grid.component.html | 7 ++- .../src/app/components/grid/grid.component.ts | 62 +++++++++++++------ .../grid/widgets/abstract-widget.component.ts | 16 +++++ .../wp-assigned-to-me.component.ts | 14 ----- .../wp-assigned.component.html} | 0 .../wp-assigned/wp-assigned.component.ts | 12 ++++ .../wp-created/wp-created.component.html | 11 ++++ .../wp-created/wp-created.component.ts | 13 ++++ .../wp-watched/wp-watched.component.html | 11 ++++ .../wp-watched/wp-watched.component.ts | 12 ++++ .../modules/hal/resources/grid-resource.ts | 2 + .../hal/resources/grid-widget-resource.ts | 37 +++++++++++ .../app/modules/hal/resources/resources.ts | 2 + lib/api/v3/grids/grid_representer.rb | 18 +++++- .../lib/api/v3/grids/grid_representer_spec.rb | 1 - 16 files changed, 219 insertions(+), 40 deletions(-) create mode 100644 frontend/src/app/components/grid/widgets/abstract-widget.component.ts delete mode 100644 frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts rename frontend/src/app/components/grid/widgets/{wp-assigned-to-me/wp-assigned-to-me.component.html => wp-assigned/wp-assigned.component.html} (100%) create mode 100644 frontend/src/app/components/grid/widgets/wp-assigned/wp-assigned.component.ts create mode 100644 frontend/src/app/components/grid/widgets/wp-created/wp-created.component.html create mode 100644 frontend/src/app/components/grid/widgets/wp-created/wp-created.component.ts create mode 100644 frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.html create mode 100644 frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.ts create mode 100644 frontend/src/app/modules/hal/resources/grid-widget-resource.ts diff --git a/frontend/src/app/angular4-modules.ts b/frontend/src/app/angular4-modules.ts index 8f227693e5..c4549fc550 100644 --- a/frontend/src/app/angular4-modules.ts +++ b/frontend/src/app/angular4-modules.ts @@ -229,7 +229,9 @@ import {WorkPackageBreadcrumbParentComponent} from './components/work-packages/w import {MyPageComponent} from "core-components/routing/my-page/my-page.component"; import {GridComponent} from "core-components/grid/grid.component"; import {GridDmService} from "core-app/modules/hal/dm-services/grid-dm.service"; -import {WidgetWpAssignedToMeComponent} from "core-components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component"; +import {WidgetWpAssignedComponent} from "core-components/grid/widgets/wp-assigned/wp-assigned.component"; +import {WidgetWpCreatedComponent} from "core-components/grid/widgets/wp-created/wp-created.component"; +import {WidgetWpWatchedComponent} from "core-components/grid/widgets/wp-watched/wp-watched.component"; @NgModule({ imports: [ @@ -269,6 +271,12 @@ import {WidgetWpAssignedToMeComponent} from "core-components/grid/widgets/wp-ass deps: [Injector], multi: true }, + { + provide: APP_INITIALIZER, + useFactory: registerWidgets, + deps: [Injector], + multi: true + }, OpTitleService, TimezoneService, WorkPackageRelationsService, @@ -527,7 +535,9 @@ import {WidgetWpAssignedToMeComponent} from "core-components/grid/widgets/wp-ass //GridBlocks GridComponent, - WidgetWpAssignedToMeComponent + WidgetWpAssignedComponent, + WidgetWpCreatedComponent, + WidgetWpWatchedComponent, ], entryComponents: [ WorkPackagesBaseComponent, @@ -629,7 +639,10 @@ import {WidgetWpAssignedToMeComponent} from "core-components/grid/widgets/wp-ass WorkPackageByVersionGraphComponent, // MyPage - MyPageComponent + MyPageComponent, + WidgetWpAssignedComponent, + WidgetWpCreatedComponent, + WidgetWpWatchedComponent, ] }) export class OpenProjectModule { @@ -664,3 +677,25 @@ export function initializeServices(injector:Injector) { ExternalQueryConfiguration.setupListener(); }; } + +export function registerWidgets(injector:Injector) { + return () => { + const hookService = injector.get(HookService); + hookService.register('gridWidgets', () => { + return [ + { + identifier: 'work_packages_assigned', + component: WidgetWpAssignedComponent + }, + { + identifier: 'work_packages_created', + component: WidgetWpCreatedComponent + }, + { + identifier: 'work_packages_watched', + component: WidgetWpWatchedComponent + } + ]; + }); + }; +} diff --git a/frontend/src/app/components/grid/grid.component.html b/frontend/src/app/components/grid/grid.component.html index fb91b3d438..c156996cea 100644 --- a/frontend/src/app/components/grid/grid.component.html +++ b/frontend/src/app/components/grid/grid.component.html @@ -1,4 +1,5 @@ -
    -
    -
    +
    +
    diff --git a/frontend/src/app/components/grid/grid.component.ts b/frontend/src/app/components/grid/grid.component.ts index c94cc5d428..857e2f4f05 100644 --- a/frontend/src/app/components/grid/grid.component.ts +++ b/frontend/src/app/components/grid/grid.component.ts @@ -1,41 +1,67 @@ -import {Component, OnInit, AfterViewInit} from "@angular/core"; +import {Component, OnInit, AfterViewInit, ComponentFactoryResolver, ElementRef, ViewChild, ViewContainerRef, + ComponentRef, + OnDestroy} from "@angular/core"; import {GridDmService} from "core-app/modules/hal/dm-services/grid-dm.service"; import {GridResource} from "core-app/modules/hal/resources/grid-resource"; +import {WidgetWpAssignedComponent} from "core-components/grid/widgets/wp-assigned/wp-assigned.component"; +import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; +import {HookService} from "core-app/modules/plugins/hook-service"; +import {debugLog} from "core-app/helpers/debug_output"; + +export interface WidgetRegistration { + identifier:string; + // TODO: Find out how to declare component to be of type class + component:any; +} @Component({ templateUrl: './grid.component.html', selector: 'grid' }) -export class GridComponent implements OnInit, AfterViewInit { - public gridItems = []; +export class GridComponent implements OnInit, AfterViewInit, OnDestroy { + public gridWidgets:ComponentRef[] = []; + private registeredWidgets:WidgetRegistration[] = []; - constructor(readonly gridDm:GridDmService) {} + @ViewChild('gridContainer', { read: ViewContainerRef }) gridContainer:ViewContainerRef; - ngOnInit() { - this.gridDm.load().then((grid:GridResource) => { + public areaResources = [{component: WidgetWpAssignedComponent}]; + + constructor(readonly gridDm:GridDmService, + readonly resolver:ComponentFactoryResolver, + readonly Hook:HookService) {} - console.log('done'); + ngOnInit() { + _.each(this.Hook.call('gridWidgets'), (registration:WidgetRegistration[]) => { + this.registeredWidgets = this.registeredWidgets.concat(registration); }); } - //public get actions() { - // return _.flatten(this.hookService.call('customActions', this.workPackage)); - //} + ngOnDestroy() { + this.gridWidgets.forEach((widget) => widget.destroy()); + } ngAfterViewInit() { - setTimeout(() => { - this.actions.forEach((action:CustomActionResource) => { - this.createComponent(action); + this.gridDm.load().then((grid:GridResource) => { + grid.widgets.forEach((widget) => { + this.createWidget(widget); }); }); } - createComponent(spec:any) { - const factory = this.resolver.resolveComponentFactory(spec.component); + createWidget(widget:GridWidgetResource) { + let registration = this.registeredWidgets.find((reg) => reg.identifier === widget.identifier); + + if (!registration) { + debugLog(`No widget registered with identifier ${widget.identifier}`); + + return; + } + + const factory = this.resolver.resolveComponentFactory(registration.component); - this.componentRef = this.actionContainer.createComponent(factory); + let componentRef = this.gridContainer.createComponent(factory); + (componentRef.instance as WidgetWpAssignedComponent).widgetResource = widget; - this.componentRef.instance.workPackage = this.workPackage; - this.componentRef.instance.action = spec.action; + this.gridWidgets.push(componentRef); } } diff --git a/frontend/src/app/components/grid/widgets/abstract-widget.component.ts b/frontend/src/app/components/grid/widgets/abstract-widget.component.ts new file mode 100644 index 0000000000..9f9a82df83 --- /dev/null +++ b/frontend/src/app/components/grid/widgets/abstract-widget.component.ts @@ -0,0 +1,16 @@ +import {Component, HostBinding} from "@angular/core"; +import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; + +export abstract class AbstractWidgetComponent { + @HostBinding('style.grid-column-start') gridColumnStart:string; + @HostBinding('style.grid-column-end') gridColumnEnd:string; + @HostBinding('style.grid-row-start') gridRowStart:string; + @HostBinding('style.grid-row-end') gridRowEnd:string; + + public set widgetResource(resource:GridWidgetResource) { + this.gridColumnStart = resource.startColumn; + this.gridColumnEnd = resource.endColumn; + this.gridRowStart = resource.startRow; + this.gridRowEnd = resource.endRow; + } +} diff --git a/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts b/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts deleted file mode 100644 index e60a3b937b..0000000000 --- a/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Component} from "@angular/core"; - -@Component({ - templateUrl: './wp-assigned-to-me.component.html', - - //TODO: remove selector as the widgets will be generated in code - selector: 'widget-wp-assigned-to-me' -}) - -export class WidgetWpAssignedToMeComponent { - public queryProps = '{"columns[]":["id","project","type","subject"],"filters":"[{\"assignee\":{\"operator\":\"=\",\"values\":[\"me\"]}},{\"status\":{\"operator\":\"o\",\"values\":[]}}]"}'; - public configuration = {"actionsColumnEnabled":false,"columnMenuEnabled":false,"contextMenuEnabled":false}; -} - diff --git a/frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.html b/frontend/src/app/components/grid/widgets/wp-assigned/wp-assigned.component.html similarity index 100% rename from frontend/src/app/components/grid/widgets/wp-assigned-to-me/wp-assigned-to-me.component.html rename to frontend/src/app/components/grid/widgets/wp-assigned/wp-assigned.component.html diff --git a/frontend/src/app/components/grid/widgets/wp-assigned/wp-assigned.component.ts b/frontend/src/app/components/grid/widgets/wp-assigned/wp-assigned.component.ts new file mode 100644 index 0000000000..90c841d726 --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-assigned/wp-assigned.component.ts @@ -0,0 +1,12 @@ +import {Component} from "@angular/core"; +import {AbstractWidgetComponent} from "core-components/grid/widgets/abstract-widget.component"; + +@Component({ + templateUrl: './wp-assigned.component.html', +}) + +export class WidgetWpAssignedComponent extends AbstractWidgetComponent{ + public queryProps = {"columns[]":["id","project","type","subject"],"filters":"[{\"assignee\":{\"operator\":\"=\",\"values\":[\"me\"]}},{\"status\":{\"operator\":\"o\",\"values\":[]}}]"}; + public configuration = {"actionsColumnEnabled":false,"columnMenuEnabled":false,"contextMenuEnabled":false}; +} + diff --git a/frontend/src/app/components/grid/widgets/wp-created/wp-created.component.html b/frontend/src/app/components/grid/widgets/wp-created/wp-created.component.html new file mode 100644 index 0000000000..f94ec8a7af --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-created/wp-created.component.html @@ -0,0 +1,11 @@ +
    + +

    + + Work packages created by me +

    + + + +
    diff --git a/frontend/src/app/components/grid/widgets/wp-created/wp-created.component.ts b/frontend/src/app/components/grid/widgets/wp-created/wp-created.component.ts new file mode 100644 index 0000000000..ca48b9f070 --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-created/wp-created.component.ts @@ -0,0 +1,13 @@ +import {Component} from "@angular/core"; +import {AbstractWidgetComponent} from "core-components/grid/widgets/abstract-widget.component"; + +@Component({ + templateUrl: './wp-created.component.html', +}) +export class WidgetWpCreatedComponent extends AbstractWidgetComponent{ + public queryProps = { "columns[]": ["id", "project", "type", "subject"] , + "filters": "[{\"author\":{\"operator\":\"=\",\"values\":[\"me\"]}},{\"status\":{\"operator\":\"o\",\"values\":[]}}]"}; + public configuration = { "actionsColumnEnabled":false, + "columnMenuEnabled":false, + "contextMenuEnabled":false }; +} diff --git a/frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.html b/frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.html new file mode 100644 index 0000000000..3dd748eede --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.html @@ -0,0 +1,11 @@ +
    + +

    + + Work packages watched by me +

    + + + +
    diff --git a/frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.ts b/frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.ts new file mode 100644 index 0000000000..9c91d67168 --- /dev/null +++ b/frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.ts @@ -0,0 +1,12 @@ +import {Component} from "@angular/core"; +import {AbstractWidgetComponent} from "core-components/grid/widgets/abstract-widget.component"; + +@Component({ + templateUrl: './wp-watched.component.html', +}) +export class WidgetWpWatchedComponent extends AbstractWidgetComponent { + public queryProps = {"columns[]":["id","project","type","subject"],"filters":"[{\"watcher\":{\"operator\":\"=\",\"values\":[\"me\"]}},{\"status\":{\"operator\":\"o\",\"values\":[]}}]"}; + public configuration = { "actionsColumnEnabled":false, + "columnMenuEnabled":false, + "contextMenuEnabled":false }; +} diff --git a/frontend/src/app/modules/hal/resources/grid-resource.ts b/frontend/src/app/modules/hal/resources/grid-resource.ts index 9fd29aad28..f18ae100ce 100644 --- a/frontend/src/app/modules/hal/resources/grid-resource.ts +++ b/frontend/src/app/modules/hal/resources/grid-resource.ts @@ -27,6 +27,8 @@ //++ import {HalResource} from 'core-app/modules/hal/resources/hal-resource'; +import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; export class GridResource extends HalResource { + public widgets:GridWidgetResource[]; } diff --git a/frontend/src/app/modules/hal/resources/grid-widget-resource.ts b/frontend/src/app/modules/hal/resources/grid-widget-resource.ts new file mode 100644 index 0000000000..bf71cc6cd2 --- /dev/null +++ b/frontend/src/app/modules/hal/resources/grid-widget-resource.ts @@ -0,0 +1,37 @@ +//-- 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 {HalResource} from 'core-app/modules/hal/resources/hal-resource'; + +export class GridWidgetResource extends HalResource { + public identifier:string; + public startRow:string; + public endRow:string; + public startColumn:string; + public endColumn:string; +} diff --git a/frontend/src/app/modules/hal/resources/resources.ts b/frontend/src/app/modules/hal/resources/resources.ts index 9243e78ba0..d304b64fb6 100644 --- a/frontend/src/app/modules/hal/resources/resources.ts +++ b/frontend/src/app/modules/hal/resources/resources.ts @@ -23,6 +23,7 @@ import {TypeResource} from 'core-app/modules/hal/resources/type-resource'; import {UserResource} from 'core-app/modules/hal/resources/user-resource'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-collection-resource'; +import {GridResource} from "core-app/modules/hal/resources/grid-resource"; export const coreHalResources = [ AttachmentCollectionResource, @@ -50,4 +51,5 @@ export const coreHalResources = [ UserResource, WorkPackageResource, WorkPackageCollectionResource, + GridResource ]; diff --git a/lib/api/v3/grids/grid_representer.rb b/lib/api/v3/grids/grid_representer.rb index 3961b2f650..5b0202a77b 100644 --- a/lib/api/v3/grids/grid_representer.rb +++ b/lib/api/v3/grids/grid_representer.rb @@ -61,11 +61,27 @@ module API [ { "_type": "Widget", - "identifier": 'work_packages_assigned_to_me', + "identifier": 'work_packages_assigned', "startRow": '2', "endRow": '4', "startColumn": '2', "endColumn": '4' + }, + { + "_type": "Widget", + "identifier": 'work_packages_created', + "startRow": '1', + "endRow": '2', + "startColumn": '1', + "endColumn": '6' + }, + { + "_type": "Widget", + "identifier": 'work_packages_watched', + "startRow": '2', + "endRow": '4', + "startColumn": '4', + "endColumn": '6' } ] end diff --git a/spec/lib/api/v3/grids/grid_representer_spec.rb b/spec/lib/api/v3/grids/grid_representer_spec.rb index e4eb1f6402..dc10293b68 100644 --- a/spec/lib/api/v3/grids/grid_representer_spec.rb +++ b/spec/lib/api/v3/grids/grid_representer_spec.rb @@ -65,7 +65,6 @@ describe ::API::V3::Grids::GridRepresenter do widgets = [ { "_type": "Widget", - "identifier": 'work_packages_assigned_to_me', "startRow": '2', "endRow": '4', "startColumn": '2', From b37bcacef909c667fa0b693815f3d1d73f90d817 Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Mon, 12 Nov 2018 09:59:27 +0100 Subject: [PATCH 003/100] start on drag&drop --- app/assets/stylesheets/content/_grid.sass | 3 -- frontend/src/app/angular4-modules.ts | 2 ++ .../app/components/grid/grid.component.html | 13 ++++++-- .../src/app/components/grid/grid.component.ts | 33 +++++++++++++++++-- .../wp-assigned/wp-assigned.component.ts | 3 +- .../wp-created/wp-created.component.html | 11 ------- .../wp-created/wp-created.component.ts | 3 +- .../wp-watched/wp-watched.component.html | 11 ------- .../wp-watched/wp-watched.component.ts | 3 +- .../wp-widget.component.html} | 6 ++-- .../src/app/modules/plugins/hook-service.ts | 6 ++-- 11 files changed, 55 insertions(+), 39 deletions(-) delete mode 100644 frontend/src/app/components/grid/widgets/wp-created/wp-created.component.html delete mode 100644 frontend/src/app/components/grid/widgets/wp-watched/wp-watched.component.html rename frontend/src/app/components/grid/widgets/{wp-assigned/wp-assigned.component.html => wp-widget/wp-widget.component.html} (51%) diff --git a/app/assets/stylesheets/content/_grid.sass b/app/assets/stylesheets/content/_grid.sass index 27fd5fcea8..d4ca7e5c70 100644 --- a/app/assets/stylesheets/content/_grid.sass +++ b/app/assets/stylesheets/content/_grid.sass @@ -1,6 +1,3 @@ .grid--container display: grid - -//.grid--item - diff --git a/frontend/src/app/angular4-modules.ts b/frontend/src/app/angular4-modules.ts index c4549fc550..81a563fdf4 100644 --- a/frontend/src/app/angular4-modules.ts +++ b/frontend/src/app/angular4-modules.ts @@ -27,6 +27,7 @@ // ++ import {PortalModule} from '@angular/cdk/portal'; +import {DragDropModule} from '@angular/cdk/drag-drop'; import {APP_INITIALIZER, ApplicationRef, Injector, NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {BrowserModule} from '@angular/platform-browser'; @@ -241,6 +242,7 @@ import {WidgetWpWatchedComponent} from "core-components/grid/widgets/wp-watched/ UIRouterModule.forRoot(), // Angular CDK PortalModule, + DragDropModule, // Commons OpenprojectCommonModule, // A11y diff --git a/frontend/src/app/components/grid/grid.component.html b/frontend/src/app/components/grid/grid.component.html index c156996cea..862f0d44b8 100644 --- a/frontend/src/app/components/grid/grid.component.html +++ b/frontend/src/app/components/grid/grid.component.html @@ -1,5 +1,14 @@
    -