Adds a new view with tiles to the motion-list
- New components 'Tile' and 'GridLayout' - Adds a grid-layout to the view - The grid-layout can have an optional title section
This commit is contained in:
parent
1599e91fa5
commit
39d891f851
@ -0,0 +1,57 @@
|
|||||||
|
<mat-card
|
||||||
|
class="block-tile"
|
||||||
|
[style.display]="orientation === 'horizontal' ? 'flex' : 'block'"
|
||||||
|
(click)="onClick($event)"
|
||||||
|
>
|
||||||
|
<div [ngSwitch]="blockType" class="block-node-container">
|
||||||
|
<div *ngSwitchCase="'text'" class="tile-text stretch-to-fill-parent" [style.border-radius]="orientation === 'horizontal' ? '4px 0 0 4px' : '4px 4px 0 0'">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ block }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div *ngSwitchCase="'image'">
|
||||||
|
<img mat-card-image [src]="block" alt="" />
|
||||||
|
</div>
|
||||||
|
<div *ngSwitchCase="'node'" class="tile-text stretch-to-fill-parent" [style.border-radius]="orientation === 'horizontal' ? '4px 0 0 4px' : '4px 4px 0 0'">
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="blockNode"
|
||||||
|
[ngTemplateOutletContext]="data"></ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tile-content-node-container">
|
||||||
|
<mat-card-content class="tile-content">
|
||||||
|
<mat-card-title class="tile-content-title stretch-to-fill-parent" *ngIf="!only || only === 'title'">
|
||||||
|
{{ title }}
|
||||||
|
</mat-card-title>
|
||||||
|
<mat-card-subtitle class="tile-content-subtitle" *ngIf="subtitle">
|
||||||
|
{{ subtitle }}
|
||||||
|
</mat-card-subtitle>
|
||||||
|
<mat-divider *ngIf="!only"></mat-divider>
|
||||||
|
<div *ngIf="!only || only === 'content'" class="tile-content-extra">
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="contentNode"
|
||||||
|
[ngTemplateOutletContext]="data"></ng-container>
|
||||||
|
</div>
|
||||||
|
<mat-card-actions *ngIf="showActions">
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="actionNode"></ng-container>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card-content>
|
||||||
|
</div>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<ng-template #blockNode>
|
||||||
|
<ng-content select=".block-node"></ng-content>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #contentNode>
|
||||||
|
<ng-content select=".block-content-node"></ng-content>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #actionNode>
|
||||||
|
<ng-content select=".block-action-node"></ng-content>
|
||||||
|
</ng-template>
|
@ -0,0 +1,60 @@
|
|||||||
|
@import '~@angular/material/theming';
|
||||||
|
|
||||||
|
@mixin os-block-tile-style($theme) {
|
||||||
|
$primary: map-get(
|
||||||
|
$map: $theme,
|
||||||
|
$key: primary
|
||||||
|
);
|
||||||
|
|
||||||
|
.block-tile {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.block-node-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 50%;
|
||||||
|
min-width: 30%;
|
||||||
|
|
||||||
|
.tile-text {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: mat-color($primary, lighter);
|
||||||
|
|
||||||
|
table {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-content-node-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin: 8px 16px !important;
|
||||||
|
|
||||||
|
.tile-content {
|
||||||
|
margin-bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.tile-content-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: unset;
|
||||||
|
margin-bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-content-extra {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 5px 8px 0px rgba(0, 0, 0, 0.14),
|
||||||
|
0px 1px 14px 0px rgba(0, 0, 0, 0.12) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BlockTileComponent } from './block-tile.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('BlockTileComponent', () => {
|
||||||
|
let component: BlockTileComponent;
|
||||||
|
let fixture: ComponentFixture<BlockTileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule]
|
||||||
|
// declarations: [BlockTileComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BlockTileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,115 @@
|
|||||||
|
import { Component, TemplateRef, ContentChild, Input } from '@angular/core';
|
||||||
|
import { TileComponent } from '../tile/tile.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration to define if the content is only text or a node.
|
||||||
|
*/
|
||||||
|
export enum ContentType {
|
||||||
|
text = 'text',
|
||||||
|
node = 'node'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration to define of which the big block is.
|
||||||
|
*/
|
||||||
|
export enum BlockType {
|
||||||
|
text = 'text',
|
||||||
|
node = 'node',
|
||||||
|
picture = 'picture'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells, whether to align the block and content next to each other or one below the other.
|
||||||
|
*/
|
||||||
|
export enum Orientation {
|
||||||
|
horizontal = 'horizontal',
|
||||||
|
vertical = 'vertical'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells, if the tile should only display the content or the title in the content part.
|
||||||
|
*/
|
||||||
|
export enum ShowOnly {
|
||||||
|
title = 'title',
|
||||||
|
content = 'content'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class, that extends the `tile.component`.
|
||||||
|
* This class specifies a tile with two separated parts: the block and the content part.
|
||||||
|
* The block part is like a header, the content part contains further information.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-block-tile',
|
||||||
|
templateUrl: './block-tile.component.html',
|
||||||
|
styleUrls: ['./block-tile.component.scss']
|
||||||
|
})
|
||||||
|
export class BlockTileComponent extends TileComponent {
|
||||||
|
/**
|
||||||
|
* Reference to the content of the content part.
|
||||||
|
*/
|
||||||
|
@ContentChild(TemplateRef)
|
||||||
|
public contentNode: TemplateRef<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the block part, if it is a node.
|
||||||
|
*/
|
||||||
|
@ContentChild(TemplateRef)
|
||||||
|
public blockNode: TemplateRef<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the action buttons in the content part, if used.
|
||||||
|
*/
|
||||||
|
@ContentChild(TemplateRef)
|
||||||
|
public actionNode: TemplateRef<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the type of the primary block.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public blockType: BlockType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input for the primary block content.
|
||||||
|
* Only string for the source of a picture or text.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public block: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the type of the content.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public contentType: ContentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title in the content part.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subtitle in the content part.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public subtitle: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the orientation -
|
||||||
|
* whether the block part should be displayed above the content or next to it.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public orientation: Orientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells, whether the tile should display only one of `Title` or `Content` in the content part.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public only: ShowOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean, whether to show action buttons in the content part.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public showActions: boolean;
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<div
|
||||||
|
*ngIf="title"
|
||||||
|
class="heading-container"
|
||||||
|
[ngClass]="{'no-space': noSpace}"
|
||||||
|
>
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="os-grid"
|
||||||
|
[ngClass]="{'no-space': noSpace}"
|
||||||
|
>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
@ -0,0 +1,17 @@
|
|||||||
|
.heading-container {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-container.no-space {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-grid {
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-grid.no-space {
|
||||||
|
padding: 0;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GridLayoutComponent } from './grid-layout.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('GridLayoutComponent', () => {
|
||||||
|
let component: GridLayoutComponent;
|
||||||
|
let fixture: ComponentFixture<GridLayoutComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule]
|
||||||
|
// declarations: [GridLayoutComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GridLayoutComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to create a `grid-layout`.
|
||||||
|
* Aligns items in a flex display.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-grid-layout',
|
||||||
|
templateUrl: './grid-layout.component.html',
|
||||||
|
styleUrls: ['./grid-layout.component.scss']
|
||||||
|
})
|
||||||
|
export class GridLayoutComponent {
|
||||||
|
/**
|
||||||
|
* Property for an optional title.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the grid layout should have no space.
|
||||||
|
* This contains the padding for the grid itself and the margin of the tiles.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public noSpace: boolean;
|
||||||
|
}
|
@ -20,6 +20,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<span class="extra-controls-wrapper">
|
||||||
|
<ng-content select=".extra-controls-slot"></ng-content>
|
||||||
|
</span>
|
||||||
|
|
||||||
<button mat-button *ngIf="hasFilters" (click)="filterMenu.opened ? filterMenu.close() : filterMenu.open()">
|
<button mat-button *ngIf="hasFilters" (click)="filterMenu.opened ? filterMenu.close() : filterMenu.open()">
|
||||||
<span *ngIf="!filterService.activeFilterCount" class="upper" translate> Filter </span>
|
<span *ngIf="!filterService.activeFilterCount" class="upper" translate> Filter </span>
|
||||||
<span *ngIf="filterService.activeFilterCount">
|
<span *ngIf="filterService.activeFilterCount">
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="tileContext"
|
||||||
|
[ngTemplateOutletContext]="data">
|
||||||
|
</ng-container>
|
94
client/src/app/shared/components/tile/tile.component.scss
Normal file
94
client/src/app/shared/components/tile/tile.component.scss
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
@import '~@angular/material/theming';
|
||||||
|
@import '../../../../assets/styles/media-queries.scss';
|
||||||
|
|
||||||
|
@mixin os-tile-style($theme) {
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
|
||||||
|
@include set-breakpoint-lower(xs) {
|
||||||
|
@for $i from 1 through 4 {
|
||||||
|
.os-tile--xs-#{$i} {
|
||||||
|
width: get-width('xs', $i, true);
|
||||||
|
}
|
||||||
|
.os-grid.no-space > .os-tile--xs-#{$i} {
|
||||||
|
width: get-width('xs', $i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include set-breakpoint-between(xs, sm) {
|
||||||
|
@for $i from 1 through 8 {
|
||||||
|
.os-tile--sm-#{$i} {
|
||||||
|
width: get-width('sm', $i, true);
|
||||||
|
}
|
||||||
|
.os-grid.no-space > .os-tile--sm-#{$i} {
|
||||||
|
width: get-width('sm', $i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include set-breakpoint-between(sm, lg) {
|
||||||
|
@for $i from 1 through 12 {
|
||||||
|
.os-tile--md-#{$i} {
|
||||||
|
width: get-width('md', $i, true);
|
||||||
|
}
|
||||||
|
.os-grid.no-space > .os-tile--md-#{$i} {
|
||||||
|
width: get-width('md', $i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include set-breakpoint-upper(lg) {
|
||||||
|
@for $i from 1 through 16 {
|
||||||
|
.os-tile--lg-#{$i} {
|
||||||
|
width: get-width('lg', $i, true);
|
||||||
|
}
|
||||||
|
.os-grid.no-space > .os-tile--lg-#{$i} {
|
||||||
|
width: get-width('md', $i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-tile {
|
||||||
|
height: calc(100% - 16px);
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
.os-grid.no-space > .os-tile {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function calculates the width to the given device-size */
|
||||||
|
@function get-width($property, $size, $space) {
|
||||||
|
@if $property == 'xs' {
|
||||||
|
@if $space == true {
|
||||||
|
@return calc(#{$size} / 4 * 100% - 16px);
|
||||||
|
} @else {
|
||||||
|
@return $size / 4 * 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $property == 'sm' {
|
||||||
|
@if $space == true {
|
||||||
|
@return calc(#{$size} / 8 * 100% - 16px);
|
||||||
|
} @else {
|
||||||
|
@return $size / 8 * 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $property == 'md' {
|
||||||
|
@if $space == true {
|
||||||
|
@return calc(#{$size} / 12 * 100% - 16px);
|
||||||
|
} @else {
|
||||||
|
@return $size / 12 * 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $property == 'lg' {
|
||||||
|
@if $space == true {
|
||||||
|
@return calc(#{$size} / 16 * 100% - 16px);
|
||||||
|
} @else {
|
||||||
|
@return $size / 16 * 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
client/src/app/shared/components/tile/tile.component.spec.ts
Normal file
24
client/src/app/shared/components/tile/tile.component.spec.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TileComponent } from './tile.component';
|
||||||
|
|
||||||
|
describe('TileComponent', () => {
|
||||||
|
let component: TileComponent;
|
||||||
|
let fixture: ComponentFixture<TileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [TileComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
185
client/src/app/shared/components/tile/tile.component.ts
Normal file
185
client/src/app/shared/components/tile/tile.component.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { Component, OnInit, Input, TemplateRef, ContentChild, Output, EventEmitter, HostBinding } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface, that defines the type of the `ClickEvent`.
|
||||||
|
*/
|
||||||
|
export interface EventObject {
|
||||||
|
data: Object;
|
||||||
|
source: MouseEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface, that defines the possible shape of `@Input() preferredSize`
|
||||||
|
*/
|
||||||
|
export interface SizeObject {
|
||||||
|
mobile?: number;
|
||||||
|
tablet?: number;
|
||||||
|
medium?: number;
|
||||||
|
large?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component, that acts like a tile in a grid-layout.
|
||||||
|
* This component accepts several attributes, that define the size of it.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-tile',
|
||||||
|
templateUrl: './tile.component.html',
|
||||||
|
styleUrls: ['./tile.component.scss']
|
||||||
|
})
|
||||||
|
export class TileComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* HostBinding to add the necessary classes to the host element `os-tile`.
|
||||||
|
*/
|
||||||
|
@HostBinding('class')
|
||||||
|
public get classes(): string {
|
||||||
|
return (
|
||||||
|
'os-tile' +
|
||||||
|
' os-tile--xs-' +
|
||||||
|
this.mobileSize +
|
||||||
|
' os-tile--sm-' +
|
||||||
|
this.tabletSize +
|
||||||
|
' os-tile--md-' +
|
||||||
|
this.mediumSize +
|
||||||
|
' os-tile--lg-' +
|
||||||
|
this.largeSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the dynamic content.
|
||||||
|
*/
|
||||||
|
@ContentChild(TemplateRef)
|
||||||
|
public tileContext: TemplateRef<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional data, that can be passed to the component.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public data: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional input to define the preferred size of the tile.
|
||||||
|
* This can be a number, which defines the size in every device resolution,
|
||||||
|
* or an object, which defines the size for each one resolution separately.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public preferredSize: number | SizeObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventEmitter for the `ClickEvent`.
|
||||||
|
*/
|
||||||
|
@Output()
|
||||||
|
public clicked: EventEmitter<EventObject> = new EventEmitter<EventObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property, which defines the size of the tile @Mobile
|
||||||
|
*/
|
||||||
|
public mobileSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property, which defines the size of the tile @Tablet
|
||||||
|
*/
|
||||||
|
public tabletSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property, which defines the size of the tile @Medium devices
|
||||||
|
*/
|
||||||
|
public mediumSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property, which defines the size of the tile @Large devices
|
||||||
|
*/
|
||||||
|
public largeSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OnInit method.
|
||||||
|
* The preferred size for the tile will calculated.
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
if (!this.preferredSize) {
|
||||||
|
this.preferredSize = 4;
|
||||||
|
}
|
||||||
|
if (typeof this.preferredSize === 'number') {
|
||||||
|
this.setLargeSize(this.preferredSize);
|
||||||
|
this.setMediumSize(this.preferredSize);
|
||||||
|
this.setTabletSize(this.preferredSize);
|
||||||
|
this.setMobileSize(this.preferredSize);
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
mobile,
|
||||||
|
tablet,
|
||||||
|
medium,
|
||||||
|
large
|
||||||
|
}: { mobile?: number; tablet?: number; medium?: number; large?: number } = this.preferredSize;
|
||||||
|
this.setMobileSize(mobile);
|
||||||
|
this.setTabletSize(tablet);
|
||||||
|
this.setMediumSize(medium);
|
||||||
|
this.setLargeSize(large);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function, that fires when the user clicks on the tile.
|
||||||
|
*
|
||||||
|
* @param event The source event on click.
|
||||||
|
*/
|
||||||
|
public onClick(event: MouseEvent): void {
|
||||||
|
this.clicked.emit({
|
||||||
|
data: this.data,
|
||||||
|
source: event
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the size @Mobile
|
||||||
|
*
|
||||||
|
* @param size how great the tile should be
|
||||||
|
*/
|
||||||
|
private setMobileSize(size: number): void {
|
||||||
|
if (size <= 4 && size >= 0) {
|
||||||
|
this.mobileSize = size;
|
||||||
|
} else {
|
||||||
|
this.mobileSize = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the size @Tablet
|
||||||
|
*
|
||||||
|
* @param size how great the tile should be
|
||||||
|
*/
|
||||||
|
private setTabletSize(size: number): void {
|
||||||
|
if (size <= 8 && size >= 0) {
|
||||||
|
this.tabletSize = size;
|
||||||
|
} else {
|
||||||
|
this.tabletSize = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the size of the tile @Medium devices
|
||||||
|
*
|
||||||
|
* @param size how great the tile should be
|
||||||
|
*/
|
||||||
|
private setMediumSize(size: number): void {
|
||||||
|
if (size <= 12 && size >= 0) {
|
||||||
|
this.mediumSize = size;
|
||||||
|
} else {
|
||||||
|
this.mediumSize = 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the size of the tile @Large devices
|
||||||
|
*
|
||||||
|
* @param size how great the tile should be
|
||||||
|
*/
|
||||||
|
private setLargeSize(size: number): void {
|
||||||
|
if (size <= 16 && size >= 0) {
|
||||||
|
this.largeSize = size;
|
||||||
|
} else {
|
||||||
|
this.largeSize = 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,8 @@ import {
|
|||||||
MatStepperModule,
|
MatStepperModule,
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatBottomSheetModule,
|
MatBottomSheetModule,
|
||||||
MatSliderModule
|
MatSliderModule,
|
||||||
|
MatDividerModule
|
||||||
} from '@angular/material';
|
} from '@angular/material';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatChipsModule } from '@angular/material';
|
import { MatChipsModule } from '@angular/material';
|
||||||
@ -84,6 +85,9 @@ import { CountdownTimeComponent } from './components/contdown-time/countdown-tim
|
|||||||
import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
|
import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
|
||||||
import { PrecisionPipe } from './pipes/precision.pipe';
|
import { PrecisionPipe } from './pipes/precision.pipe';
|
||||||
import { SpeakerButtonComponent } from './components/speaker-button/speaker-button.component';
|
import { SpeakerButtonComponent } from './components/speaker-button/speaker-button.component';
|
||||||
|
import { GridLayoutComponent } from './components/grid-layout/grid-layout.component';
|
||||||
|
import { TileComponent } from './components/tile/tile.component';
|
||||||
|
import { BlockTileComponent } from './components/block-tile/block-tile.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -133,6 +137,7 @@ import { SpeakerButtonComponent } from './components/speaker-button/speaker-butt
|
|||||||
MatStepperModule,
|
MatStepperModule,
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatSliderModule,
|
MatSliderModule,
|
||||||
|
MatDividerModule,
|
||||||
OwlDateTimeModule,
|
OwlDateTimeModule,
|
||||||
OwlNativeDateTimeModule,
|
OwlNativeDateTimeModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
@ -176,6 +181,7 @@ import { SpeakerButtonComponent } from './components/speaker-button/speaker-butt
|
|||||||
MatButtonToggleModule,
|
MatButtonToggleModule,
|
||||||
MatStepperModule,
|
MatStepperModule,
|
||||||
MatSliderModule,
|
MatSliderModule,
|
||||||
|
MatDividerModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
NgxMatSelectSearchModule,
|
NgxMatSelectSearchModule,
|
||||||
FileDropModule,
|
FileDropModule,
|
||||||
@ -207,8 +213,11 @@ import { SpeakerButtonComponent } from './components/speaker-button/speaker-butt
|
|||||||
CountdownTimeComponent,
|
CountdownTimeComponent,
|
||||||
MediaUploadContentComponent,
|
MediaUploadContentComponent,
|
||||||
PrecisionPipe,
|
PrecisionPipe,
|
||||||
ScrollingModule,
|
SpeakerButtonComponent,
|
||||||
SpeakerButtonComponent
|
GridLayoutComponent,
|
||||||
|
TileComponent,
|
||||||
|
BlockTileComponent,
|
||||||
|
ScrollingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
@ -237,7 +246,10 @@ import { SpeakerButtonComponent } from './components/speaker-button/speaker-butt
|
|||||||
CountdownTimeComponent,
|
CountdownTimeComponent,
|
||||||
MediaUploadContentComponent,
|
MediaUploadContentComponent,
|
||||||
PrecisionPipe,
|
PrecisionPipe,
|
||||||
SpeakerButtonComponent
|
SpeakerButtonComponent,
|
||||||
|
GridLayoutComponent,
|
||||||
|
TileComponent,
|
||||||
|
BlockTileComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||||
|
@ -23,116 +23,122 @@
|
|||||||
[sortService]="sortService"
|
[sortService]="sortService"
|
||||||
(searchFieldChange)="searchFilter($event)"
|
(searchFieldChange)="searchFilter($event)"
|
||||||
>
|
>
|
||||||
|
<mat-button-toggle-group *ngIf="isCategoryAvailable()" #group="matButtonToggleGroup" [value]="selectedView" (change)="onChangeView(group.value)" appearance="legacy" aria-label="Select view" class="extra-controls-slot select-view-wrapper">
|
||||||
|
<mat-button-toggle value="tiles" matTooltip="{{ 'Tile view' | translate }}"><mat-icon>view_module</mat-icon></mat-button-toggle>
|
||||||
|
<mat-button-toggle value="list" matTooltip="{{ 'List view' | translate }}"><mat-icon>view_headline</mat-icon></mat-button-toggle>
|
||||||
|
</mat-button-toggle-group>
|
||||||
</os-sort-filter-bar>
|
</os-sort-filter-bar>
|
||||||
|
|
||||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
<div [ngSwitch]="selectedView">
|
||||||
<!-- Selector column -->
|
<span *ngSwitchCase="'list'">
|
||||||
<ng-container matColumnDef="selector">
|
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header></mat-header-cell>
|
<!-- Selector column -->
|
||||||
<mat-cell *matCellDef="let motion">
|
<ng-container matColumnDef="selector">
|
||||||
<mat-icon>{{ isSelected(motion) ? 'check_circle' : '' }}</mat-icon>
|
<mat-header-cell *matHeaderCellDef mat-sort-header></mat-header-cell>
|
||||||
</mat-cell>
|
<mat-cell *matCellDef="let motion">
|
||||||
</ng-container>
|
<mat-icon>{{ isSelected(motion) ? 'check_circle' : '' }}</mat-icon>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Projector column -->
|
<!-- Projector column -->
|
||||||
<ng-container matColumnDef="projector">
|
<ng-container matColumnDef="projector">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let motion">
|
<mat-cell *matCellDef="let motion">
|
||||||
<os-projector-button [object]="motion"></os-projector-button>
|
<os-projector-button [object]="motion"></os-projector-button>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- identifier column -->
|
<!-- identifier column -->
|
||||||
<ng-container matColumnDef="identifier">
|
<ng-container matColumnDef="identifier">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let motion">
|
<mat-cell *matCellDef="let motion">
|
||||||
<div class="innerTable">
|
<div class="innerTable">
|
||||||
{{ motion.identifier }}
|
{{ motion.identifier }}
|
||||||
</div>
|
|
||||||
</mat-cell>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- title column -->
|
|
||||||
<ng-container matColumnDef="title">
|
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
|
||||||
<mat-cell *matCellDef="let motion">
|
|
||||||
<div class="innerTable max-width">
|
|
||||||
<!-- title line -->
|
|
||||||
<div class="title-line ellipsis-overflow">
|
|
||||||
<!-- favorite icon -->
|
|
||||||
<span *ngIf="motion.star" class="favorite-star">
|
|
||||||
<mat-icon inline>star</mat-icon>
|
|
||||||
</span>
|
|
||||||
<!-- attachment icon -->
|
|
||||||
<span class="attached-files" *ngIf="motion.hasAttachments()">
|
|
||||||
<mat-icon>attach_file</mat-icon>
|
|
||||||
</span>
|
|
||||||
<!-- title -->
|
|
||||||
<span class="motion-list-title">
|
|
||||||
{{ motion.title }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- submitters line -->
|
|
||||||
<div class="submitters-line ellipsis-overflow" *ngIf="motion.submitters.length">
|
|
||||||
<span translate>by</span> {{ motion.submitters }}
|
|
||||||
<span *osPerms="'motions.can_manage'">
|
|
||||||
·
|
|
||||||
<span translate>Sequential number</span>
|
|
||||||
{{ motion.id }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- state line-->
|
|
||||||
<div class="ellipsis-overflow white">
|
|
||||||
<mat-basic-chip *ngIf="motion.state" [ngClass]="motion.stateCssColor" [disabled]="true">
|
|
||||||
{{ getStateLabel(motion) }}
|
|
||||||
</mat-basic-chip>
|
|
||||||
</div>
|
|
||||||
<!-- recommendation line -->
|
|
||||||
<div
|
|
||||||
*ngIf="motion.recommendation && motion.state.next_states_id.length > 0"
|
|
||||||
class="ellipsis-overflow white spacer-top-3"
|
|
||||||
>
|
|
||||||
<mat-basic-chip class="bluegrey" [disabled]="true">
|
|
||||||
{{ getRecommendationLabel(motion) }}
|
|
||||||
</mat-basic-chip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-cell>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- state column -->
|
|
||||||
<ng-container matColumnDef="state">
|
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
|
||||||
<mat-cell (click)="openEditInfo(motion, $event)" *matCellDef="let motion">
|
|
||||||
<div class="fill">
|
|
||||||
<div class="innerTable state-column">
|
|
||||||
<div class="small ellipsis-overflow" *ngIf="motion.category">
|
|
||||||
<mat-icon>device_hub</mat-icon>
|
|
||||||
{{ motion.category }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="small ellipsis-overflow" *ngIf="motion.motion_block">
|
</mat-cell>
|
||||||
<mat-icon>widgets</mat-icon>
|
</ng-container>
|
||||||
{{ motion.motion_block.title }}
|
|
||||||
</div>
|
|
||||||
<div class="small ellipsis-overflow" *ngIf="motion.tags && motion.tags.length">
|
|
||||||
<mat-icon>local_offer</mat-icon>
|
|
||||||
<span *ngFor="let tag of motion.tags; let last = last">
|
|
||||||
{{ tag.getTitle() }}
|
|
||||||
<span *ngIf="!last">, </span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-cell>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Anchor column to open the separate tab -->
|
<!-- title column -->
|
||||||
<ng-container matColumnDef="anchor">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let motion">
|
<mat-cell *matCellDef="let motion">
|
||||||
<a [routerLink]="motion.id" *ngIf="!isMultiSelect"></a>
|
<div class="innerTable max-width">
|
||||||
</mat-cell>
|
<!-- title line -->
|
||||||
</ng-container>
|
<div class="title-line ellipsis-overflow">
|
||||||
|
<!-- favorite icon -->
|
||||||
|
<span *ngIf="motion.star" class="favorite-star">
|
||||||
|
<mat-icon inline>star</mat-icon>
|
||||||
|
</span>
|
||||||
|
<!-- attachment icon -->
|
||||||
|
<span class="attached-files" *ngIf="motion.hasAttachments()">
|
||||||
|
<mat-icon>attach_file</mat-icon>
|
||||||
|
</span>
|
||||||
|
<!-- title -->
|
||||||
|
<span class="motion-list-title">
|
||||||
|
{{ motion.title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- submitters line -->
|
||||||
|
<div class="submitters-line ellipsis-overflow" *ngIf="motion.submitters.length">
|
||||||
|
<span translate>by</span> {{ motion.submitters }}
|
||||||
|
<span *osPerms="'motions.can_manage'">
|
||||||
|
·
|
||||||
|
<span translate>Sequential number</span>
|
||||||
|
{{ motion.id }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- state line-->
|
||||||
|
<div class="ellipsis-overflow white">
|
||||||
|
<mat-basic-chip *ngIf="motion.state" [ngClass]="motion.stateCssColor" [disabled]="true">
|
||||||
|
{{ getStateLabel(motion) }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
</div>
|
||||||
|
<!-- recommendation line -->
|
||||||
|
<div
|
||||||
|
*ngIf="motion.recommendation && motion.state.next_states_id.length > 0"
|
||||||
|
class="ellipsis-overflow white"
|
||||||
|
>
|
||||||
|
<mat-basic-chip class="bluegrey" [disabled]="true">
|
||||||
|
{{ getRecommendationLabel(motion) }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- state column -->
|
||||||
|
<ng-container matColumnDef="state">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
||||||
|
<mat-cell (click)="openEditInfo(motion, $event)" *matCellDef="let motion">
|
||||||
|
<div class="fill">
|
||||||
|
<div class="innerTable state-column">
|
||||||
|
<div class="small ellipsis-overflow" *ngIf="motion.category">
|
||||||
|
<mat-icon>device_hub</mat-icon>
|
||||||
|
{{ motion.category }}
|
||||||
|
</div>
|
||||||
|
<div class="small ellipsis-overflow" *ngIf="motion.motion_block">
|
||||||
|
<mat-icon>widgets</mat-icon>
|
||||||
|
{{ motion.motion_block.title }}
|
||||||
|
</div>
|
||||||
|
<div class="small ellipsis-overflow" *ngIf="motion.tags && motion.tags.length">
|
||||||
|
<mat-icon>local_offer</mat-icon>
|
||||||
|
<span *ngFor="let tag of motion.tags; let last = last">
|
||||||
|
{{ tag.getTitle() }}
|
||||||
|
<span *ngIf="!last">, </span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Anchor column to open the separate tab -->
|
||||||
|
<ng-container matColumnDef="anchor">
|
||||||
|
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let motion">
|
||||||
|
<a [routerLink]="motion.id" *ngIf="!isMultiSelect"></a>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Speakers column -->
|
<!-- Speakers column -->
|
||||||
<ng-container matColumnDef="speakers">
|
<ng-container matColumnDef="speakers">
|
||||||
@ -142,17 +148,40 @@
|
|||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||||
<mat-row
|
<mat-row
|
||||||
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected' : ''"
|
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected' : ''"
|
||||||
(click)="selectItem(row, $event)"
|
(click)="selectItem(row, $event)"
|
||||||
*matRowDef="let row; columns: getColumnDefinition()"
|
*matRowDef="let row; columns: getColumnDefinition()"
|
||||||
class="lg"
|
class="lg"
|
||||||
>
|
>
|
||||||
</mat-row>
|
</mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="pageSize"></mat-paginator>
|
<mat-paginator class="on-transition-fade" [pageSizeOptions]="pageSize"></mat-paginator>
|
||||||
|
</span>
|
||||||
|
<span *ngSwitchCase="'tiles'">
|
||||||
|
<os-grid-layout>
|
||||||
|
<os-block-tile *ngFor="let tileCategory of tileCategories" (clicked)="changeToViewWithTileCategory(tileCategory)" [orientation]="'horizontal'" [only]="'title'" [blockType]="'node'" [data]="tileCategory" title="{{ tileCategory.name | translate }}">
|
||||||
|
<ng-container class="block-node">
|
||||||
|
<table matTooltip="{{ tileCategory.amountOfMotions }} {{ 'Motions' | translate }} – {{ tileCategory.name | translate }}">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="tile-block-title" [matBadge]="tileCategory.amountOfMotions" [matBadgeColor]="'accent'" [ngSwitch]="tileCategory.name">
|
||||||
|
<span *ngSwitchCase="'Favorites'"><mat-icon>star</mat-icon></span>
|
||||||
|
<span *ngSwitchCase="'No category'"><mat-icon>block</mat-icon></span>
|
||||||
|
<span *ngSwitchDefault>{{ tileCategory.prefix }}</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
</os-block-tile>
|
||||||
|
</os-grid-layout>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</mat-drawer-container>
|
</mat-drawer-container>
|
||||||
|
|
||||||
<mat-menu #motionListMenu="matMenu">
|
<mat-menu #motionListMenu="matMenu">
|
||||||
|
@ -7,6 +7,17 @@
|
|||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-button-toggle-group {
|
||||||
|
line-height: normal;
|
||||||
|
vertical-align: middle;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.os-listview-table {
|
.os-listview-table {
|
||||||
/** identifier */
|
/** identifier */
|
||||||
.mat-column-identifier {
|
.mat-column-identifier {
|
||||||
@ -72,3 +83,15 @@
|
|||||||
.max-width {
|
.max-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os-grid-layout {
|
||||||
|
.tile-block-title {
|
||||||
|
font-size: 36px;
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
font-size: 36px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -31,6 +31,16 @@ import { MotionXlsxExportService } from 'app/site/motions/services/motion-xlsx-e
|
|||||||
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
|
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { PdfError } from 'app/core/ui-services/pdf-document.service';
|
import { PdfError } from 'app/core/ui-services/pdf-document.service';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
interface TileCategoryInformation {
|
||||||
|
filter: string;
|
||||||
|
name: string;
|
||||||
|
prefix?: string;
|
||||||
|
condition: number | boolean | null;
|
||||||
|
amountOfMotions: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to describe possible values and changes for
|
* Interface to describe possible values and changes for
|
||||||
@ -79,6 +89,11 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
*/
|
*/
|
||||||
public infoDialog: InfoDialog;
|
public infoDialog: InfoDialog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to define the current selected view.
|
||||||
|
*/
|
||||||
|
public selectedView: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Columns to display in table when desktop view is available
|
* Columns to display in table when desktop view is available
|
||||||
*/
|
*/
|
||||||
@ -101,6 +116,17 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
public categories: ViewCategory[] = [];
|
public categories: ViewCategory[] = [];
|
||||||
public motionBlocks: ViewMotionBlock[] = [];
|
public motionBlocks: ViewMotionBlock[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of `TileCategoryInformation`.
|
||||||
|
* Necessary to not iterate over the values of the map below.
|
||||||
|
*/
|
||||||
|
public tileCategories: TileCategoryInformation[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of information about the categories relating to their id.
|
||||||
|
*/
|
||||||
|
public informationOfMotionsInTileCategories: { [id: number]: TileCategoryInformation } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor implements title and translation Module.
|
* Constructor implements title and translation Module.
|
||||||
*
|
*
|
||||||
@ -161,9 +187,10 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
* Sets the title, inits the table, defines the filter/sorting options and
|
* Sets the title, inits the table, defines the filter/sorting options and
|
||||||
* subscribes to filter and sorting services
|
* subscribes to filter and sorting services
|
||||||
*/
|
*/
|
||||||
public ngOnInit(): void {
|
public async ngOnInit(): Promise<void> {
|
||||||
super.setTitle('Motions');
|
super.setTitle('Motions');
|
||||||
this.initTable();
|
this.initTable();
|
||||||
|
const storedView = await this.storage.get<string>('motionListView');
|
||||||
this.configService
|
this.configService
|
||||||
.get<boolean>('motions_statutes_enabled')
|
.get<boolean>('motions_statutes_enabled')
|
||||||
.subscribe(enabled => (this.statutesEnabled = enabled));
|
.subscribe(enabled => (this.statutesEnabled = enabled));
|
||||||
@ -176,6 +203,11 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
});
|
});
|
||||||
this.categoryRepo.getViewModelListObservable().subscribe(cats => {
|
this.categoryRepo.getViewModelListObservable().subscribe(cats => {
|
||||||
this.categories = cats;
|
this.categories = cats;
|
||||||
|
if (cats.length > 0) {
|
||||||
|
this.selectedView = storedView || 'tiles';
|
||||||
|
} else {
|
||||||
|
this.selectedView = 'list';
|
||||||
|
}
|
||||||
this.updateStateColumnVisibility();
|
this.updateStateColumnVisibility();
|
||||||
});
|
});
|
||||||
this.tagRepo.getViewModelListObservable().subscribe(tags => {
|
this.tagRepo.getViewModelListObservable().subscribe(tags => {
|
||||||
@ -194,6 +226,70 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
this.router.navigate(['./' + motion.id], { relativeTo: this.route });
|
this.router.navigate(['./' + motion.id], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwriting method of base-class.
|
||||||
|
* Every time this method is called, all motions are counted in their related categories.
|
||||||
|
*
|
||||||
|
* @returns {Observable<ViewMotion[]>} An observable containing the list of motions.
|
||||||
|
*/
|
||||||
|
protected getModelListObservable(): Observable<ViewMotion[]> {
|
||||||
|
return super.getModelListObservable().pipe(
|
||||||
|
tap(motions => {
|
||||||
|
this.informationOfMotionsInTileCategories = {};
|
||||||
|
for (const motion of motions) {
|
||||||
|
if (motion.star) {
|
||||||
|
this.countMotions(-1, true, 'star', 'Favorites');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (motion.category_id) {
|
||||||
|
this.countMotions(
|
||||||
|
motion.category_id,
|
||||||
|
motion.category_id,
|
||||||
|
'category',
|
||||||
|
motion.category.name,
|
||||||
|
motion.category.prefix
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.countMotions(-2, null, 'category', 'No category');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tileCategories = Object.values(this.informationOfMotionsInTileCategories);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to count the motions in their related categories.
|
||||||
|
*
|
||||||
|
* @param id The key of TileCategory in `informationOfMotionsInTileCategories` object
|
||||||
|
* @param condition The condition, if the tile is selected
|
||||||
|
* @param filter The filter, if the tile is selected
|
||||||
|
* @param name The title of the tile
|
||||||
|
* @param prefix The prefix of the category
|
||||||
|
*/
|
||||||
|
private countMotions(
|
||||||
|
id: number,
|
||||||
|
condition: number | boolean | null,
|
||||||
|
filter: string,
|
||||||
|
name: string,
|
||||||
|
prefix?: string
|
||||||
|
): void {
|
||||||
|
let info = this.informationOfMotionsInTileCategories[id];
|
||||||
|
if (info) {
|
||||||
|
++info.amountOfMotions;
|
||||||
|
} else {
|
||||||
|
info = {
|
||||||
|
filter,
|
||||||
|
name,
|
||||||
|
condition,
|
||||||
|
prefix,
|
||||||
|
amountOfMotions: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.informationOfMotionsInTileCategories[id] = info;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the icon to the corresponding Motion Status
|
* Get the icon to the corresponding Motion Status
|
||||||
* TODO Needs to be more accessible (Motion workflow needs adjustment on the server)
|
* TODO Needs to be more accessible (Motion workflow needs adjustment on the server)
|
||||||
@ -388,6 +484,31 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function saves the selected view by changes.
|
||||||
|
*
|
||||||
|
* @param value is the new view the user has selected.
|
||||||
|
*/
|
||||||
|
public onChangeView(value: string): void {
|
||||||
|
this.selectedView = value;
|
||||||
|
this.storage.set('motionListView', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function changes the view to the list of motions where the selected category becomes the active filter.
|
||||||
|
*
|
||||||
|
* @param tileCategory information about filter and condition.
|
||||||
|
*/
|
||||||
|
public changeToViewWithTileCategory(tileCategory: TileCategoryInformation): void {
|
||||||
|
this.filterService.clearAllFilters();
|
||||||
|
this.filterService.toggleFilterOption(tileCategory.filter, {
|
||||||
|
label: tileCategory.name,
|
||||||
|
condition: tileCategory.condition,
|
||||||
|
isActive: false
|
||||||
|
});
|
||||||
|
this.onChangeView('list');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a dialog to edit some meta information about a motion.
|
* Opens a dialog to edit some meta information about a motion.
|
||||||
*
|
*
|
||||||
|
@ -84,4 +84,12 @@
|
|||||||
input[readonly] {
|
input[readonly] {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stretch-to-fill-parent {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
51
client/src/assets/styles/media-queries.scss
Normal file
51
client/src/assets/styles/media-queries.scss
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
$breakpoints: (
|
||||||
|
xs: 599px,
|
||||||
|
sm: 959px,
|
||||||
|
md: 1199px,
|
||||||
|
lg: 1599px
|
||||||
|
);
|
||||||
|
|
||||||
|
@mixin set-breakpoint-lower($breakpoint) {
|
||||||
|
@if map-has-key($map: $breakpoints, $key: $breakpoint) {
|
||||||
|
|
||||||
|
// Get the value from breakpoint
|
||||||
|
$breakpoint-value: map-get($breakpoints, $breakpoint);
|
||||||
|
|
||||||
|
@media screen and (max-width: $breakpoint-value) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
@warn 'Invalid breakpoint: #{$breakpoint}'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@mixin set-breakpoint-between($lower, $upper) {
|
||||||
|
@if map-has-key($breakpoints, $lower) and map-has-key($breakpoints, $upper) {
|
||||||
|
|
||||||
|
$lower-point: map-get($breakpoints, $lower);
|
||||||
|
$upper-point: map-get($breakpoints, $upper);
|
||||||
|
|
||||||
|
@media (min-width: $lower-point + 1) and (max-width: $upper-point) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
|
||||||
|
} @else {
|
||||||
|
@if (map-has-key($breakpoints, $lower) == false) {
|
||||||
|
@warn 'Invalid lower breakpoint: #{$lower}'
|
||||||
|
};
|
||||||
|
|
||||||
|
@if (map-has-key($breakpoints, $upper) == false) {
|
||||||
|
@warn 'Invalid upper breakpoint: #{$upper}'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@mixin set-breakpoint-upper($breakpoint) {
|
||||||
|
@if map-has-key($breakpoints, $breakpoint) {
|
||||||
|
$breakpoint-value: map-get($breakpoints, $breakpoint);
|
||||||
|
|
||||||
|
@media screen and (min-width: $breakpoint-value + 1) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
@import './app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
@import './app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
||||||
@import './app/shared/components/sorting-tree/sorting-tree.component.scss';
|
@import './app/shared/components/sorting-tree/sorting-tree.component.scss';
|
||||||
@import './app/site/global-spinner/global-spinner.component.scss';
|
@import './app/site/global-spinner/global-spinner.component.scss';
|
||||||
|
@import './app/shared/components/tile/tile.component.scss';
|
||||||
|
@import './app/shared/components/block-tile/block-tile.component.scss';
|
||||||
|
|
||||||
/** fonts */
|
/** fonts */
|
||||||
@import './assets/styles/fonts.scss';
|
@import './assets/styles/fonts.scss';
|
||||||
@ -29,6 +31,8 @@
|
|||||||
@include os-list-of-speakers-style($theme);
|
@include os-list-of-speakers-style($theme);
|
||||||
@include os-sorting-tree-style($theme);
|
@include os-sorting-tree-style($theme);
|
||||||
@include os-global-spinner-theme($theme);
|
@include os-global-spinner-theme($theme);
|
||||||
|
@include os-tile-style($theme);
|
||||||
|
@include os-block-tile-style($theme);
|
||||||
/** More components are added here */
|
/** More components are added here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user