diff --git a/frontend/src/app/helpers/images/path-helper.ts b/frontend/src/app/helpers/images/path-helper.ts new file mode 100644 index 0000000000..dcaf0b94b0 --- /dev/null +++ b/frontend/src/app/helpers/images/path-helper.ts @@ -0,0 +1,18 @@ +export namespace ImageHelpers { + + /** + * Returns an absolute asset path from the assets/images/ folder + * + * e.g., to access: + * frontend/src/assets/images/board_creation_modal/assignees.svg + * + * use + * imagePath('board_creation_modal/assignees.svg') + * + * + * @param image Path to the image starting from frontend/src/assets/images + */ + export function imagePath(image:string) { + return __webpack_public_path__ + 'assets/images/' + image; + } +} diff --git a/frontend/src/app/modules/boards/board/board-actions/assignee/assignee-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/assignee/assignee-action.service.ts index f4952dafe7..20ef55f132 100644 --- a/frontend/src/app/modules/boards/board/board-actions/assignee/assignee-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/assignee/assignee-action.service.ts @@ -6,24 +6,25 @@ import {ProjectResource} from "core-app/modules/hal/resources/project-resource"; import {CachedBoardActionService} from "core-app/modules/boards/board/board-actions/cached-board-action.service"; import {HalResource} from "core-app/modules/hal/resources/hal-resource"; import {Board} from "core-app/modules/boards/board/board"; -import {ApiV3Filter, FilterOperator} from "core-components/api/api-v3/api-v3-filter-builder"; +import {ApiV3Filter} from "core-components/api/api-v3/api-v3-filter-builder"; import {QueryResource} from "core-app/modules/hal/resources/query-resource"; - +import {ImageHelpers} from "core-app/helpers/images/path-helper"; +import imagePath = ImageHelpers.imagePath; @Injectable() export class BoardAssigneeActionService extends CachedBoardActionService { filterName = 'assignee'; - text = this.I18n.t('js.boards.board_type.action_by_attribute', - { attribute: this.I18n.t('js.boards.board_type.action_type.assignee')}); + text = this.I18n.t('js.boards.board_type.action_type.assignee'); - description = this.I18n.t('js.boards.board_type.action_text', - { attribute: this.I18n.t('js.boards.board_type.action_type.assignee')}); + description = this.I18n.t('js.boards.board_type.action_text_assignee'); label = this.I18n.t('js.boards.add_list_modal.labels.assignee'); icon = 'icon-user'; + image = ImageHelpers.imagePath('board_creation_modal/assignees.svg'); + readonly unassignedUser:any = { id: null, href: null, diff --git a/frontend/src/app/modules/boards/board/board-actions/board-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/board-action.service.ts index 20f9176b9e..ae2ef16829 100644 --- a/frontend/src/app/modules/boards/board/board-actions/board-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/board-action.service.ts @@ -55,6 +55,11 @@ export abstract class BoardActionService { */ description:string; + /** + * The description used in tile + */ + image:string; + /** * Label used to describe the values in the modal */ diff --git a/frontend/src/app/modules/boards/board/board-actions/board-actions-registry.service.ts b/frontend/src/app/modules/boards/board/board-actions/board-actions-registry.service.ts index d9580321db..eefe79c4b9 100644 --- a/frontend/src/app/modules/boards/board/board-actions/board-actions-registry.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/board-actions-registry.service.ts @@ -12,7 +12,7 @@ export class BoardActionsRegistryService { public available() { return _.map(this.mapping, (service:BoardActionService, attribute:string) => { - return { attribute: attribute, text: service.localizedName, icon:'', description:'' }; + return { attribute: attribute, text: service.localizedName, icon:'', description:'', image:''}; }); } diff --git a/frontend/src/app/modules/boards/board/board-actions/status/status-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/status/status-action.service.ts index d8715d7882..d801f6a42f 100644 --- a/frontend/src/app/modules/boards/board/board-actions/status/status-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/status/status-action.service.ts @@ -4,21 +4,22 @@ import {StatusResource} from "core-app/modules/hal/resources/status-resource"; import {BoardActionService} from "core-app/modules/boards/board/board-actions/board-action.service"; import {CachedBoardActionService} from "core-app/modules/boards/board/board-actions/cached-board-action.service"; import {StatusBoardHeaderComponent} from "core-app/modules/boards/board/board-actions/status/status-board-header.component"; +import {ImageHelpers} from "core-app/helpers/images/path-helper"; @Injectable() export class BoardStatusActionService extends CachedBoardActionService { filterName = 'status'; - text = this.I18n.t('js.boards.board_type.action_by_attribute', - { attribute: this.I18n.t('js.boards.board_type.action_type.status')}); + text = this.I18n.t('js.boards.board_type.action_type.status'); - description = this.I18n.t('js.boards.board_type.action_text', - { attribute: this.I18n.t('js.boards.board_type.action_type.status')}); + description = this.I18n.t('js.boards.board_type.action_text_status'); label = this.I18n.t('js.boards.add_list_modal.labels.status'); icon = 'icon-workflow'; + image = ImageHelpers.imagePath('board_creation_modal/status.svg'); + public get localizedName() { return this.I18n.t('js.work_packages.properties.status'); } diff --git a/frontend/src/app/modules/boards/board/board-actions/subproject/subproject-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/subproject/subproject-action.service.ts index 2ee90af5eb..beb7af892a 100644 --- a/frontend/src/app/modules/boards/board/board-actions/subproject/subproject-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/subproject/subproject-action.service.ts @@ -4,18 +4,17 @@ import {HalResource} from "core-app/modules/hal/resources/hal-resource"; import {buildApiV3Filter} from "core-components/api/api-v3/api-v3-filter-builder"; import {UserResource} from 'core-app/modules/hal/resources/user-resource'; import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource'; -import {input} from "reactivestates"; import {WorkPackageChangeset} from "core-components/wp-edit/work-package-changeset"; import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource"; import {SubprojectBoardHeaderComponent} from "core-app/modules/boards/board/board-actions/subproject/subproject-board-header.component"; import {CachedBoardActionService} from "core-app/modules/boards/board/board-actions/cached-board-action.service"; +import {ImageHelpers} from "core-app/helpers/images/path-helper"; @Injectable() export class BoardSubprojectActionService extends CachedBoardActionService { filterName = 'onlySubproject'; - text = this.I18n.t('js.boards.board_type.action_by_attribute', - { attribute: this.I18n.t('js.boards.board_type.action_type.subproject')}) ; + text = this.I18n.t('js.boards.board_type.action_type.subproject'); description = this.I18n.t('js.boards.board_type.action_text_subprojects'); @@ -23,6 +22,8 @@ export class BoardSubprojectActionService extends CachedBoardActionService { icon = 'icon-projects'; + image = ImageHelpers.imagePath('board_creation_modal/subproject.svg'); + get localizedName() { return this.I18n.t('js.work_packages.properties.subproject'); } diff --git a/frontend/src/app/modules/boards/board/board-actions/subtasks/board-subtasks-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/subtasks/board-subtasks-action.service.ts index 5b0d833f2c..3529689c79 100644 --- a/frontend/src/app/modules/boards/board/board-actions/subtasks/board-subtasks-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/subtasks/board-subtasks-action.service.ts @@ -8,13 +8,13 @@ import {ApiV3FilterBuilder} from "core-components/api/api-v3/api-v3-filter-build import {SubtasksBoardHeaderComponent} from "core-app/modules/boards/board/board-actions/subtasks/subtasks-board-header.component"; import {QueryResource} from "core-app/modules/hal/resources/query-resource"; import {WorkPackageChangeset} from "core-components/wp-edit/work-package-changeset"; +import {ImageHelpers} from "core-app/helpers/images/path-helper"; @Injectable() export class BoardSubtasksActionService extends BoardActionService { filterName = 'parent'; - text = this.I18n.t('js.boards.board_type.action_by_attribute', - { attribute: this.I18n.t('js.boards.board_type.action_type.subtasks') }); + text = this.I18n.t('js.boards.board_type.action_type.subtasks'); description = this.I18n.t('js.boards.board_type.action_text_subtasks'); @@ -22,6 +22,8 @@ export class BoardSubtasksActionService extends BoardActionService { icon = 'icon-hierarchy'; + image = ImageHelpers.imagePath('board_creation_modal/parent-child.svg'); + public get localizedName() { return this.I18n.t('js.boards.board_type.action_type.subtasks'); } diff --git a/frontend/src/app/modules/boards/board/board-actions/version/version-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/version/version-action.service.ts index db0b308751..6a7c28cb91 100644 --- a/frontend/src/app/modules/boards/board/board-actions/version/version-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/version/version-action.service.ts @@ -13,6 +13,7 @@ import {VersionBoardHeaderComponent} from "core-app/modules/boards/board/board-a import {FormResource} from "core-app/modules/hal/resources/form-resource"; import {InjectField} from "core-app/helpers/angular/inject-field.decorator"; import {CachedBoardActionService} from "core-app/modules/boards/board/board-actions/cached-board-action.service"; +import {ImageHelpers} from "core-app/helpers/images/path-helper"; @Injectable() export class BoardVersionActionService extends CachedBoardActionService { @@ -21,16 +22,16 @@ export class BoardVersionActionService extends CachedBoardActionService { filterName = 'version'; - text = this.I18n.t('js.boards.board_type.action_by_attribute', - { attribute: this.I18n.t('js.boards.board_type.action_type.version')}); + text = this.I18n.t('js.boards.board_type.action_type.version'); - description = this.I18n.t('js.boards.board_type.action_text', - { attribute: this.I18n.t('js.boards.board_type.action_type.version')}); + description = this.I18n.t('js.boards.board_type.action_text_version'); label = this.I18n.t('js.boards.add_list_modal.labels.version'); icon = 'icon-getting-started'; + image = ImageHelpers.imagePath('board_creation_modal/version.svg'); + private writable$:Promise; public get localizedName() { diff --git a/frontend/src/app/modules/boards/new-board-modal/new-board-modal.component.ts b/frontend/src/app/modules/boards/new-board-modal/new-board-modal.component.ts index d06bebf571..bcd8c54b90 100644 --- a/frontend/src/app/modules/boards/new-board-modal/new-board-modal.component.ts +++ b/frontend/src/app/modules/boards/new-board-modal/new-board-modal.component.ts @@ -37,7 +37,9 @@ import {BoardService} from "core-app/modules/boards/board/board.service"; import {BoardActionsRegistryService} from "core-app/modules/boards/board/board-actions/board-actions-registry.service"; import {LoadingIndicatorService} from "core-app/modules/common/loading-indicator/loading-indicator.service"; import {HalResourceNotificationService} from "core-app/modules/hal/services/hal-resource-notification.service"; -import { ITileViewEntry } from '../tile-view/tile-view.component'; +import {ITileViewEntry} from '../tile-view/tile-view.component'; +import {ImageHelpers} from "core-app/helpers/images/path-helper"; +import imagePath = ImageHelpers.imagePath; @Component({ @@ -85,19 +87,26 @@ export class NewBoardModalComponent extends OpModalComponent { public createBoard(attribute:string) { if (attribute === 'basic') { - this.createFree(); - } - else { + this.createFree(); + } else { this.createAction(attribute); } } + private initiateTiles() { - this.available.unshift({attribute:'basic', text:this.text.free_board, - icon:'icon-boards', description:this.text.free_board_text}); + this.available.unshift({ + attribute: 'basic', + text: this.text.free_board, + icon: 'icon-boards', + description: this.text.free_board_text, + image: imagePath('board_creation_modal/lists.svg') + }); this.addIcon(this.available); this.addDescription(this.available); this.addText(this.available); + this.addImage(this.available); } + private createFree() { this.create({ type: 'free' }); } @@ -121,13 +130,16 @@ export class NewBoardModalComponent extends OpModalComponent { this.halNotification.handleRawError(error); }); } + private addDescription(tiles:ITileViewEntry[]) { tiles.forEach(element => { if (element.attribute !== 'basic') { const service = this.boardActionRegistry.get(element.attribute!); - element.description = service.description; } + element.description = service.description; + } }); } + private addIcon(tiles:ITileViewEntry[]) { tiles.forEach(element => { if (element.attribute !== 'basic') { @@ -136,6 +148,7 @@ export class NewBoardModalComponent extends OpModalComponent { } }); } + private addText(tiles:ITileViewEntry[]) { tiles.forEach(element => { if (element.attribute !== 'basic') { @@ -144,4 +157,13 @@ export class NewBoardModalComponent extends OpModalComponent { } }); } + + private addImage(tiles:ITileViewEntry[]) { + tiles.forEach(element => { + if (element.attribute !== 'basic') { + const service = this.boardActionRegistry.get(element.attribute!); + element.image = service.image; + } + }); + } } diff --git a/frontend/src/app/modules/boards/tile-view/tile-view.component.html b/frontend/src/app/modules/boards/tile-view/tile-view.component.html index ed30e3e2de..9ee4cc6a7c 100644 --- a/frontend/src/app/modules/boards/tile-view/tile-view.component.html +++ b/frontend/src/app/modules/boards/tile-view/tile-view.component.html @@ -1,14 +1,12 @@
-
- -
- - {{ tile.text | titlecase}} -
- -

+
+ +
+ {{ tile.text | titlecase}} +

+
\ No newline at end of file diff --git a/frontend/src/app/modules/boards/tile-view/tile-view.component.sass b/frontend/src/app/modules/boards/tile-view/tile-view.component.sass index 5cc1bc7c62..42acc83adc 100644 --- a/frontend/src/app/modules/boards/tile-view/tile-view.component.sass +++ b/frontend/src/app/modules/boards/tile-view/tile-view.component.sass @@ -1,32 +1,40 @@ .tile-blocks--container display: grid - grid-template: repeat(auto-fit, 220px) / repeat(auto-fit, 450px) - grid-auto-rows: 220px + grid-template: repeat(auto-fit, 200px) / repeat(auto-fit, 450px) + grid-auto-rows: 200px grid-column-gap: 10px grid-row-gap: 10px - min-width: 500px - margin: 5px + min-width: 910px + .tile-block - border-radius: 3px + border-radius: 10px display: grid grid-template: 95px 1fr / 1fr + grid-template-columns: auto 1fr auto + grid-template-rows: auto grid-row-gap: 5px justify-items: left - background: #f8f8f8 + background: #f7fafc &:hover - outline: 1px solid grey text-decoration: none + border: 1px solid grey + border-radius: 10px !important + cursor: pointer .tile-block--icon font-size: 50px -.tile-block-header - padding-left: 10px +.tile-block-title + padding-top: 30px color: var(--primary-color-dark) - display: flex - align-items: center + display: block + text-align: left + font-weight: bolder .tile-block p - text-align: left \ No newline at end of file + text-align: left + width: 90% + + diff --git a/frontend/src/app/modules/boards/tile-view/tile-view.component.spec.ts b/frontend/src/app/modules/boards/tile-view/tile-view.component.spec.ts index 9ead365954..aa03296dc6 100644 --- a/frontend/src/app/modules/boards/tile-view/tile-view.component.spec.ts +++ b/frontend/src/app/modules/boards/tile-view/tile-view.component.spec.ts @@ -2,6 +2,8 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {DebugElement} from '@angular/core'; import {By} from '@angular/platform-browser'; import {TileViewComponent} from './tile-view.component'; +import {ImageHelpers} from "core-app/helpers/images/path-helper"; +import imagePath = ImageHelpers.imagePath; describe('shows tiles', () => { let app:TileViewComponent; @@ -9,8 +11,11 @@ describe('shows tiles', () => { let element:DebugElement; let tilesStub = [{ - attribute: 'basic', text: 'Basic board', - icon: 'icon-boards', description: `Create a board in which you can freely + attribute: 'basic', + text: 'Basic board', + icon: 'icon-boards', + image: imagePath('board_creation_modal/lists.svg'), + description: `Create a board in which you can freely create lists and order your work packages within. Moving work packages between lists do not change the work package itself.` }]; @@ -37,13 +42,13 @@ describe('shows tiles', () => { it('should show each tile', () => { fixture.detectChanges(); let tile:HTMLElement = element.query(By.css('.tile-block')).nativeElement; - expect(tile.textContent).toContain('Basic Board'); + expect(tile.textContent).toContain('Basic'); }); - it('should show the icon', () => { + it('should show the image', () => { fixture.detectChanges(); - let tile = document.querySelector('.tile-block--icon'); + let tile = document.querySelector('.tile-block-image'); expect(document.contains(tile)).toBeTruthy(); }); diff --git a/frontend/src/app/modules/boards/tile-view/tile-view.component.ts b/frontend/src/app/modules/boards/tile-view/tile-view.component.ts index f7ee7b5c21..a8f98d4784 100644 --- a/frontend/src/app/modules/boards/tile-view/tile-view.component.ts +++ b/frontend/src/app/modules/boards/tile-view/tile-view.component.ts @@ -5,6 +5,7 @@ export interface ITileViewEntry { attribute:string; icon:string; description:string; + image:string; } @Component({ diff --git a/frontend/src/assets/images/board_creation_modal/assignees.svg b/frontend/src/assets/images/board_creation_modal/assignees.svg new file mode 100644 index 0000000000..6c82d4f6b5 --- /dev/null +++ b/frontend/src/assets/images/board_creation_modal/assignees.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/images/board_creation_modal/lists.svg b/frontend/src/assets/images/board_creation_modal/lists.svg new file mode 100644 index 0000000000..ab01ce7dae --- /dev/null +++ b/frontend/src/assets/images/board_creation_modal/lists.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/board_creation_modal/parent-child.svg b/frontend/src/assets/images/board_creation_modal/parent-child.svg new file mode 100644 index 0000000000..11b8264302 --- /dev/null +++ b/frontend/src/assets/images/board_creation_modal/parent-child.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/board_creation_modal/status.svg b/frontend/src/assets/images/board_creation_modal/status.svg new file mode 100644 index 0000000000..0c70d9cce5 --- /dev/null +++ b/frontend/src/assets/images/board_creation_modal/status.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/src/assets/images/board_creation_modal/subproject.svg b/frontend/src/assets/images/board_creation_modal/subproject.svg new file mode 100644 index 0000000000..e9e594b26e --- /dev/null +++ b/frontend/src/assets/images/board_creation_modal/subproject.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/board_creation_modal/version.svg b/frontend/src/assets/images/board_creation_modal/version.svg new file mode 100644 index 0000000000..65f814afec --- /dev/null +++ b/frontend/src/assets/images/board_creation_modal/version.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/modules/boards/config/locales/js-en.yml b/modules/boards/config/locales/js-en.yml index 78748912fa..4a817ebdd2 100644 --- a/modules/boards/config/locales/js-en.yml +++ b/modules/boards/config/locales/js-en.yml @@ -36,28 +36,36 @@ en: click_to_remove_list: "Click to remove this list" board_type: text: 'Board type' - free: 'Basic board' + free: 'basic' select_board_type: 'Please choose the type of board you need.' free_text: > - A board in which you can freely create lists and order your work packages within. - Dragging work packages between lists do not change the work package itself. + Start from scratch with a blank board. + Manually add cards and columns to this board. action: 'Action board' action_by_attribute: 'Action board (%{attribute})' action_text: > A board with filtered lists on %{attribute} attribute. Moving work packages to other lists will update their attribute. action_text_subprojects: > - A board with lists of subprojects of this project and their work packages as list items. - Dragging work packages to other lists will move them to the corresponding subproject. + Board with automated columns for subprojects. + Ideal for handling permissions across stakeholders. action_text_subtasks: > - A board with lists of selected parents and their child work packages as list items. - Dragging work packages to other lists will update the parent accordingly. + Board with automated columns for sub-elements. + Dragging work packages to other lists updates the parent accordingly. + action_text_status: > + Basic kanban style board with columns for status such as To Do, In Progress, Done. + action_text_assignee: > + Board with automated columns based on assigned users. + Ideal for dispatching work packages. + action_text_version: > + Board with automated columns based on the version attribute. + Ideal for planning software releases. action_type: assignee: assignee status: status version: version subproject: subproject - subtasks: children + subtasks: Parent_child select_attribute: "Action attribute" add_list_modal: diff --git a/modules/boards/spec/features/action_boards/subtasks_board_spec.rb b/modules/boards/spec/features/action_boards/subtasks_board_spec.rb index a2c4cdfb7a..11b5c2e98e 100644 --- a/modules/boards/spec/features/action_boards/subtasks_board_spec.rb +++ b/modules/boards/spec/features/action_boards/subtasks_board_spec.rb @@ -63,7 +63,7 @@ describe 'Subtasks action board', type: :feature, js: true do board_index.visit! # Create new board - board_page = board_index.create_board action: :Children, expect_empty: true + board_page = board_index.create_board action: :Parent_child, expect_empty: true # Expect we can add a work package column board_page.add_list option: 'Parent WP' @@ -87,7 +87,7 @@ describe 'Subtasks action board', type: :feature, js: true do board_index.visit! # Create new board - board_page = board_index.create_board action: :Children, expect_empty: true + board_page = board_index.create_board action: :Parent_child, expect_empty: true # Expect we can add a child 1 board_page.add_list option: 'Parent WP' @@ -100,7 +100,7 @@ describe 'Subtasks action board', type: :feature, js: true do board_page.expect_movable 'Parent WP', 'Child', movable: true board_page.board(reload: true) do |board| - expect(board.name).to eq 'Action board (children)' + expect(board.name).to eq 'Action board (Parent_child)' queries = board.contained_queries expect(queries.count).to eq(1) @@ -158,7 +158,7 @@ describe 'Subtasks action board', type: :feature, js: true do it 'prevents adding a work package to its own column' do board_index.visit! - board_page = board_index.create_board action: :Children, expect_empty: true + board_page = board_index.create_board action: :Parent_child, expect_empty: true board_page.add_list option: 'Parent WP' board_page.expect_list 'Parent WP' board_page.expect_card 'Parent WP', 'Child' diff --git a/modules/boards/spec/features/support/board_index_page.rb b/modules/boards/spec/features/support/board_index_page.rb index e0052a5ca2..0a5028668c 100644 --- a/modules/boards/spec/features/support/board_index_page.rb +++ b/modules/boards/spec/features/support/board_index_page.rb @@ -60,9 +60,9 @@ module Pages page.find('.toolbar-item a', text: 'Board').click if action == nil - find('.tile-block', text: 'Basic Board').click + find('.tile-block-title', text: 'Basic').click else - find('.tile-block', text: "Action Board (#{action.to_s})").click + find('.tile-block-title', text: action.to_s).click end if expect_empty