1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

Better live notification label

This commit is contained in:
Chocobozzz 2025-01-16 10:47:45 +01:00
parent d5c4cc2b44
commit 82246a0c8d
No known key found for this signature in database
GPG key ID: 583A612D890159BE
13 changed files with 73 additions and 35 deletions

View file

@ -66,6 +66,7 @@
</div> </div>
<my-user-notifications <my-user-notifications
#userNotifications
[ignoreLoadingBar]="true" [infiniteScroll]="false" [itemsPerPage]="10" [ignoreLoadingBar]="true" [infiniteScroll]="false" [itemsPerPage]="10"
[markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()" [markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()"
></my-user-notifications> ></my-user-notifications>

View file

@ -35,6 +35,7 @@ export class NotificationDropdownComponent implements OnInit, OnDestroy {
opened = false opened = false
markAllAsReadSubject = new Subject<boolean>() markAllAsReadSubject = new Subject<boolean>()
userNotificationReload = new Subject<boolean>()
private notificationSub: Subscription private notificationSub: Subscription
private routeSub: Subscription private routeSub: Subscription
@ -78,6 +79,8 @@ export class NotificationDropdownComponent implements OnInit, OnDestroy {
} }
onDropdownShown () { onDropdownShown () {
if (this.loaded) this.userNotificationReload.next(true)
this.opened = true this.opened = true
} }

View file

@ -63,11 +63,15 @@
<ng-container *ngSwitchCase="15"> <!-- UserNotificationType.ABUSE_STATE_CHANGE --> <ng-container *ngSwitchCase="15"> <!-- UserNotificationType.ABUSE_STATE_CHANGE -->
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" i18n> @if (isAbuseAccepted(notification)) {
<a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been <div class="message" i18n>
<ng-container *ngIf="isAccepted(notification)">accepted</ng-container> <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been accepted
<ng-container *ngIf="!isAccepted(notification)">rejected</ng-container> </div>
</div> } @else {
<div class="message" i18n>
<a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been accepted
</div>
}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="16"> <!-- UserNotificationType.ABUSE_NEW_MESSAGE --> <ng-container *ngSwitchCase="16"> <!-- UserNotificationType.ABUSE_NEW_MESSAGE -->
@ -227,9 +231,15 @@
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" /> <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
</a> </a>
<div class="message" i18n> @if (isVideoPublished(notification)) {
{{ notification.video.channel.displayName }} is live streaming in <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> <div class="message" i18n>
</div> {{ notification.video.channel.displayName }} is live streaming: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
</div>
} @else {
<div class="message" i18n>
{{ notification.video.channel.displayName }} went live: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
</div>
}
} @else { } @else {
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>

View file

@ -1,7 +1,7 @@
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { ComponentPagination, hasMoreItems, Notifier } from '@app/core' import { ComponentPagination, hasMoreItems, Notifier } from '@app/core'
import { AbuseState } from '@peertube/peertube-models' import { AbuseState, VideoState } from '@peertube/peertube-models'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { GlobalIconComponent } from '../shared-icons/global-icon.component' import { GlobalIconComponent } from '../shared-icons/global-icon.component'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
@ -22,6 +22,7 @@ export class UserNotificationsComponent implements OnInit {
@Input() infiniteScroll = true @Input() infiniteScroll = true
@Input() itemsPerPage = 20 @Input() itemsPerPage = 20
@Input() markAllAsReadSubject: Subject<boolean> @Input() markAllAsReadSubject: Subject<boolean>
@Input() userNotificationReload: Subject<boolean>
@Output() notificationsLoaded = new EventEmitter() @Output() notificationsLoaded = new EventEmitter()
@ -49,9 +50,15 @@ export class UserNotificationsComponent implements OnInit {
if (this.markAllAsReadSubject) { if (this.markAllAsReadSubject) {
this.markAllAsReadSubject.subscribe(() => this.markAllAsRead()) this.markAllAsReadSubject.subscribe(() => this.markAllAsRead())
} }
if (this.userNotificationReload) {
this.userNotificationReload.subscribe(() => this.loadNotifications(true))
}
} }
loadNotifications (reset?: boolean) { loadNotifications (reset?: boolean) {
if (reset) this.componentPagination.currentPage = 1
const options = { const options = {
pagination: this.componentPagination, pagination: this.componentPagination,
ignoreLoadingBar: this.ignoreLoadingBar, ignoreLoadingBar: this.ignoreLoadingBar,
@ -123,7 +130,11 @@ export class UserNotificationsComponent implements OnInit {
this.loadNotifications(true) this.loadNotifications(true)
} }
isAccepted (notification: UserNotification) { isAbuseAccepted (notification: UserNotification) {
return notification.abuse.state === AbuseState.ACCEPTED return notification.abuse.state === AbuseState.ACCEPTED
} }
isVideoPublished (notification: UserNotification) {
return notification.video.state.id === VideoState.PUBLISHED
}
} }

View file

@ -2,6 +2,7 @@ import { FollowState } from '../actors/index.js'
import { AbuseStateType } from '../moderation/index.js' import { AbuseStateType } from '../moderation/index.js'
import { PluginType_Type } from '../plugins/index.js' import { PluginType_Type } from '../plugins/index.js'
import { VideoConstant } from '../videos/video-constant.model.js' import { VideoConstant } from '../videos/video-constant.model.js'
import { VideoStateType } from '../videos/video-state.enum.js'
export const UserNotificationType = { export const UserNotificationType = {
NEW_VIDEO_FROM_SUBSCRIPTION: 1, NEW_VIDEO_FROM_SUBSCRIPTION: 1,
@ -49,6 +50,10 @@ export interface VideoInfo {
uuid: string uuid: string
shortUUID: string shortUUID: string
name: string name: string
state: {
id: VideoStateType
label: string
}
} }
export interface AvatarInfo { export interface AvatarInfo {

View file

@ -966,6 +966,7 @@ function checkVideo (video: any, videoName?: string, shortUUID?: string) {
expect(video.shortUUID).to.equal(shortUUID) expect(video.shortUUID).to.equal(shortUUID)
} }
expect(video.state).to.exist
expect(video.id).to.be.a('number') expect(video.id).to.be.a('number')
} }

View file

@ -78,7 +78,7 @@ export class NewVideoOrLiveForSubscribers extends AbstractNotification <MVideoAc
return { return {
to, to,
subject: channelName + ' is live streaming', subject: channelName + ' is live streaming',
text: `Your subscription ${channelName} is live streaming in "${this.payload.name}".`, text: `Your subscription ${channelName} is live streaming: "${this.payload.name}".`,
locals: { locals: {
title: 'New content ', title: 'New content ',
action: { action: {

View file

@ -80,6 +80,7 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery {
"Video"."id" AS "Video.id", "Video"."id" AS "Video.id",
"Video"."uuid" AS "Video.uuid", "Video"."uuid" AS "Video.uuid",
"Video"."name" AS "Video.name", "Video"."name" AS "Video.name",
"Video"."state" AS "Video.state",
"Video->VideoChannel"."id" AS "Video.VideoChannel.id", "Video->VideoChannel"."id" AS "Video.VideoChannel.id",
"Video->VideoChannel"."name" AS "Video.VideoChannel.name", "Video->VideoChannel"."name" AS "Video.VideoChannel.name",
"Video->VideoChannel->Actor"."id" AS "Video.VideoChannel.Actor.id", "Video->VideoChannel->Actor"."id" AS "Video.VideoChannel.Actor.id",
@ -106,18 +107,21 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery {
"VideoComment->Video"."id" AS "VideoComment.Video.id", "VideoComment->Video"."id" AS "VideoComment.Video.id",
"VideoComment->Video"."uuid" AS "VideoComment.Video.uuid", "VideoComment->Video"."uuid" AS "VideoComment.Video.uuid",
"VideoComment->Video"."name" AS "VideoComment.Video.name", "VideoComment->Video"."name" AS "VideoComment.Video.name",
"VideoComment->Video"."state" AS "VideoComment.Video.state",
"Abuse"."id" AS "Abuse.id", "Abuse"."id" AS "Abuse.id",
"Abuse"."state" AS "Abuse.state", "Abuse"."state" AS "Abuse.state",
"Abuse->VideoAbuse"."id" AS "Abuse.VideoAbuse.id", "Abuse->VideoAbuse"."id" AS "Abuse.VideoAbuse.id",
"Abuse->VideoAbuse->Video"."id" AS "Abuse.VideoAbuse.Video.id", "Abuse->VideoAbuse->Video"."id" AS "Abuse.VideoAbuse.Video.id",
"Abuse->VideoAbuse->Video"."uuid" AS "Abuse.VideoAbuse.Video.uuid", "Abuse->VideoAbuse->Video"."uuid" AS "Abuse.VideoAbuse.Video.uuid",
"Abuse->VideoAbuse->Video"."name" AS "Abuse.VideoAbuse.Video.name", "Abuse->VideoAbuse->Video"."name" AS "Abuse.VideoAbuse.Video.name",
"Abuse->VideoAbuse->Video"."state" AS "Abuse.VideoAbuse.Video.state",
"Abuse->VideoCommentAbuse"."id" AS "Abuse.VideoCommentAbuse.id", "Abuse->VideoCommentAbuse"."id" AS "Abuse.VideoCommentAbuse.id",
"Abuse->VideoCommentAbuse->VideoComment"."id" AS "Abuse.VideoCommentAbuse.VideoComment.id", "Abuse->VideoCommentAbuse->VideoComment"."id" AS "Abuse.VideoCommentAbuse.VideoComment.id",
"Abuse->VideoCommentAbuse->VideoComment"."originCommentId" AS "Abuse.VideoCommentAbuse.VideoComment.originCommentId", "Abuse->VideoCommentAbuse->VideoComment"."originCommentId" AS "Abuse.VideoCommentAbuse.VideoComment.originCommentId",
"Abuse->VideoCommentAbuse->VideoComment->Video"."id" AS "Abuse.VideoCommentAbuse.VideoComment.Video.id", "Abuse->VideoCommentAbuse->VideoComment->Video"."id" AS "Abuse.VideoCommentAbuse.VideoComment.Video.id",
"Abuse->VideoCommentAbuse->VideoComment->Video"."name" AS "Abuse.VideoCommentAbuse.VideoComment.Video.name", "Abuse->VideoCommentAbuse->VideoComment->Video"."name" AS "Abuse.VideoCommentAbuse.VideoComment.Video.name",
"Abuse->VideoCommentAbuse->VideoComment->Video"."uuid" AS "Abuse.VideoCommentAbuse.VideoComment.Video.uuid", "Abuse->VideoCommentAbuse->VideoComment->Video"."uuid" AS "Abuse.VideoCommentAbuse.VideoComment.Video.uuid",
"Abuse->VideoCommentAbuse->VideoComment->Video"."state" AS "Abuse.VideoCommentAbuse.VideoComment.Video.state",
"Abuse->FlaggedAccount"."id" AS "Abuse.FlaggedAccount.id", "Abuse->FlaggedAccount"."id" AS "Abuse.FlaggedAccount.id",
"Abuse->FlaggedAccount"."name" AS "Abuse.FlaggedAccount.name", "Abuse->FlaggedAccount"."name" AS "Abuse.FlaggedAccount.name",
"Abuse->FlaggedAccount"."description" AS "Abuse.FlaggedAccount.description", "Abuse->FlaggedAccount"."description" AS "Abuse.FlaggedAccount.description",
@ -138,6 +142,7 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery {
"VideoBlacklist->Video"."id" AS "VideoBlacklist.Video.id", "VideoBlacklist->Video"."id" AS "VideoBlacklist.Video.id",
"VideoBlacklist->Video"."uuid" AS "VideoBlacklist.Video.uuid", "VideoBlacklist->Video"."uuid" AS "VideoBlacklist.Video.uuid",
"VideoBlacklist->Video"."name" AS "VideoBlacklist.Video.name", "VideoBlacklist->Video"."name" AS "VideoBlacklist.Video.name",
"VideoBlacklist->Video"."state" AS "VideoBlacklist.Video.name",
"VideoImport"."id" AS "VideoImport.id", "VideoImport"."id" AS "VideoImport.id",
"VideoImport"."magnetUri" AS "VideoImport.magnetUri", "VideoImport"."magnetUri" AS "VideoImport.magnetUri",
"VideoImport"."targetUrl" AS "VideoImport.targetUrl", "VideoImport"."targetUrl" AS "VideoImport.targetUrl",
@ -145,6 +150,7 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery {
"VideoImport->Video"."id" AS "VideoImport.Video.id", "VideoImport->Video"."id" AS "VideoImport.Video.id",
"VideoImport->Video"."uuid" AS "VideoImport.Video.uuid", "VideoImport->Video"."uuid" AS "VideoImport.Video.uuid",
"VideoImport->Video"."name" AS "VideoImport.Video.name", "VideoImport->Video"."name" AS "VideoImport.Video.name",
"VideoImport->Video"."state" AS "VideoImport.Video.name",
"Plugin"."id" AS "Plugin.id", "Plugin"."id" AS "Plugin.id",
"Plugin"."name" AS "Plugin.name", "Plugin"."name" AS "Plugin.name",
"Plugin"."type" AS "Plugin.type", "Plugin"."type" AS "Plugin.type",
@ -188,7 +194,8 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery {
"VideoCaption"."language" AS "VideoCaption.language", "VideoCaption"."language" AS "VideoCaption.language",
"VideoCaption->Video"."id" AS "VideoCaption.Video.id", "VideoCaption->Video"."id" AS "VideoCaption.Video.id",
"VideoCaption->Video"."uuid" AS "VideoCaption.Video.uuid", "VideoCaption->Video"."uuid" AS "VideoCaption.Video.uuid",
"VideoCaption->Video"."name" AS "VideoCaption.Video.name"` "VideoCaption->Video"."name" AS "VideoCaption.Video.name",
"VideoCaption->Video"."state" AS "VideoCaption.Video.state"`
} }
private getJoins () { private getJoins () {

View file

@ -12,6 +12,7 @@ import { ActorFollowModel } from '../actor/actor-follow.js'
import { ApplicationModel } from '../application/application.js' import { ApplicationModel } from '../application/application.js'
import { PluginModel } from '../server/plugin.js' import { PluginModel } from '../server/plugin.js'
import { SequelizeModel, throwIfNotValid } from '../shared/index.js' import { SequelizeModel, throwIfNotValid } from '../shared/index.js'
import { getStateLabel } from '../video/formatter/video-api-format.js'
import { VideoBlacklistModel } from '../video/video-blacklist.js' import { VideoBlacklistModel } from '../video/video-blacklist.js'
import { VideoCaptionModel } from '../video/video-caption.js' import { VideoCaptionModel } from '../video/video-caption.js'
import { VideoCommentModel } from '../video/video-comment.js' import { VideoCommentModel } from '../video/video-comment.js'
@ -490,7 +491,11 @@ export class UserNotificationModel extends SequelizeModel<UserNotificationModel>
id: video.id, id: video.id,
uuid: video.uuid, uuid: video.uuid,
shortUUID: uuidToShort(video.uuid), shortUUID: uuidToShort(video.uuid),
name: video.name name: video.name,
state: {
id: video.state,
label: getStateLabel(video.state)
}
} }
} }
@ -500,12 +505,7 @@ export class UserNotificationModel extends SequelizeModel<UserNotificationModel>
threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
video: abuse.VideoCommentAbuse.VideoComment.Video video: abuse.VideoCommentAbuse.VideoComment.Video
? { ? this.formatVideo(abuse.VideoCommentAbuse.VideoComment.Video)
id: abuse.VideoCommentAbuse.VideoComment.Video.id,
name: abuse.VideoCommentAbuse.VideoComment.Video.name,
shortUUID: uuidToShort(abuse.VideoCommentAbuse.VideoComment.Video.uuid),
uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
}
: undefined : undefined
} }
: undefined : undefined

View file

@ -30,14 +30,16 @@ import { SequelizeModel, buildWhereIdOrUUID, throwIfNotValid } from '../shared/i
import { VideoModel } from './video.js' import { VideoModel } from './video.js'
export enum ScopeNames { export enum ScopeNames {
WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' CAPTION_WITH_VIDEO = 'CAPTION_WITH_VIDEO'
} }
const videoAttributes = [ 'id', 'name', 'remote', 'uuid', 'url', 'state' ]
@Scopes(() => ({ @Scopes(() => ({
[ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { [ScopeNames.CAPTION_WITH_VIDEO]: {
include: [ include: [
{ {
attributes: [ 'id', 'uuid', 'remote' ], attributes: videoAttributes,
model: VideoModel.unscoped(), model: VideoModel.unscoped(),
required: true required: true
} }
@ -130,17 +132,13 @@ export class VideoCaptionModel extends SequelizeModel<VideoCaptionModel> {
static loadByVideoIdAndLanguage (videoId: string | number, language: string, transaction?: Transaction): Promise<MVideoCaptionVideo> { static loadByVideoIdAndLanguage (videoId: string | number, language: string, transaction?: Transaction): Promise<MVideoCaptionVideo> {
const videoInclude = { const videoInclude = {
model: VideoModel.unscoped(), model: VideoModel.unscoped(),
attributes: [ 'id', 'name', 'remote', 'uuid', 'url' ], attributes: videoAttributes,
where: buildWhereIdOrUUID(videoId) where: buildWhereIdOrUUID(videoId)
} }
const query = { const query = {
where: { where: { language },
language include: [ videoInclude ],
},
include: [
videoInclude
],
transaction transaction
} }
@ -155,7 +153,7 @@ export class VideoCaptionModel extends SequelizeModel<VideoCaptionModel> {
include: [ include: [
{ {
model: VideoModel.unscoped(), model: VideoModel.unscoped(),
attributes: [ 'id', 'remote', 'uuid' ] attributes: videoAttributes
} }
] ]
} }
@ -186,7 +184,7 @@ export class VideoCaptionModel extends SequelizeModel<VideoCaptionModel> {
transaction transaction
} }
return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query) return VideoCaptionModel.scope(ScopeNames.CAPTION_WITH_VIDEO).findAll(query)
} }
static async listCaptionsOfMultipleVideos (videoIds: number[], transaction?: Transaction) { static async listCaptionsOfMultipleVideos (videoIds: number[], transaction?: Transaction) {
@ -200,7 +198,7 @@ export class VideoCaptionModel extends SequelizeModel<VideoCaptionModel> {
transaction transaction
} }
const captions = await VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll<MVideoCaptionVideo>(query) const captions = await VideoCaptionModel.scope(ScopeNames.CAPTION_WITH_VIDEO).findAll<MVideoCaptionVideo>(query)
const result: { [ id: number ]: MVideoCaptionVideo[] } = {} const result: { [ id: number ]: MVideoCaptionVideo[] } = {}
for (const id of videoIds) { for (const id of videoIds) {

View file

@ -25,7 +25,7 @@ type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationMo
export module UserNotificationIncludes { export module UserNotificationIncludes {
export type ActorImageInclude = Pick<ActorImageModel, 'createdAt' | 'filename' | 'getStaticPath' | 'width' | 'updatedAt'> export type ActorImageInclude = Pick<ActorImageModel, 'createdAt' | 'filename' | 'getStaticPath' | 'width' | 'updatedAt'>
export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name' | 'state'>
export type VideoIncludeChannel = export type VideoIncludeChannel =
VideoInclude & VideoInclude &
PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor>
@ -58,7 +58,7 @@ export module UserNotificationIncludes {
Pick<VideoCommentAbuseModel, 'id'> & Pick<VideoCommentAbuseModel, 'id'> &
PickWith<VideoCommentAbuseModel, 'VideoComment', PickWith<VideoCommentAbuseModel, 'VideoComment',
Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid'>>> PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid' | 'state'>>>
export type AbuseInclude = export type AbuseInclude =
Pick<AbuseModel, 'id' | 'state'> & Pick<AbuseModel, 'id' | 'state'> &

View file

@ -17,7 +17,7 @@ export type MVideoCaptionLanguageUrl =
export type MVideoCaptionVideo = export type MVideoCaptionVideo =
MVideoCaption & MVideoCaption &
Use<'Video', Pick<MVideo, 'id' | 'name' | 'remote' | 'uuid' | 'url' | 'getWatchStaticPath'>> Use<'Video', Pick<MVideo, 'id' | 'name' | 'remote' | 'uuid' | 'url' | 'state' | 'getWatchStaticPath'>>
// ############################################################################ // ############################################################################

View file

@ -8252,6 +8252,8 @@ components:
$ref: '#/components/schemas/Video/properties/uuid' $ref: '#/components/schemas/Video/properties/uuid'
name: name:
$ref: '#/components/schemas/Video/properties/name' $ref: '#/components/schemas/Video/properties/name'
state:
$ref: '#/components/schemas/Video/properties/state'
Video: Video:
properties: properties:
id: id: