Skip to content

Commit

Permalink
[#28] create "My Links" page
Browse files Browse the repository at this point in the history
  • Loading branch information
philipphoeninger committed Oct 14, 2024
1 parent bb780db commit 7331686
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 0 deletions.
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>&nbsp;</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>
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;
}
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 CLIENT/CLIENT.FileSharing/src/app/pages/my-links/my-links.component.ts
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);
}
}

0 comments on commit 7331686

Please sign in to comment.