feat: adds table view

feat: adds table view
This commit is contained in:
2022-12-23 10:22:05 +01:00
parent e9472972cd
commit 75b20dc508
18 changed files with 229 additions and 27 deletions

View File

@@ -22,8 +22,8 @@ const routes: Routes = [
canActivate: [AuthGuard], canActivate: [AuthGuard],
}, },
{ {
path: 'followings', path: 'list',
loadChildren: () => import('./followings/followings.module').then(m => m.FollowingsModule), loadChildren: () => import('./followings-list/followings-list.module').then(m => m.FollowingsListModule),
canActivate: [AuthGuard], canActivate: [AuthGuard],
}, },
{ {
@@ -31,6 +31,11 @@ const routes: Routes = [
loadChildren: () => import('./followings-matrix/followings-matrix.module').then(m => m.FollowingsMatrixModule), loadChildren: () => import('./followings-matrix/followings-matrix.module').then(m => m.FollowingsMatrixModule),
canActivate: [AuthGuard], canActivate: [AuthGuard],
}, },
{
path: 'table',
loadChildren: () => import('./followings-table/followings-table.module').then(m => m.FollowingsTableModule),
canActivate: [AuthGuard],
},
]; ];
@NgModule({ @NgModule({

View File

@@ -1,8 +1,6 @@
import {Component, isDevMode} from '@angular/core'; import {Component, isDevMode} from '@angular/core';
import {select, Store} from "@ngrx/store"; import {Store} from "@ngrx/store";
import {MastodonApiActions} from "./shared/state/store/actions"; import {MastodonApiActions} from "./shared/state/store/actions";
import {Observable, tap} from "rxjs";
import {selectLoadingPercentage} from "./shared/state/store/selectors";
import {PersistentStore} from "./shared/state/persistent/persistent-store.service"; import {PersistentStore} from "./shared/state/persistent/persistent-store.service";
@Component({ @Component({
@@ -16,8 +14,9 @@ export class AppComponent {
navigationItems = [ navigationItems = [
{title: 'Authorize', link: '/auth'}, {title: 'Authorize', link: '/auth'},
{title: 'Stats', link: '/sync'}, {title: 'Stats', link: '/sync'},
{title: 'List view', link: '/followings'}, {title: 'List view', link: '/list'},
{title: 'Matrix View', link: '/matrix'}, {title: 'Matrix View', link: '/matrix'},
{title: 'Table View', link: '/table'},
]; ];
constructor(private store: Store, private persistentStore: PersistentStore) { constructor(private store: Store, private persistentStore: PersistentStore) {

View File

@@ -1,11 +1,11 @@
import {RouterModule, Routes} from "@angular/router"; import {RouterModule, Routes} from "@angular/router";
import {NgModule} from "@angular/core"; import {NgModule} from "@angular/core";
import {FollowingsComponent} from "./followings/followings.component"; import {ListComponent} from "./list/list.component";
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: FollowingsComponent component: ListComponent
} }
]; ];
@@ -13,5 +13,5 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule] exports: [RouterModule]
}) })
export class FollowingsRoutingModule { export class FollowingsListRoutingModule {
} }

View File

@@ -1,23 +1,23 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {FollowingsComponent} from './followings/followings.component'; import {ListComponent} from './list/list.component';
import {SharedModule} from '../shared/shared.module'; import {SharedModule} from '../shared/shared.module';
import {FollowingsRoutingModule} from './followings-routing.module'; import {FollowingsListRoutingModule} from './followings-list-routing.module';
import {NbListModule, NbToggleModule} from "@nebular/theme"; import {NbListModule, NbToggleModule} from "@nebular/theme";
@NgModule({ @NgModule({
declarations: [ declarations: [
FollowingsComponent ListComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
FollowingsRoutingModule, FollowingsListRoutingModule,
// Nebula // Nebula
NbListModule, NbListModule,
NbToggleModule, NbToggleModule,
] ]
}) })
export class FollowingsModule { export class FollowingsListModule {
} }

View File

@@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FollowingsComponent } from './followings.component'; import { ListComponent } from './list.component';
describe('FollowingsComponent', () => { describe('FollowingsComponent', () => {
let component: FollowingsComponent; let component: ListComponent;
let fixture: ComponentFixture<FollowingsComponent>; let fixture: ComponentFixture<ListComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ FollowingsComponent ] declarations: [ ListComponent ]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(FollowingsComponent); fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -9,11 +9,11 @@ import {selectFilteredFollowingsWithLists, selectFollowings, selectFollowingsWit
import {FiltersActions, ListActions, MastodonApiActions} from "../../shared/state/store/actions"; import {FiltersActions, ListActions, MastodonApiActions} from "../../shared/state/store/actions";
@Component({ @Component({
selector: 'app-followings', selector: 'app-list',
templateUrl: './followings.component.html', templateUrl: './list.component.html',
styleUrls: ['./followings.component.scss'] styleUrls: ['./list.component.scss']
}) })
export class FollowingsComponent { export class ListComponent {
followings$: Observable<ReadonlyArray<Account>>; followings$: Observable<ReadonlyArray<Account>>;
lists$: Observable<ReadonlyArray<List>>; lists$: Observable<ReadonlyArray<List>>;

View File

@@ -1,7 +1,7 @@
table { table {
display: block; display: block;
height: max(600px, calc(100vh - 300px)); height: max(600px, calc(100vh - 300px));
width: 1080px; width: 100%;
overflow-x: auto; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
border: none; border: none;

View File

@@ -0,0 +1,17 @@
import {RouterModule, Routes} from "@angular/router";
import {NgModule} from "@angular/core";
import {TableComponent} from "./table/table.component";
const routes: Routes = [
{
path: '',
component: TableComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FollowingsTableRoutingModule {
}

View File

@@ -0,0 +1,22 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {TableComponent} from './table/table.component';
import {SharedModule} from "../shared/shared.module";
import {FollowingsTableRoutingModule} from "./followings-table-routing.module";
import {NbUserModule} from "@nebular/theme";
@NgModule({
declarations: [
TableComponent
],
imports: [
CommonModule,
SharedModule,
FollowingsTableRoutingModule,
// Nebular
NbUserModule,
]
})
export class FollowingsTableModule {
}

View File

@@ -0,0 +1,53 @@
<nb-card>
<nb-card-header style="position: relative;">
<h1>Table View for Followings</h1>
<div class="divider"></div>
<app-filters></app-filters>
</nb-card-header>
<nb-card-body>
<table>
<tr>
<th>Username</th>
<th>Notes</th>
<th>Fields</th>
<th>Lists</th>
<th>Actions</th>
</tr>
<tr *ngFor="let row of rows$ |async">
<td>
<a [href]="row.url">
<nb-user
size="medium"
shape="semi-round"
[name]="row.displayName"
[title]="row.username"
[picture]="row.avatar"
>
</nb-user>
</a>
</td>
<td><span [innerHTML]="row.note"></span></td>
<td><span [innerHTML]="row.fields"></span></td>
<td>
<div *ngFor="let list of row.lists">
{{list.title}}
<nb-icon icon="person-remove-outline" (click)="removeAccountFromList(row.id, list.id)">
Remove Account from list
</nb-icon>
</div>
</td>
<td>
<div class="actions">
<select class="list-select" #listSelect>
<option *ngFor="let list of lists$ | async" [value]="list.id">{{list.title}}</option>
</select>
<nb-icon icon="person-add-outline" (click)="addAccountToSelectedList(row.id, listSelect.value)">
Add Account to list
</nb-icon>
</div>
</td>
</tr>
</table>
</nb-card-body>
</nb-card>

View File

@@ -0,0 +1,25 @@
table {
display: block;
height: max(600px, calc(100vh - 300px));
width: 100%;
overflow-x: auto;
overflow-y: auto;
border: none;
th {
border: 1pt solid #101426;
}
td {
border: 1pt solid #101426;
}
}
div.actions {
display: flex;
flex-direction: row;
select {
margin-right: 5px;
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TableComponent } from './table.component';
describe('TableComponent', () => {
let component: TableComponent;
let fixture: ComponentFixture<TableComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TableComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(TableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,58 @@
import {Component} from '@angular/core';
import {map, Observable} from "rxjs";
import {Account} from "../../../../../mastodon-api/src/lib/interfaces/public/account";
import {List} from "../../../../../mastodon-api/src/lib/interfaces/public/list";
import {select, Store} from "@ngrx/store";
import {selectFilteredFollowingsWithLists, selectLists} from "../../shared/state/store/selectors";
import {ListActions} from "../../shared/state/store/actions";
interface DataGridRow {
id: string;
url: string;
displayName: string;
username: string;
avatar: string;
note: string;
fields: string;
lists: List[];
}
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent {
rows$: Observable<DataGridRow[]>;
lists$: Observable<ReadonlyArray<List>>;
constructor(private store: Store) {
this.rows$ = this.store
.pipe(
select(selectFilteredFollowingsWithLists),
map((accounts: ReadonlyArray<Account>) => {
return accounts.map((account) => {
return {
id: account.id,
username: `@${account.username}`,
displayName: account.displayName,
url: account.url,
avatar: account.avatar,
note: account.note,
fields: account.fields.map(field => `${field.name}: ${field.value}`).join('<br>'),
lists: account.lists,
} as DataGridRow;
});
}),
);
this.lists$ = this.store.pipe(select(selectLists));
}
addAccountToSelectedList(accountId: string, listId: string) {
this.store.dispatch(ListActions.addAccountToList({accountId, listId}));
}
removeAccountFromList(accountId: string, listId: string) {
this.store.dispatch(ListActions.removeAccountFromList({accountId, listId}));
}
}

View File

@@ -8,7 +8,7 @@
<ul> <ul>
<li><a [routerLink]="['/auth']">Authorize</a> with your instance</li> <li><a [routerLink]="['/auth']">Authorize</a> with your instance</li>
<li><a [routerLink]="['/sync']">Sync</a> your lists and followings to local storage</li> <li><a [routerLink]="['/sync']">Sync</a> your lists and followings to local storage</li>
<li>Select the <a [routerLink]="['/followings']">List view</a> to add and remove users from lists</li> <li>Select the <a [routerLink]="['/list']">List view</a> to add and remove users from lists</li>
<li>... or use the experimental <a [routerLink]="['/matrix']">Matrix view</a></li> <li>... or use the experimental <a [routerLink]="['/matrix']">Matrix view</a></li>
</ul> </ul>
</nb-card-body> </nb-card-body>

View File

@@ -40,7 +40,7 @@
<div *ngIf="(lists$ | async) && (followings$ | async)"> <div *ngIf="(lists$ | async) && (followings$ | async)">
<p>Now you can:</p> <p>Now you can:</p>
<ul> <ul>
<li>Select the <a [routerLink]="['/followings']">List view</a> to add and remove users from lists</li> <li>Select the <a [routerLink]="['/list']">List view</a> to add and remove users from lists</li>
<li>... or use the experimental <a [routerLink]="['/matrix']">Matrix view</a></li> <li>... or use the experimental <a [routerLink]="['/matrix']">Matrix view</a></li>
</ul> </ul>
</div> </div>

View File

@@ -9,7 +9,7 @@ $nb-themes: nb-register-theme(
( (
font-family-primary: 'Inter', font-family-primary: 'Inter',
font-family-secondary: 'Inter', font-family-secondary: 'Inter',
layout-content-width: 1200px, layout-content-width: 100vw,
), ),
dark, dark,
dark dark