-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bb780db
commit 7331686
Showing
4 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
158 changes: 158 additions & 0 deletions
158
CLIENT/CLIENT.FileSharing/src/app/pages/my-links/my-links.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
<h2 class="heading">My Links</h2> | ||
<div class="filter-container"> | ||
<form class="filter-form"> | ||
<!-- Filter by Valid To --> | ||
<mat-form-field subscriptSizing="dynamic"> | ||
<mat-label>Valid To</mat-label> | ||
<mat-select [formControl]="validToControl" (selectionChange)="(null)"> | ||
@for (timeSpan of timeSpans; track timeSpan) { | ||
<mat-option [value]="timeSpan">{{ timeSpan.label }}</mat-option> | ||
} | ||
</mat-select> | ||
<div matSuffix style="margin-left: 4px; margin-right: 8px"> | ||
<mat-icon | ||
(click)="$event.stopPropagation(); validToControl.setValue('')" | ||
>clear</mat-icon | ||
> | ||
</div> | ||
</mat-form-field> | ||
<button style="margin-left: 16px" mat-flat-button (click)="clearFilters()"> | ||
<mat-icon>clear</mat-icon> | ||
<span>Clear filters</span> | ||
</button> | ||
</form> | ||
</div> | ||
<div class="table-container"> | ||
<table mat-table class="full-width-table" matSort aria-label="Elements"> | ||
<!-- Select Column --> | ||
<ng-container matColumnDef="select"> | ||
<th mat-header-cell *matHeaderCellDef style="padding-inline: 0"> | ||
<mat-checkbox | ||
(change)="$event ? toggleAllRows() : null" | ||
[checked]="selection.hasValue() && isAllSelected()" | ||
[indeterminate]="selection.hasValue() && !isAllSelected()" | ||
> | ||
</mat-checkbox> | ||
</th> | ||
<td mat-cell *matCellDef="let row" style="padding-inline: 0"> | ||
<mat-checkbox | ||
(click)="$event.stopPropagation()" | ||
(change)="$event ? selection.toggle(row) : null" | ||
[checked]="selection.isSelected(row)" | ||
> | ||
</mat-checkbox> | ||
</td> | ||
</ng-container> | ||
|
||
<!-- Link Address Column --> | ||
<ng-container matColumnDef="name"> | ||
<th | ||
mat-header-cell | ||
*matHeaderCellDef | ||
mat-sort-header | ||
style="padding-left: 0" | ||
> | ||
Name | ||
</th> | ||
<td mat-cell *matCellDef="let row" style="padding-left: 0"> | ||
<div style="display: flex; align-items: center; gap: 8px"> | ||
@switch (row.fileType) { | ||
@case (EnFileType.Folder) { | ||
<mat-icon>folder</mat-icon> | ||
} | ||
@case (EnFileType.Document) { | ||
<mat-icon>description</mat-icon> | ||
} | ||
@default { | ||
<mat-icon>link</mat-icon> | ||
} | ||
} | ||
<span>{{ row.name ?? row.url }}</span> | ||
</div> | ||
</td> | ||
</ng-container> | ||
|
||
<!-- CreatedAt Column --> | ||
<ng-container matColumnDef="createdAt"> | ||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Created at</th> | ||
<td mat-cell *matCellDef="let row"> | ||
{{ row.createdAt.toLocaleString() }} | ||
</td> | ||
</ng-container> | ||
|
||
<!-- ValidTo Column --> | ||
<ng-container matColumnDef="validTo"> | ||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Valid To</th> | ||
<td mat-cell *matCellDef="let row"> | ||
{{ row.validTo.toLocaleString() }} | ||
</td> | ||
</ng-container> | ||
|
||
<!-- Visits Column --> | ||
<ng-container matColumnDef="visits"> | ||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Visits</th> | ||
<td mat-cell *matCellDef="let row">{{ row.visits }}</td> | ||
</ng-container> | ||
|
||
<!-- Actions Column --> | ||
<ng-container matColumnDef="actions" stickyEnd> | ||
<th mat-header-cell *matHeaderCellDef> </th> | ||
<td mat-cell *matCellDef="let row" style="text-align: right"> | ||
<button (click)="onContextMenuAction($event, row)" mat-icon-button> | ||
<mat-icon>more_vert</mat-icon> | ||
</button> | ||
</td> | ||
</ng-container> | ||
|
||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> | ||
<tr | ||
mat-row | ||
*matRowDef="let row; columns: displayedColumns" | ||
(contextmenu)="onContextMenuAction($event, row)" | ||
></tr> | ||
</table> | ||
</div> | ||
<mat-paginator | ||
#paginator | ||
class="my-links-table-paginator" | ||
[length]="dataSource.data.length" | ||
[pageIndex]="0" | ||
[pageSize]="10" | ||
[pageSizeOptions]="[5, 10, 20]" | ||
aria-label="Select page" | ||
> | ||
</mat-paginator> | ||
<div | ||
class="my-links-context-menu" | ||
[style.left]="contextMenuPosition.x" | ||
[style.top]="contextMenuPosition.y" | ||
[matMenuTriggerFor]="contextMenu" | ||
></div> | ||
<mat-menu #contextMenu="matMenu"> | ||
<ng-template matMenuContent let-link="link"> | ||
<button | ||
mat-menu-item | ||
[matMenuTriggerFor]="timeContextMenu" | ||
[matMenuTriggerData]="{ link: link }" | ||
> | ||
Extend by | ||
</button> | ||
<button mat-menu-item (click)="renameLink(link)">Rename</button> | ||
<button mat-menu-item (click)="changeLinkColor(link)">Change color</button> | ||
<button mat-menu-item (click)="deleteLink(link)">Delete</button> | ||
</ng-template> | ||
</mat-menu> | ||
|
||
<mat-menu #timeContextMenu="matMenu"> | ||
<ng-template matMenuContent let-link="link"> | ||
<button mat-menu-item (click)="extendLink(link, EnExtendTimeSpan.Day)"> | ||
1 Day | ||
</button> | ||
<button mat-menu-item (click)="extendLink(link, EnExtendTimeSpan.Week)"> | ||
1 Week | ||
</button> | ||
<button mat-menu-item (click)="extendLink(link, EnExtendTimeSpan.Month)"> | ||
1 Month | ||
</button> | ||
</ng-template> | ||
</mat-menu> |
65 changes: 65 additions & 0 deletions
65
CLIENT/CLIENT.FileSharing/src/app/pages/my-links/my-links.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
.heading { | ||
font-size: 1.75em; | ||
margin-bottom: 0; | ||
} | ||
|
||
.full-width-table { | ||
width: 100%; | ||
} | ||
|
||
.filter-container { | ||
// @include mat.form-field-density(minimum); // TODO: handle with theming | ||
.filter-form { | ||
display: flex; | ||
align-items: center; | ||
} | ||
} | ||
|
||
.table-container { | ||
overflow: auto; | ||
max-height: calc(100vh - 244px); | ||
|
||
table { | ||
padding-right: 8px; | ||
} | ||
|
||
&::-webkit-scrollbar { | ||
border-radius: 35px; | ||
width: 8px; | ||
height: 8px; | ||
} | ||
|
||
&::-webkit-scrollbar-thumb { | ||
border-radius: 35px; | ||
background: #b2b8c5; | ||
min-height: 50px; | ||
} | ||
|
||
&::-webkit-scrollbar-track { | ||
margin-top: 56px; | ||
border-radius: 35px; | ||
background: transparent; | ||
} | ||
|
||
&::-webkit-scrollbar-thumb:hover { | ||
background: #999ca8; | ||
} | ||
|
||
&::-webkit-scrollbar-corner { | ||
background: transparent; | ||
} | ||
} | ||
|
||
.my-links-table-paginator { | ||
margin-bottom: 16px; | ||
} | ||
|
||
.my-links-context-menu { | ||
visibility: hidden; | ||
position: fixed; | ||
} | ||
|
||
.mat-column-select { | ||
overflow: initial; | ||
width: 44px; | ||
} |
25 changes: 25 additions & 0 deletions
25
CLIENT/CLIENT.FileSharing/src/app/pages/my-links/my-links.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; | ||
|
||
import { MyLinksComponent } from './my-links.component'; | ||
|
||
describe('MyLinksComponent', () => { | ||
let component: MyLinksComponent; | ||
let fixture: ComponentFixture<MyLinksComponent>; | ||
|
||
beforeEach(waitForAsync(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [NoopAnimationsModule] | ||
}).compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(MyLinksComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should compile', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
122 changes: 122 additions & 0 deletions
122
CLIENT/CLIENT.FileSharing/src/app/pages/my-links/my-links.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { EnExtendTimeSpan } from './../../links/shared/extend-time-span.enum'; | ||
import { AfterViewInit, Component, ViewChild } from '@angular/core'; | ||
import { MatTableModule, MatTable } from '@angular/material/table'; | ||
import { MatPaginatorModule, MatPaginator } from '@angular/material/paginator'; | ||
import { MatSortModule, MatSort } from '@angular/material/sort'; | ||
import { MatIconModule } from '@angular/material/icon'; | ||
import { MyLinksDataSource } from '../../links/shared/mocks/linkDataSource'; | ||
import { LinkModel } from '../../links/shared/link.model'; | ||
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
import { MatSelectModule } from '@angular/material/select'; | ||
import { MatFormFieldModule } from '@angular/material/form-field'; | ||
import { MatButtonModule } from '@angular/material/button'; | ||
import { ITimeSpan } from '../../file-items/shared/time-span.interface'; | ||
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; | ||
import { MatCheckboxModule } from '@angular/material/checkbox'; | ||
import { SelectionModel } from '@angular/cdk/collections'; | ||
import { EnFileType } from '../../file-items/shared/file-type.enum'; | ||
import { MOCK_TIME_SPANS_FUTURE } from '../../links/shared/mocks/timeSpans.mock.data'; | ||
|
||
@Component({ | ||
selector: 'my-links', | ||
templateUrl: './my-links.component.html', | ||
styleUrl: './my-links.component.scss', | ||
standalone: true, | ||
imports: [ | ||
MatTableModule, | ||
MatPaginatorModule, | ||
MatSortModule, | ||
MatIconModule, | ||
MatFormFieldModule, | ||
MatSelectModule, | ||
FormsModule, | ||
ReactiveFormsModule, | ||
MatButtonModule, | ||
MatMenuModule, | ||
MatCheckboxModule, | ||
], | ||
}) | ||
export class MyLinksComponent implements AfterViewInit { | ||
@ViewChild(MatPaginator) paginator!: MatPaginator; | ||
@ViewChild(MatSort) sort!: MatSort; | ||
@ViewChild(MatTable) table!: MatTable<LinkModel>; | ||
@ViewChild(MatMenuTrigger) contextMenu!: MatMenuTrigger; | ||
|
||
EnFileType = EnFileType; | ||
EnExtendTimeSpan = EnExtendTimeSpan; | ||
|
||
dataSource = new MyLinksDataSource(); | ||
|
||
timeSpans: ITimeSpan[] = MOCK_TIME_SPANS_FUTURE; | ||
|
||
displayedColumns = [ | ||
'select', | ||
'name', | ||
'createdAt', | ||
'validTo', | ||
'visits', | ||
'actions', | ||
]; | ||
|
||
validToControl = new FormControl(''); | ||
|
||
contextMenuPosition = { x: '0px', y: '0px' }; | ||
|
||
initialSelection = []; | ||
allowMultiSelect = true; | ||
selection = new SelectionModel<LinkModel>( | ||
this.allowMultiSelect, | ||
this.initialSelection, | ||
); | ||
|
||
ngAfterViewInit(): void { | ||
this.dataSource.sort = this.sort; | ||
this.dataSource.paginator = this.paginator; | ||
this.table.dataSource = this.dataSource; | ||
} | ||
|
||
clearFilters() { | ||
this.validToControl.setValue(''); | ||
} | ||
|
||
onContextMenuAction(event: any, link: LinkModel) { | ||
event.preventDefault(); | ||
this.contextMenuPosition.x = event.clientX + 'px'; | ||
this.contextMenuPosition.y = event.clientY + 'px'; | ||
this.contextMenu.menuData = { link }; | ||
this.contextMenu.menu?.focusFirstItem('mouse'); | ||
this.contextMenu.openMenu(); | ||
} | ||
|
||
isAllSelected() { | ||
const numSelected = this.selection.selected.length; | ||
const numRows = this.dataSource.data.length; | ||
return numSelected == numRows; | ||
} | ||
|
||
toggleAllRows() { | ||
this.isAllSelected() | ||
? this.selection.clear() | ||
: this.dataSource.data.forEach((row) => this.selection.select(row)); | ||
} | ||
|
||
extendLink(link: LinkModel, time: EnExtendTimeSpan) { | ||
// TODO | ||
alert('Extend ' + (link.name ?? link.url)); | ||
} | ||
|
||
renameLink(link: LinkModel) { | ||
// TODO | ||
alert('Rename ' + link.name); | ||
} | ||
|
||
changeLinkColor(link: LinkModel) { | ||
// TODO | ||
alert('Change color for ' + link.name); | ||
} | ||
|
||
deleteLink(link: LinkModel) { | ||
// TODO | ||
alert('Delete ' + link.name); | ||
} | ||
} |