Add "local" videos in menu
This commit is contained in:
parent
66c3b7744c
commit
066e94c538
|
@ -24,6 +24,11 @@
|
||||||
* Added `video.uuid` field
|
* Added `video.uuid` field
|
||||||
* Added `video.url` field
|
* Added `video.url` field
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add "Local" in menu that lists only local videos
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## v1.0.0-alpha.4
|
## v1.0.0-alpha.4
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,11 @@
|
||||||
<span class="icon icon-videos-recently-added"></span>
|
<span class="icon icon-videos-recently-added"></span>
|
||||||
Recently added
|
Recently added
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a routerLink="/videos/local" routerLinkActive="active">
|
||||||
|
<span class="icon icon-videos-local"></span>
|
||||||
|
Local
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
|
|
|
@ -126,6 +126,14 @@ menu {
|
||||||
background-image: url('../../assets/images/menu/recently-added.svg');
|
background-image: url('../../assets/images/menu/recently-added.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.icon-videos-local {
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
background-image: url('../../assets/images/menu/home.svg');
|
||||||
|
}
|
||||||
|
|
||||||
&.icon-administration {
|
&.icon-administration {
|
||||||
width: 23px;
|
width: 23px;
|
||||||
height: 23px;
|
height: 23px;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } fr
|
||||||
import { ResultList } from '../../../../../shared/models/result-list.model'
|
import { ResultList } from '../../../../../shared/models/result-list.model'
|
||||||
import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
|
import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
|
||||||
import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
|
import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
|
||||||
|
import { VideoFilter } from '../../../../../shared/models/videos/video-query.type'
|
||||||
import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
|
import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
|
||||||
import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
|
import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
|
||||||
import { environment } from '../../../environments/environment'
|
import { environment } from '../../../environments/environment'
|
||||||
|
@ -94,12 +95,20 @@ export class VideoService {
|
||||||
.catch((res) => this.restExtractor.handleError(res))
|
.catch((res) => this.restExtractor.handleError(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideos (videoPagination: ComponentPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
|
getVideos (
|
||||||
|
videoPagination: ComponentPagination,
|
||||||
|
sort: SortField,
|
||||||
|
filter?: VideoFilter
|
||||||
|
): Observable<{ videos: Video[], totalVideos: number}> {
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
params = params.set('filter', filter)
|
||||||
|
}
|
||||||
|
|
||||||
return this.authHttp
|
return this.authHttp
|
||||||
.get(VideoService.BASE_VIDEO_URL, { params })
|
.get(VideoService.BASE_VIDEO_URL, { params })
|
||||||
.map(this.extractVideos)
|
.map(this.extractVideos)
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
|
import { immutableAssign } from '@app/shared/misc/utils'
|
||||||
|
import { NotificationsService } from 'angular2-notifications'
|
||||||
|
import { AuthService } from '../../core/auth'
|
||||||
|
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||||
|
import { SortField } from '../../shared/video/sort-field.type'
|
||||||
|
import { VideoService } from '../../shared/video/video.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-videos-local',
|
||||||
|
styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
|
||||||
|
templateUrl: '../../shared/video/abstract-video-list.html'
|
||||||
|
})
|
||||||
|
export class VideoLocalComponent extends AbstractVideoList implements OnInit {
|
||||||
|
titlePage = 'Local videos'
|
||||||
|
currentRoute = '/videos/local'
|
||||||
|
sort = '-createdAt' as SortField
|
||||||
|
|
||||||
|
constructor (protected router: Router,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected authService: AuthService,
|
||||||
|
private videoService: VideoService) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
super.ngOnInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
getVideosObservable (page: number) {
|
||||||
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
|
|
||||||
|
return this.videoService.getVideos(newPagination, this.sort, 'local')
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
|
import { VideoLocalComponent } from '@app/videos/video-list/video-local.component'
|
||||||
import { MetaGuard } from '@ngx-meta/core'
|
import { MetaGuard } from '@ngx-meta/core'
|
||||||
import { VideoSearchComponent } from './video-list'
|
import { VideoSearchComponent } from './video-list'
|
||||||
import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
|
import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
|
||||||
|
@ -35,6 +36,15 @@ const videosRoutes: Routes = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'local',
|
||||||
|
component: VideoLocalComponent,
|
||||||
|
data: {
|
||||||
|
meta: {
|
||||||
|
title: 'Local videos'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'search',
|
path: 'search',
|
||||||
component: VideoSearchComponent,
|
component: VideoSearchComponent,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
|
import { VideoLocalComponent } from '@app/videos/video-list/video-local.component'
|
||||||
import { SharedModule } from '../shared'
|
import { SharedModule } from '../shared'
|
||||||
import { VideoSearchComponent } from './video-list'
|
import { VideoSearchComponent } from './video-list'
|
||||||
import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
|
import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
|
||||||
|
@ -17,6 +18,7 @@ import { VideosComponent } from './videos.component'
|
||||||
|
|
||||||
VideoTrendingComponent,
|
VideoTrendingComponent,
|
||||||
VideoRecentlyAddedComponent,
|
VideoRecentlyAddedComponent,
|
||||||
|
VideoLocalComponent,
|
||||||
VideoSearchComponent
|
VideoSearchComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>home</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="Artboard-4" transform="translate(-620.000000, -159.000000)" stroke="#808080" stroke-width="2">
|
||||||
|
<g id="34" transform="translate(620.000000, 159.000000)">
|
||||||
|
<path d="M1,11 L12,2 C12,2 22.9999989,11.0000005 23,11" id="Path-50"></path>
|
||||||
|
<path d="M3,10 C3,10 3,10.4453982 3,10.9968336 L3,20.0170446 C3,20.5675806 3.43788135,21.0138782 4.00292933,21.0138781 L8.99707067,21.0138779 C9.55097324,21.0138779 10,20.5751284 10,20.0089602 L10,15.0049177 C10,14.449917 10.4433532,14 11.0093689,14 L12.9906311,14 C13.5480902,14 14,14.4387495 14,15.0049177 L14,20.0089602 C14,20.5639609 14.4378817,21.0138779 15.0029302,21.0138779 L19.9970758,21.0138781 C20.5509789,21.0138782 21.000006,20.56848 21.000006,20.0170446 L21.0000057,10" id="Path-51"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -388,7 +388,7 @@ async function getVideoDescription (req: express.Request, res: express.Response)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort)
|
const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.filter)
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
import { VideoPrivacy, VideoResolution } from '../../../shared'
|
import { VideoPrivacy, VideoResolution } from '../../../shared'
|
||||||
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
||||||
import { Video, VideoDetails } from '../../../shared/models/videos'
|
import { Video, VideoDetails } from '../../../shared/models/videos'
|
||||||
|
import { VideoFilter } from '../../../shared/models/videos/video-query.type'
|
||||||
import { activityPubCollection } from '../../helpers/activitypub'
|
import { activityPubCollection } from '../../helpers/activitypub'
|
||||||
import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
|
import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
|
@ -91,7 +92,7 @@ enum ScopeNames {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes({
|
||||||
[ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number) => ({
|
[ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter) => ({
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
[Sequelize.Op.notIn]: Sequelize.literal(
|
[Sequelize.Op.notIn]: Sequelize.literal(
|
||||||
|
@ -129,6 +130,7 @@ enum ScopeNames {
|
||||||
attributes: [ 'preferredUsername', 'url', 'serverId' ],
|
attributes: [ 'preferredUsername', 'url', 'serverId' ],
|
||||||
model: ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
|
where: VideoModel.buildActorWhereWithFilter(filter),
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'host' ],
|
attributes: [ 'host' ],
|
||||||
|
@ -639,7 +641,7 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async listForApi (start: number, count: number, sort: string) {
|
static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter) {
|
||||||
const query = {
|
const query = {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
|
@ -648,7 +650,7 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
|
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] })
|
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter ] })
|
||||||
.findAndCountAll(query)
|
.findAndCountAll(query)
|
||||||
.then(({ rows, count }) => {
|
.then(({ rows, count }) => {
|
||||||
return {
|
return {
|
||||||
|
@ -790,6 +792,16 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static buildActorWhereWithFilter (filter?: VideoFilter) {
|
||||||
|
if (filter && filter === 'local') {
|
||||||
|
return {
|
||||||
|
serverId: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
getOriginalFile () {
|
getOriginalFile () {
|
||||||
if (Array.isArray(this.VideoFiles) === false) return undefined
|
if (Array.isArray(this.VideoFiles) === false) return undefined
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
dateIsValid,
|
dateIsValid,
|
||||||
doubleFollow,
|
doubleFollow,
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
flushTests,
|
flushTests, getLocalVideos,
|
||||||
getVideo,
|
getVideo,
|
||||||
getVideoChannelsList,
|
getVideoChannelsList,
|
||||||
getVideosList,
|
getVideosList,
|
||||||
|
@ -349,6 +349,36 @@ describe('Test multiple servers', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('It should list local videos', function () {
|
||||||
|
it('Should list only local videos on server 1', async function () {
|
||||||
|
const { body } = await getLocalVideos(servers[0].url)
|
||||||
|
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data).to.be.an('array')
|
||||||
|
expect(body.data.length).to.equal(1)
|
||||||
|
expect(body.data[0].name).to.equal('my super name for server 1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should list only local videos on server 2', async function () {
|
||||||
|
const { body } = await getLocalVideos(servers[1].url)
|
||||||
|
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data).to.be.an('array')
|
||||||
|
expect(body.data.length).to.equal(1)
|
||||||
|
expect(body.data[0].name).to.equal('my super name for server 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should list only local videos on server 3', async function () {
|
||||||
|
const { body } = await getLocalVideos(servers[2].url)
|
||||||
|
|
||||||
|
expect(body.total).to.equal(2)
|
||||||
|
expect(body.data).to.be.an('array')
|
||||||
|
expect(body.data.length).to.equal(2)
|
||||||
|
expect(body.data[0].name).to.equal('my super name for server 3')
|
||||||
|
expect(body.data[1].name).to.equal('my super name for server 3-2')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Should seed the uploaded video', function () {
|
describe('Should seed the uploaded video', function () {
|
||||||
it('Should add the file 1 by asking server 3', async function () {
|
it('Should add the file 1 by asking server 3', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
|
@ -123,6 +123,17 @@ function getVideosList (url: string) {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLocalVideos (url: string) {
|
||||||
|
const path = '/api/v1/videos'
|
||||||
|
|
||||||
|
return request(url)
|
||||||
|
.get(path)
|
||||||
|
.query({ sort: 'name', filter: 'local' })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect(200)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
}
|
||||||
|
|
||||||
function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
|
function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
|
||||||
const path = '/api/v1/users/me/videos'
|
const path = '/api/v1/users/me/videos'
|
||||||
|
|
||||||
|
@ -487,6 +498,7 @@ export {
|
||||||
rateVideo,
|
rateVideo,
|
||||||
viewVideo,
|
viewVideo,
|
||||||
parseTorrentVideo,
|
parseTorrentVideo,
|
||||||
|
getLocalVideos,
|
||||||
completeVideoCheck,
|
completeVideoCheck,
|
||||||
checkVideoFilesWereRemoved
|
checkVideoFilesWereRemoved
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export type VideoFilter = 'local'
|
Loading…
Reference in New Issue