first intermediate version

This commit is contained in:
Flo 2024-08-24 21:54:19 +00:00
commit b3e7254568
47 changed files with 1191 additions and 0 deletions

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# template
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.2.6.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

125
angular.json Normal file
View File

@ -0,0 +1,125 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"template": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:module": {
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
"src/assets",
"src/favicon.ico"
],
"styles": [
"@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "800kb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "template:build:production"
},
"development": {
"browserTarget": "template:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "template:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/assets",
"src/favicon.ico"
],
"styles": [
"@angular/material/prebuilt-themes/deeppurple-amber.scss",
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

43
bin/script/init Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
ENV_DIR=$(realpath $PROJECT_DIR/../../../)
EXIT=0
# Check docker-compose.yml file
if [ ! -f "$PROJECT_DIR/docker/docker-compose.yml" ]
then
cp "$PROJECT_DIR/docker/docker-compose.yml.dist" "$PROJECT_DIR/docker/docker-compose.yml"
EXIT=1
fi
# Check docker-compose-mac.yml file
if [ ! -f "$PROJECT_DIR/docker/docker-compose-mac.yml" ]
then
cp "$PROJECT_DIR/docker/docker-compose-mac.yml.dist" "$PROJECT_DIR/docker/docker-compose-mac.yml"
EXIT=1
fi
if [ $EXIT -eq 1 ]
then
echo "docker-compose or env files created, please change variables and call init again"
exit 1
fi
# Source key-scripts
source $ENV_DIR/bin/drun
source $ENV_DIR/bin/dexec
# Build and start docker containers
dexec template-frontend build
dexec template-frontend up -d
# Install node Packages
drun template-frontend npm install
drun template-frontend npm install -g @angular/cli
# Initial build of website
drun template-frontend npm run build

View File

@ -0,0 +1,31 @@
networks:
template:
external: true
services:
template-frontend-app:
image: template-frontend-app
networks:
- template
volumes:
- /Users/flo/dev/frontend/template/:/var/www/
build:
context: ./../
dockerfile: ./docker/npm/dockerfile
tty: true
template-frontend-nginx:
image: template-frontend-nginx
networks:
- template
volumes:
- /Users/flo/dev/frontend/template/:/var/www/html:z
build:
context: ./../
dockerfile: ./docker/nginx/dockerfile
labels:
- "traefik.http.routers.frontend.rule=Host(`template.local`)"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls.certresolver=le"
depends_on:
- template-frontend-app

View File

@ -0,0 +1,31 @@
networks:
template:
external: true
services:
template-frontend-app:
image: template-frontend-app
networks:
- template
volumes:
- ./../:/var/www/html
build:
context: ./../
dockerfile: ./docker/npm/dockerfile
tty: true
template-frontend-nginx:
image: template-frontend-nginx
networks:
- template
volumes:
- ./../:/var/www/html
build:
context: ./../
dockerfile: ./docker/nginx/dockerfile
labels:
- "traefik.http.routers.frontend.rule=Host(`template.local`)"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls.certresolver=le"
depends_on:
- template-frontend-app

View File

@ -0,0 +1,27 @@
upstream host-backend-nginx {
server template-backend-nginx:80;
}
server{
listen 80 default_server;
client_max_body_size 10000M;
root /var/www/html/dist;
index index.html;
location / {
try_files $uri$args $uri$args/ /index.html;
}
location /api/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://host-backend-nginx;
}
}

5
docker/nginx/dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM nginx:alpine
COPY docker/nginx/config/nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]

12
docker/npm/dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:20-slim
WORKDIR /var/www/html
# confirm installation
RUN node -v
RUN npm -v
# use newest npm version
RUN npm install npm
CMD ["/bin/bash"]

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "template",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^15.2.10",
"@angular/cdk": "^15.2.9",
"@angular/common": "^15.2.0",
"@angular/compiler": "^15.2.0",
"@angular/core": "^15.2.0",
"@angular/forms": "^15.2.0",
"@angular/material": "^15.2.9",
"@angular/platform-browser": "^15.2.0",
"@angular/platform-browser-dynamic": "^15.2.0",
"@angular/router": "^15.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.2.6",
"@angular/cli": "~15.2.6",
"@angular/compiler-cli": "^15.2.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"tailwindcss": "^3.4.3",
"typescript": "~4.9.4"
}
}

View File

@ -0,0 +1,2 @@
<app-navigation></app-navigation>
<router-outlet></router-outlet>

View File

9
src/app/app.component.ts Normal file
View File

@ -0,0 +1,9 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
}

44
src/app/app.module.ts Normal file
View File

@ -0,0 +1,44 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { RouterModule, Routes } from '@angular/router';
import { SharedModule } from './shared/shared.module';
import { NavigationComponent } from './component/navigation/navigation.component';
import { HomeComponent } from './component/home/home.component';
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{
path: 'video',
loadChildren: () =>
import('./feature/video/video.module').then((m) => m.VideoModule),
},
{
path: 'analyze',
loadChildren: () =>
import('./feature/analyze/analyze.module').then((m) => m.AnalyzeModule),
},
{
path: 'tag',
loadChildren: () =>
import('./feature/tag/tag.module').then((m) => m.TagModule),
},
{ path: '', redirectTo: 'home', pathMatch: 'full' },
];
@NgModule({
declarations: [AppComponent, NavigationComponent, HomeComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
RouterModule.forRoot(routes),
SharedModule,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

21
src/app/app.service.ts Normal file
View File

@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AppService {
private username: string|null;
constructor() {
this.username = null;
}
public getUsername(): string|null {
return this.username;
}
public setUsername(name: string|null): void {
this.username = name;
}
}

View File

@ -0,0 +1,9 @@
<div class="m-5 flex flex-nowrap overflow-x-auto gap-2">
<shared-tag-card *ngFor="let tag of tags" [tag]="tag" />
</div>
<shared-video-list
(onReadList)="readList($event)"
[total]="total"
[videos]="videos"
/>

View File

@ -0,0 +1,42 @@
import { Component } from '@angular/core';
import { TagListEntry } from 'src/app/model/TagListEntry';
import { VideoListEntry } from 'src/app/model/VideoListEntry';
import { RequestService } from 'src/app/request.service';
import { OnReadListModel } from 'src/app/shared/components/video-list/video-list.component';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
total: number = 0;
videos: VideoListEntry[] = [];
tags: TagListEntry[] = [];
constructor(public requestService: RequestService) {
this.readTagList();
}
readList(model: OnReadListModel): void {
this.requestService.post(
'video-list/read-list',
{
query: model.query,
page: model.page,
perPage: model.perPage,
sortBy: model.sortBy,
},
(response: any) => {
this.videos = response.items;
this.total = response.total;
}
);
}
readTagList(): void {
this.requestService.post('tag-list/read-list', {}, (response: any) => {
this.tags = response.items;
});
}
}

View File

@ -0,0 +1,15 @@
<a routerLink="/home">
<h1 class="w-full bg-zinc-700 p-5 text-white text-6xl font-bold text-center">
Template
</h1>
</a>
<ul
class="flex flex-wrap items-center justify-center text-xl font-bold bg-zinc-700 text-white"
>
<li *ngFor="let link of links" class="p-5 me-4">
<a [routerLink]="[link.routerLink]">
{{ link.caption }}
</a>
</li>
</ul>

View File

@ -0,0 +1,28 @@
import { Component } from '@angular/core';
interface Link {
routerLink: string;
caption: string;
}
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent {
links: Link[] = [
{
routerLink: '/video/upload',
caption: 'Upload',
},
{
routerLink: '/analyze',
caption: 'Analyse',
},
{
routerLink: '/tag/list',
caption: 'Tags',
},
];
}

105
src/app/request.service.ts Normal file
View File

@ -0,0 +1,105 @@
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
@Injectable({
providedIn: 'root'
})
export class RequestService {
constructor(
private http: HttpClient,
private router: Router,
private snackBar: MatSnackBar
) {
}
post(apiPath: string, body: any, fct: Function)
{
let url = this.obtainUrl(apiPath);
let observable = this.http.post(url, body).subscribe(
(answer:any) => {
fct(answer);
},
(error:any) => {
this.handleError(error);
}
);
}
postFiles(apiPath: string, files: File[], fct: Function) {
if (!files || files.length === 0) {
throw 'Need to select at least one file';
}
const formData = new FormData();
for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
formData.append('file' + fileIndex, files[fileIndex]);
}
let url = this.obtainUrl(apiPath);
let observable = this.http.post<any>(url, formData).subscribe(
(answer: any) => {
fct(answer);
},
(error: any) => {
this.handleError(error);
}
);
}
get(apiPath: string, body: any, fct: Function)
{
let url = this.obtainUrl(apiPath);
let observable = this.http.get(url, body).subscribe(
(answer:any) => {
fct(answer);
},
(error:any) => {
this.handleError(error);
}
);
}
private obtainUrl(apiPath: string): string {
let hostString = window.location.host;
let protocol = window.location.protocol;
return protocol + '//' + hostString + '/api/' + apiPath;
}
private handleError(answer: any): void {
if (answer.status == 401) {
console.log('Deine Sitzung konnte nicht gefunden werden');
this.router.navigate(['/auth']);
return;
}
if (answer.status == 403) {
this.showSnackBar('Du bist nicht für diese Aktion autorisiert', 'Ok');
return;
}
try {
let errorObject = answer.error.error;
if (errorObject.hasOwnProperty('message')) {
throw errorObject.message.toString();
}
if (errorObject.hasOwnProperty('code')) {
throw errorObject.code.toString();
}
} catch(error:any) {
this.showSnackBar(error.toString(), 'Ok');
}
}
private showSnackBar(message: string, action?: string) {
this.snackBar.open(message.toString(), action, {
duration: 3000,
});
}
}

View File

@ -0,0 +1,31 @@
<div
id="card"
class="grow p-3 my-2 rounded-xl shadow-lg border border-gray-100 dark:bg-zinc-800 dark:border-zinc-900"
>
<div id="header">
<div class="flex flex-row">
<div *ngIf="icon !== null">
<img class="h-6 w-6 m-2" [src]="icon" />
</div>
<div
*ngIf="header !== null"
class="my-auto text-xl font-medium text-black dark:text-white truncate"
>
{{ header }}
</div>
</div>
<div
*ngIf="subHeader !== null"
class="text-slate-500 dark:text-slate-400 mx-2"
>
{{ subHeader }}
</div>
</div>
<div id="content" class="mx-5 text-black dark:text-white">
<div *ngIf="content !== null" class="my-2 italic">"{{ content }}"</div>
<div *ngIf="content === null">
<ng-content></ng-content>
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'shared-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
})
export class CardComponent {
@Input() header: string | null = null;
@Input() icon: string | null = null;
@Input() subHeader: string | null = null;
@Input() content: string | null = null;
}

View File

@ -0,0 +1,21 @@
<div
*ngIf="caption !== null"
class="text-lg font-bold mb-3 text-black dark:text-white"
>
{{ caption }}
</div>
<div class="flex flex-col md:space-x-4">
<div id="form-body" class="flex-1 basis-full">
<ng-content />
</div>
<div class="flex flex-row">
<div class="flex-1"></div>
<button
(click)="submit.emit()"
class="flex-0 p-2 min-w-24 bg-green-700 hover:bg-green-800 dark:bg-green-800 dark:hover:bg-green-900 rounded-lg"
>
<p class="text-lg text-white text-md font-bold">Submit</p>
</button>
</div>
</div>

View File

@ -0,0 +1,12 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'shared-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent {
@Output() submit = new EventEmitter<any>();
@Input() caption: string|null = null;
}

View File

@ -0,0 +1,69 @@
<nav aria-label="Page navigation example">
<ul class="flex items-center -space-x-px h-8 text-sm">
<li>
<button
(click)="setActivePage(-1)"
class="flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 dark:text-white bg-white dark:bg-zinc-800 border border-e-0 border-gray-500 dark:border-zinc-800 rounded-s-lg hover:bg-gray-100 hover:text-gray-700"
>
<span class="sr-only">Previous</span>
<svg
class="w-2.5 h-2.5 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 1 1 5l4 4"
/>
</svg>
</button>
</li>
<li *ngFor="let item of items" class="w-full">
<div
class="flex items-center justify-center h-8 leading-tight border border-gray-500 dark:border-zinc-800"
[ngClass]="{
'bg-green-700 dark:bg-green-800 text-white': item.page === page,
'bg-white dark:bg-zinc-700 text-black dark:text-white':
item.page !== page
}"
>
<div *ngIf="item.page === null" class="px-3">...</div>
<button
class="px-3 w-full h-full hover:bg-gray-100 hover:text-gray-700"
*ngIf="item.page !== null"
(click)="setActivePage(item.page)"
>
{{ item.page }}
</button>
</div>
</li>
<li>
<button
(click)="setActivePage(-2)"
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 dark:text-white bg-white dark:bg-zinc-800 border border-gray-500 dark:border-zinc-800 rounded-e-lg hover:bg-gray-100 hover:text-gray-700"
>
<span class="sr-only">Next</span>
<svg
class="w-2.5 h-2.5 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 9 4-4-4-4"
/>
</svg>
</button>
</li>
</ul>
</nav>

View File

@ -0,0 +1,80 @@
import {
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
} from '@angular/core';
export interface PaginatorItem {
page: number | null;
}
@Component({
selector: 'shared-paginator',
templateUrl: './paginator.component.html',
styleUrls: ['./paginator.component.scss'],
})
export class PaginatorComponent implements OnChanges {
@Output() pageChange = new EventEmitter<number>();
@Input() page: number = 1;
@Input() perPage: number = 10;
@Input() total: number | null = null;
items: PaginatorItem[] = [];
ngOnChanges(changes: SimpleChanges): void {
this.calculateItems();
}
protected setActivePage(newPage: number | null) {
if (newPage === null) {
return;
}
if (newPage === -1) {
newPage = this.page - 1;
}
if (newPage === -2) {
newPage = this.page + 1;
}
if (newPage <= 0 || newPage > this.getMaxPages()) return;
this.page = newPage;
this.pageChange.emit(newPage);
this.calculateItems();
}
private calculateItems(): void {
this.items = [];
let first = 1;
let last = this.getMaxPages();
let rangeFirst = this.page - 1;
let rangeLast = this.page + 1;
if (this.page === 1) {
rangeFirst = 1;
rangeLast = last < 3 ? last : 3;
}
if (this.page === last) {
rangeFirst = Math.max(1, last - 2);
rangeLast = last;
}
if (rangeFirst > 1) this.items.push({ page: first });
if (rangeFirst > 2) this.items.push({ page: null });
for (let index = rangeFirst; index <= rangeLast; index++) {
this.items.push({ page: index });
}
if (rangeLast < last - 1) this.items.push({ page: null });
if (rangeLast < last) this.items.push({ page: last });
}
private getMaxPages(): number {
return Math.ceil((this.total ?? 0) / this.perPage);
}
}

View File

@ -0,0 +1,20 @@
<div class="text-sm font-medium text-center text-gray-500">
<ul class="flex flex-wrap -mb-px">
<li *ngFor="let tab of tabs; index as currentIndex" class="grow lg:grow-0">
<button
(click)="setActiveTab(tab)"
class="w-full lg:w-auto text-xl font-bold inline-block px-4 pt-4 pb-3 border-b-2 rounded-t-lg hover:text-green-700 hover:border-green-700 hover:no-underline dark:hover:text-green-800 dark:hover:border-green-800"
[ngClass]="{
'text-black border-black dark:text-green-800 dark:border-green-800 hover:text-green-700 dark:hover:border-white dark:hover:text-white':
currentIndex === activeIndex
}"
>
{{ tab.title }}
</button>
</li>
</ul>
</div>
<div class="tab-content">
<ng-content></ng-content>
</div>

View File

@ -0,0 +1,30 @@
import { Component, ContentChildren, EventEmitter, Input, Output, QueryList } from '@angular/core';
import { TabItem } from '../../models/TabItem';
@Component({
selector: 'shared-tab-control',
templateUrl: './tab-control.component.html',
styleUrls: ['./tab-control.component.scss']
})
export class TabControlComponent {
@Input() public tabs: TabItem[] = [];
@Input() public activeTabId: string = '';
@Output() public activeTabIdChange = new EventEmitter<string>();
@ContentChildren('tabContent') tabContents!: QueryList<any>;
public activeIndex = 0;
public ngOnChanges() {
this.setActiveTab(null);
}
public setActiveTab(tab: TabItem|null) {
if (tab !== null) {
this.activeTabId = tab.id;
this.activeTabIdChange.emit(this.activeTabId);
}
this.activeIndex = this.tabs.findIndex((i) => i.id === this.activeTabId);
}
}

View File

@ -0,0 +1,54 @@
<table class="w-full" [ngClass]="[background, foreground, border]">
<thead class="uppercase border-b text-sm" [ngClass]="[border]">
<tr>
<th
*ngFor="let column of columns; index as currentIndex"
[class]="'px-5 py-2 ' + column.headerCss"
[ngClass]="{
backgroundColumn: currentIndex % 2 === 0,
backgroundColumnAlternate: currentIndex % 2 === 1
}"
>
<div [innerHtml]="column.header"></div>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items" class="border-b" [ngClass]="[border]">
<td
*ngFor="let column of columns; index as currentIndex"
[class]="'px-5 py-2 ' + column.columnCss"
[ngClass]="{
backgroundColumn: currentIndex % 2 === 0,
backgroundColumnAlternate: currentIndex % 2 === 1
}"
>
<div *ngIf="column.routerLink !== undefined">
<a [routerLink]="column.routerLink(item)">
<div *ngIf="column.columnContent !== undefined">
{{ column.columnContent }}
</div>
<div
*ngIf="column.columnFunction !== undefined"
[innerHtml]="column.columnFunction(item)"
></div>
</a>
</div>
<div *ngIf="column.routerLink === undefined">
<div *ngIf="column.columnContent !== undefined">
<div *ngIf="isBoolean(column.columnContent)">
<input type="checkbox" [(ngModel)]="column.columnContent" />
</div>
<div *ngIf="!isBoolean(column.columnContent)">
{{ column.columnContent }}
</div>
</div>
<div
*ngIf="column.columnFunction !== undefined"
[innerHtml]="column.columnFunction(item)"
></div>
</div>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,31 @@
import { Component, Input } from '@angular/core';
export interface ColumnDefinition {
header: string;
columnContent?: string | undefined;
columnFunction?: Function | undefined;
routerLink?: Function | undefined;
headerCss?: string | undefined;
columnCss?: string | undefined;
}
@Component({
selector: 'shared-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss'],
})
export class TableComponent {
@Input() foreground: string = 'text-white';
@Input() background: string = 'bg-zinc-800';
@Input() backgroundColumn: string = 'bg-zinc-800';
@Input() backgroundColumnAlternate: string = 'bg-zinc-700';
@Input() border: string = 'border-zinc-600';
@Input() items: any[] = [];
@Input() columns: ColumnDefinition[] = [];
isBoolean(obje: any): boolean {
console.log(obje);
return typeof obje === 'boolean';
}
}

View File

@ -0,0 +1,4 @@
export interface TabItem {
id: string;
title: string;
}

View File

@ -0,0 +1,115 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormComponent } from './components/form/form.component';
import { CardComponent } from './components/card/card.component';
import { PaginatorComponent } from './components/paginator/paginator.component';
import { TabControlComponent } from './components/tab-control/tab-control.component';
import { VideoPresenterComponent } from './components/video-presenter/video-presenter.component';
import { ImagePresenterComponent } from './components/image-presenter/image-presenter.component';
import { TableComponent } from './components/table/table.component';
import { FormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatMenuModule } from '@angular/material/menu';
import { MatListModule } from '@angular/material/list';
import { MatIconModule } from '@angular/material/icon';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatDividerModule } from '@angular/material/divider';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTableModule } from '@angular/material/table';
import { MatChipsModule } from '@angular/material/chips';
import { VideoListComponent } from './components/video-list/video-list.component';
import { VideoCardComponent } from './components/video-card/video-card.component';
import { TagCardComponent } from './components/tag-card/tag-card.component';
import { TagSelectorComponent } from './components/tag-selector/tag-selector.component';
@NgModule({
declarations: [
ImagePresenterComponent,
VideoPresenterComponent,
CardComponent,
TabControlComponent,
PaginatorComponent,
FormComponent,
TableComponent,
VideoListComponent,
VideoCardComponent,
TagCardComponent,
TagSelectorComponent,
],
exports: [
ImagePresenterComponent,
VideoPresenterComponent,
CardComponent,
TabControlComponent,
PaginatorComponent,
FormComponent,
TableComponent,
VideoListComponent,
VideoCardComponent,
TagSelectorComponent,
TagCardComponent,
MatSlideToggleModule,
MatCardModule,
MatButtonModule,
MatIconModule,
MatGridListModule,
MatFormFieldModule,
FormsModule,
MatExpansionModule,
MatMenuModule,
MatListModule,
MatToolbarModule,
MatSidenavModule,
MatInputModule,
MatSelectModule,
MatDividerModule,
MatDialogModule,
MatSnackBarModule,
MatPaginatorModule,
MatCheckboxModule,
MatTabsModule,
MatTableModule,
MatChipsModule,
],
imports: [
RouterModule,
CommonModule,
MatSlideToggleModule,
MatCardModule,
MatButtonModule,
MatIconModule,
MatGridListModule,
MatFormFieldModule,
FormsModule,
MatExpansionModule,
MatMenuModule,
MatListModule,
MatToolbarModule,
MatSidenavModule,
MatInputModule,
MatSelectModule,
MatDividerModule,
MatDialogModule,
MatSnackBarModule,
MatPaginatorModule,
MatCheckboxModule,
MatTabsModule,
MatTableModule,
MatChipsModule,
],
})
export class SharedModule {}

BIN
src/assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

15
src/index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Template</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="assets/icon.ico" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
</head>
<body class="bg-zinc-900">
<app-root></app-root>
</body>
</html>

7
src/main.ts Normal file
View File

@ -0,0 +1,7 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

3
src/styles.scss Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

9
tailwind.config.js Normal file
View File

@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,ts}"],
theme: {
extend: {},
},
plugins: [],
}

14
tsconfig.app.json Normal file
View File

@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

33
tsconfig.json Normal file
View File

@ -0,0 +1,33 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

14
tsconfig.spec.json Normal file
View File

@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}