diff --git a/src/app/api/types/levels/level.ts b/src/app/api/types/levels/level.ts index 22b2c3ef..0ee498c1 100644 --- a/src/app/api/types/levels/level.ts +++ b/src/app/api/types/levels/level.ts @@ -13,7 +13,11 @@ export interface Level { totalPlays: number; uniquePlays: number; publisher: User | undefined; + originalPublisher: string | undefined; + isReUpload: boolean; + isModded: boolean; teamPicked: boolean; + dateTeamPicked: Date | undefined; gameVersion: number; score: number; slotType: number; diff --git a/src/app/components/items/level-preview.component.ts b/src/app/components/items/level-preview.component.ts index c6261493..38d754e0 100644 --- a/src/app/components/items/level-preview.component.ts +++ b/src/app/components/items/level-preview.component.ts @@ -8,19 +8,23 @@ import {GamePipe} from "../../pipes/game.pipe"; import {DateComponent} from "../ui/info/date.component"; import {DefaultPipe} from "../../pipes/default.pipe"; import {LabelComponent} from "../ui/info/label.component"; +import { OriginalPublisherRouterLink } from "../ui/text/links/original-publisher-router-link.component"; +import { TooltipComponent } from "../ui/text/tooltip.component"; @Component({ selector: 'app-level-preview', imports: [ - UserLinkComponent, - LevelAvatarComponent, - LevelStatisticsComponent, - LevelRouterLinkComponent, - GamePipe, - DateComponent, - DefaultPipe, - LabelComponent - ], + UserLinkComponent, + LevelAvatarComponent, + LevelStatisticsComponent, + LevelRouterLinkComponent, + GamePipe, + DateComponent, + DefaultPipe, + LabelComponent, + OriginalPublisherRouterLink, + TooltipComponent +], template: `
@@ -32,15 +36,30 @@ import {LabelComponent} from "../ui/info/label.component"; [title]=level.title>{{ level.title | default: "Unnamed Level" }}

- + -
- by +
+ @if (level.isReUpload) { +

by

+

()

+ } + @else { +

by

+ } +
- {{level.gameVersion | game: true}} + + {{level.gameVersion | game: true}} + + + @if (level.isModded) { + + Modded + + } diff --git a/src/app/components/items/level-statistics.component.ts b/src/app/components/items/level-statistics.component.ts index ce015014..cee7ccb1 100644 --- a/src/app/components/items/level-statistics.component.ts +++ b/src/app/components/items/level-statistics.component.ts @@ -2,24 +2,32 @@ import {Component, Input} from '@angular/core'; import {Level} from "../../api/types/levels/level"; import {faHeart, faPlay, faStar, faThumbsDown, faThumbsUp} from "@fortawesome/free-solid-svg-icons"; import {StatisticComponent} from "../ui/info/statistic.component"; +import { getFormattedDateTime, getShortDateTime } from '../../helpers/date-time'; +import { LevelTeamPickStatusComponent } from "./level-team-pick-status.component"; @Component({ selector: 'app-level-statistics', imports: [ - StatisticComponent - ], + StatisticComponent, + LevelTeamPickStatusComponent +], template: ` -
+
+ @if (level.teamPicked) { + + }
` }) export class LevelStatisticsComponent { @Input({required: true}) level: Level = undefined!; + @Input() short: boolean = false; + protected readonly faThumbsUp = faThumbsUp; protected readonly faThumbsDown = faThumbsDown; protected readonly faHeart = faHeart; diff --git a/src/app/components/items/level-team-pick-status.component.ts b/src/app/components/items/level-team-pick-status.component.ts new file mode 100644 index 00000000..7ddd8358 --- /dev/null +++ b/src/app/components/items/level-team-pick-status.component.ts @@ -0,0 +1,43 @@ +import {Component, Input} from '@angular/core'; +import {Level} from "../../api/types/levels/level"; +import {faCircleCheck} from "@fortawesome/free-solid-svg-icons"; +import { FaIconComponent } from "@fortawesome/angular-fontawesome"; +import { TooltipComponent } from "../ui/text/tooltip.component"; +import { getFormattedDateTime, getShortDateTime } from '../../helpers/date-time'; + +@Component({ + selector: 'app-level-team-pick-status', + imports: [ + FaIconComponent, + TooltipComponent +], + template: ` + + +
+ + @if (!short) { +

team picked {{this.shortTime}}

+ } + +
+
+ ` +}) +export class LevelTeamPickStatusComponent { + @Input({required: true}) level: Level = undefined!; + @Input() short: boolean = false; + + protected formattedTime: string = "unknown"; + protected shortTime: string = ""; + + ngOnInit() { + if (this.level.dateTeamPicked != null) { + this.level.dateTeamPicked = new Date(this.level.dateTeamPicked); + this.shortTime = getShortDateTime(this.level.dateTeamPicked); + this.formattedTime = getFormattedDateTime(this.level.dateTeamPicked); + } + } + + protected readonly faCircleCheck = faCircleCheck; +} diff --git a/src/app/components/ui/info/date.component.ts b/src/app/components/ui/info/date.component.ts index 94c20148..6dcfcbb9 100644 --- a/src/app/components/ui/info/date.component.ts +++ b/src/app/components/ui/info/date.component.ts @@ -11,6 +11,7 @@ import { } from '@angular/core'; import {TooltipComponent} from "../text/tooltip.component"; import {isPlatformBrowser} from "@angular/common"; +import { getFormattedDateTime, getShortDateTime } from '../../../helpers/date-time'; @Component({ selector: 'app-date', @@ -44,8 +45,6 @@ export class DateComponent implements OnInit, OnDestroy { return isPlatformBrowser(this.platformId); } - protected recentText = "just now"; - // this setter is actually required to be here; this is not a hold-over from the old site // for some reason, javascript's date parser doesn't create a full Date object. // so we need to do this horse-shit. @@ -55,36 +54,11 @@ export class DateComponent implements OnInit, OnDestroy { } get moment(): string { - const now = new Date(); - const totalSeconds = Math.floor((now.getTime() - this._date.getTime()) / 1000); - - const intervals: { [key: string]: number } = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1, - }; - - if (totalSeconds < 20) - return this.recentText; - - for (const interval in intervals) { - const time = Math.floor(totalSeconds / intervals[interval]); - if (time > 1) { - return `${time} ${interval}s ago`; - } else if (time == 1) { - return `a${interval == "hour" ? 'n' : ''} ${interval} ago`; - } - } - - return this.recentText; + return getShortDateTime(this._date); } get formattedDate(): string { - return `${this._date.toLocaleDateString()} @ ${this._date.toLocaleTimeString()}`; + return getFormattedDateTime(this._date); } ngOnDestroy() { diff --git a/src/app/components/ui/info/larger-label.component.ts b/src/app/components/ui/info/larger-label.component.ts new file mode 100644 index 00000000..24e3818c --- /dev/null +++ b/src/app/components/ui/info/larger-label.component.ts @@ -0,0 +1,17 @@ +import {Component, Input} from '@angular/core'; +import {NgClass} from "@angular/common"; + +@Component({ + selector: 'app-larger-label', + imports: [ + NgClass + ], + template: ` +
+ +
+ ` +}) +export class LargerLabelComponent { + @Input() primary: boolean = false; +} diff --git a/src/app/components/ui/layouts/fancy-header-buttons.component.ts b/src/app/components/ui/layouts/fancy-header-buttons.component.ts index e6266145..4c4ce341 100644 --- a/src/app/components/ui/layouts/fancy-header-buttons.component.ts +++ b/src/app/components/ui/layouts/fancy-header-buttons.component.ts @@ -19,7 +19,7 @@ import {faEllipsisV} from "@fortawesome/free-solid-svg-icons"; -
+
-
- - +
+
+ + + +
+
+ +
-
+
+ +
` }) diff --git a/src/app/components/ui/text/links/original-publisher-router-link.component.ts b/src/app/components/ui/text/links/original-publisher-router-link.component.ts new file mode 100644 index 00000000..bab91d7a --- /dev/null +++ b/src/app/components/ui/text/links/original-publisher-router-link.component.ts @@ -0,0 +1,21 @@ +import {Component, Input} from '@angular/core'; +import {RouterLink} from "@angular/router"; +import { Level } from '../../../../api/types/levels/level'; + +@Component({ + selector: 'app-original-publisher-router-link', + imports: [ + RouterLink + ], + template: ` + + {{level.originalPublisher ?? this.unknownName}} + + + ` +}) +export class OriginalPublisherRouterLink { + @Input({required: true}) public level: Level = undefined!; + protected unknownName: string = "Unknown"; +} diff --git a/src/app/helpers/date-time.ts b/src/app/helpers/date-time.ts new file mode 100644 index 00000000..b709ed85 --- /dev/null +++ b/src/app/helpers/date-time.ts @@ -0,0 +1,33 @@ +export function getFormattedDateTime(date: Date) { + return `${date.toLocaleDateString()} @ ${date.toLocaleTimeString()}`; +} + +export function getShortDateTime(date: Date) { + const recentText = "just now"; + const now = new Date(); + const totalSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + if (totalSeconds < 20) + return recentText; + + for (const interval in timeIntervals) { + const time = Math.floor(totalSeconds / timeIntervals[interval]); + if (time > 1) { + return `${time} ${interval}s ago`; + } else if (time == 1) { + return `a${interval == "hour" ? 'n' : ''} ${interval} ago`; + } + } + + return recentText; +} + +export const timeIntervals: { [key: string]: number } = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1, +}; \ No newline at end of file diff --git a/src/app/pages/level/level.component.html b/src/app/pages/level/level.component.html index 44cf539f..cc3fdc7f 100644 --- a/src/app/pages/level/level.component.html +++ b/src/app/pages/level/level.component.html @@ -2,18 +2,55 @@ - - by - + @if (level.isReUpload) { +
+ originally by , + uploaded by +
+ } + @else { +

+ by +

+ }
-
- Published for {{level.gameVersion | game: isMobile}} - - @if(level.updateDate) {, - updated - + + +
+ + {{level.gameVersion | game: isMobile}} + + + @if (level.isModded) { + + Modded + }
+ + @if (!isMobile) { +
+
+

published

+
+ + @if(level.updateDate != level.publishDate) { +
+

updated

+
+ } +
+ } + @else { +
+ published + + @if(level.updateDate != level.publishDate) {, + updated + } +
+ } + @if(relations && !isMobile) { diff --git a/src/app/pages/level/level.component.ts b/src/app/pages/level/level.component.ts index e7272f92..619bff6d 100644 --- a/src/app/pages/level/level.component.ts +++ b/src/app/pages/level/level.component.ts @@ -3,7 +3,7 @@ import {Level} from "../../api/types/levels/level"; import {ClientService} from "../../api/client.service"; import {ActivatedRoute, RouterLink} from "@angular/router"; import {SlugPipe} from "../../pipes/slug.pipe"; -import { AsyncPipe, isPlatformBrowser, } from "@angular/common"; +import { AsyncPipe, isPlatformBrowser } from "@angular/common"; import {LevelStatisticsComponent} from "../../components/items/level-statistics.component"; import {DefaultPipe} from "../../pipes/default.pipe"; import {LevelAvatarComponent} from "../../components/ui/photos/level-avatar.component"; @@ -26,6 +26,9 @@ import {AuthenticationService} from "../../api/authentication.service"; import { ExtendedUser } from '../../api/types/users/extended-user'; import { FancyHeaderLevelButtonsComponent } from '../../components/ui/layouts/fancy-header-level-buttons.component'; import { LevelRelations } from '../../api/types/levels/level-relations'; +import { OriginalPublisherRouterLink } from "../../components/ui/text/links/original-publisher-router-link.component"; +import { LargerLabelComponent } from "../../components/ui/info/larger-label.component"; +import { TooltipComponent } from "../../components/ui/text/tooltip.component"; @Component({ @@ -47,7 +50,10 @@ import { LevelRelations } from '../../api/types/levels/level-relations'; PaneTitleComponent, EventPageComponent, RouterLink, - SlugPipe + SlugPipe, + OriginalPublisherRouterLink, + LargerLabelComponent, + TooltipComponent ], providers: [ SlugPipe