Add video pages.

This commit is contained in:
Haifeng Luo 2022-05-07 15:01:50 +08:00
parent 36f2787837
commit c38af47c59
18 changed files with 790 additions and 3 deletions

61
controllers/video.go Normal file
View File

@ -0,0 +1,61 @@
package controllers
import (
"encoding/json"
"github.com/openbrainorg/openbrain/object"
)
func (c *ApiController) GetGlobalVideos() {
c.Data["json"] = object.GetGlobalVideos()
c.ServeJSON()
}
func (c *ApiController) GetVideos() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetVideos(owner)
c.ServeJSON()
}
func (c *ApiController) GetVideo() {
id := c.Input().Get("id")
c.Data["json"] = object.GetVideo(id)
c.ServeJSON()
}
func (c *ApiController) UpdateVideo() {
id := c.Input().Get("id")
var video object.Video
err := json.Unmarshal(c.Ctx.Input.RequestBody, &video)
if err != nil {
panic(err)
}
c.Data["json"] = object.UpdateVideo(id, &video)
c.ServeJSON()
}
func (c *ApiController) AddVideo() {
var video object.Video
err := json.Unmarshal(c.Ctx.Input.RequestBody, &video)
if err != nil {
panic(err)
}
c.Data["json"] = object.AddVideo(&video)
c.ServeJSON()
}
func (c *ApiController) DeleteVideo() {
var video object.Video
err := json.Unmarshal(c.Ctx.Input.RequestBody, &video)
if err != nil {
panic(err)
}
c.Data["json"] = object.DeleteVideo(&video)
c.ServeJSON()
}

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/openbrainorg/openbrain
go 1.16 go 1.16
require ( require (
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1585
github.com/astaxie/beego v1.12.3 github.com/astaxie/beego v1.12.3
github.com/casdoor/casdoor-go-sdk v0.3.3 github.com/casdoor/casdoor-go-sdk v0.3.3
github.com/danaugrs/go-tsne/tsne v0.0.0-20220306155740-2250969e057f github.com/danaugrs/go-tsne/tsne v0.0.0-20220306155740-2250969e057f

10
go.sum
View File

@ -51,6 +51,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1585 h1:ECnkjykkSn3Gsibjd8FrcC+8SMDJcUbJOP4iT2hItrw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1585/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -159,6 +161,7 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@ -301,8 +304,10 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -875,6 +880,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -887,8 +894,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -94,4 +94,9 @@ func (a *Adapter) createTable() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = a.engine.Sync2(new(Video))
if err != nil {
panic(err)
}
} }

98
object/video.go Normal file
View File

@ -0,0 +1,98 @@
package object
import (
"fmt"
"github.com/openbrainorg/openbrain/util"
"github.com/openbrainorg/openbrain/video"
"xorm.io/core"
)
type Video struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(500)" json:"displayName"`
VideoId string `xorm:"varchar(100)" json:"videoId"`
CoverUrl string `xorm:"varchar(200)" json:"coverUrl"`
PlayAuth string `xorm:"-" json:"playAuth"`
}
func GetGlobalVideos() []*Video {
videos := []*Video{}
err := adapter.engine.Asc("owner").Desc("created_time").Find(&videos)
if err != nil {
panic(err)
}
return videos
}
func GetVideos(owner string) []*Video {
videos := []*Video{}
err := adapter.engine.Desc("created_time").Find(&videos, &Video{Owner: owner})
if err != nil {
panic(err)
}
return videos
}
func getVideo(owner string, name string) *Video {
v := Video{Owner: owner, Name: name}
existed, err := adapter.engine.Get(&v)
if err != nil {
panic(err)
}
if existed {
v.PlayAuth = video.GetVideoPlayAuth(v.VideoId)
return &v
} else {
return nil
}
}
func GetVideo(id string) *Video {
owner, name := util.GetOwnerAndNameFromId(id)
return getVideo(owner, name)
}
func UpdateVideo(id string, video *Video) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getVideo(owner, name) == nil {
return false
}
_, err := adapter.engine.ID(core.PK{owner, name}).AllCols().Update(video)
if err != nil {
panic(err)
}
//return affected != 0
return true
}
func AddVideo(video *Video) bool {
affected, err := adapter.engine.Insert(video)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteVideo(video *Video) bool {
affected, err := adapter.engine.ID(core.PK{video.Owner, video.Name}).Delete(&Video{})
if err != nil {
panic(err)
}
return affected != 0
}
func (video *Video) GetId() string {
return fmt.Sprintf("%s/%s", video.Owner, video.Name)
}

View File

@ -38,4 +38,11 @@ func initAPI() {
beego.Router("/api/update-vectorset", &controllers.ApiController{}, "POST:UpdateVectorset") beego.Router("/api/update-vectorset", &controllers.ApiController{}, "POST:UpdateVectorset")
beego.Router("/api/add-vectorset", &controllers.ApiController{}, "POST:AddVectorset") beego.Router("/api/add-vectorset", &controllers.ApiController{}, "POST:AddVectorset")
beego.Router("/api/delete-vectorset", &controllers.ApiController{}, "POST:DeleteVectorset") beego.Router("/api/delete-vectorset", &controllers.ApiController{}, "POST:DeleteVectorset")
beego.Router("/api/get-global-videos", &controllers.ApiController{}, "GET:GetGlobalVideos")
beego.Router("/api/get-videos", &controllers.ApiController{}, "GET:GetVideos")
beego.Router("/api/get-video", &controllers.ApiController{}, "GET:GetVideo")
beego.Router("/api/update-video", &controllers.ApiController{}, "POST:UpdateVideo")
beego.Router("/api/add-video", &controllers.ApiController{}, "POST:AddVideo")
beego.Router("/api/delete-video", &controllers.ApiController{}, "POST:DeleteVideo")
} }

5
video/conf.go Normal file
View File

@ -0,0 +1,5 @@
package video
var regionId = ""
var accessKeyId = ""
var accessKeySecret = ""

18
video/init.go Normal file
View File

@ -0,0 +1,18 @@
package video
import "github.com/aliyun/alibaba-cloud-sdk-go/services/vod"
var vodClient *vod.Client
func init() {
vodClient = InitVodClient()
}
func InitVodClient() *vod.Client {
vodClient, err := vod.NewClientWithAccessKey(regionId, accessKeyId, accessKeySecret)
if err != nil {
panic(err)
}
return vodClient
}

17
video/vod_api.go Normal file
View File

@ -0,0 +1,17 @@
package video
import "github.com/aliyun/alibaba-cloud-sdk-go/services/vod"
func GetVideoPlayAuth(videoId string) string {
r := vod.CreateGetVideoPlayAuthRequest()
r.VideoId = videoId
r.AcceptFormat = "JSON"
resp, err := vodClient.GetVideoPlayAuth(r)
if err != nil {
panic(err)
}
playAuth := resp.PlayAuth
return playAuth
}

View File

@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "4.6.2", "@ant-design/icons": "4.6.2",
"@craco/craco": "6.1.1", "@craco/craco": "6.1.1",
"aliplayer-react": "^0.7.0",
"antd": "4.15.5", "antd": "4.15.5",
"casdoor-js-sdk": "^0.2.7", "casdoor-js-sdk": "^0.2.7",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",

View File

@ -13,6 +13,8 @@ import WordsetEditPage from "./WordsetEditPage";
import WordsetGraphPage from "./WordsetGraphPage"; import WordsetGraphPage from "./WordsetGraphPage";
import VectorsetListPage from "./VectorsetListPage"; import VectorsetListPage from "./VectorsetListPage";
import VectorsetEditPage from "./VectorsetEditPage"; import VectorsetEditPage from "./VectorsetEditPage";
import VideoListPage from "./VideoListPage";
import VideoEditPage from "./VideoEditPage";
import SigninPage from "./SigninPage"; import SigninPage from "./SigninPage";
import i18next from "i18next"; import i18next from "i18next";
import SelectLanguageBox from "./SelectLanguageBox"; import SelectLanguageBox from "./SelectLanguageBox";
@ -58,6 +60,8 @@ class App extends Component {
this.setState({ selectedMenuKey: '/wordsets' }); this.setState({ selectedMenuKey: '/wordsets' });
} else if (uri.includes('/vectorsets')) { } else if (uri.includes('/vectorsets')) {
this.setState({ selectedMenuKey: '/vectorsets' }); this.setState({ selectedMenuKey: '/vectorsets' });
} else if (uri.includes('/videos')) {
this.setState({ selectedMenuKey: '/videos' });
} else { } else {
this.setState({selectedMenuKey: 'null'}); this.setState({selectedMenuKey: 'null'});
} }
@ -231,6 +235,13 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="/videos">
<Link to="/videos">
{i18next.t("general:Videos")}
</Link>
</Menu.Item>
);
return res; return res;
} }
@ -286,6 +297,8 @@ class App extends Component {
<Route exact path="/wordsets/:wordsetName/graph" render={(props) => this.renderSigninIfNotSignedIn(<WordsetGraphPage account={this.state.account} {...props} />)}/> <Route exact path="/wordsets/:wordsetName/graph" render={(props) => this.renderSigninIfNotSignedIn(<WordsetGraphPage account={this.state.account} {...props} />)}/>
<Route exact path="/vectorsets" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetListPage account={this.state.account} {...props} />)}/> <Route exact path="/vectorsets" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetListPage account={this.state.account} {...props} />)}/>
<Route exact path="/vectorsets/:vectorsetName" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetEditPage account={this.state.account} {...props} />)}/> <Route exact path="/vectorsets/:vectorsetName" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/videos" render={(props) => this.renderSigninIfNotSignedIn(<VideoListPage account={this.state.account} {...props} />)}/>
<Route exact path="/videos/:videoName" render={(props) => this.renderSigninIfNotSignedIn(<VideoEditPage account={this.state.account} {...props} />)}/>
</Switch> </Switch>
</div> </div>
) )

92
web/src/Video.js Normal file
View File

@ -0,0 +1,92 @@
import React from "react";
import Player from 'aliplayer-react';
import * as Setting from "./Setting";
class Video extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
player: null,
width: !Setting.isMobile() ? this.props.task.video.width : "100%",
height: "100%",
};
}
updateVideoSize(width, height) {
if (this.props.onUpdateVideoSize !== undefined) {
this.props.onUpdateVideoSize(width, height);
}
}
handleReady(player) {
let videoWidth = player.tag.videoWidth;
let videoHeight = player.tag.videoHeight;
if (this.props.onUpdateVideoSize !== undefined) {
if (videoWidth !== 0 && videoHeight !== 0) {
this.updateVideoSize(videoWidth, videoHeight);
}
} else {
videoWidth = this.props.task.video.videoWidth;
videoHeight = this.props.task.video.videoHeight;
}
const myWidth = player.tag.scrollWidth;
const myHeight = videoHeight * myWidth / videoWidth;
player.setPlayerSize(myWidth, myHeight);
this.setState({
width: myWidth,
height: myHeight,
});
}
initPlayer(player) {
player.on('ready', () => {this.handleReady(player)});
}
render() {
const video = this.props.task.video;
const config = {
source: video.source,
cover: video.cover,
width: !Setting.isMobile() ? video.width : "100%",
height: "100%",
autoplay: video.autoplay,
isLive: video.isLive,
rePlay: video.rePlay,
playsinline: video.playsinline,
preload: video.preload,
controlBarVisibility: video.controlBarVisibility,
useH5Prism: video.useH5Prism,
// components: [
// {
// name: "RateComponent",
// type: Player.components.RateComponent,
// }
// ]
};
if (video.source !== undefined) {
config.source = video.source;
} else {
config.vid = video.vid;
config.playauth = video.playAuth;
}
return (
<div style={{width: this.state.width, height: this.state.height, margin: "auto"}}>
<Player
config={config}
onGetInstance={player => {
this.initPlayer(player);
}}
/>
</div>
)
}
}
export default Video;

207
web/src/VideoEditPage.js Normal file
View File

@ -0,0 +1,207 @@
import React from "react";
import {Button, Card, Col, Input, Row} from 'antd';
import * as VideoBackend from "./backend/VideoBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import {LinkOutlined} from "@ant-design/icons";
import Video from "./Video";
class VideoEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
videoName: props.match.params.videoName,
video: null,
};
}
componentWillMount() {
this.getVideo();
}
getVideo() {
VideoBackend.getVideo(this.props.account.name, this.state.videoName)
.then((video) => {
this.setState({
video: video,
});
});
}
parseVideoField(key, value) {
if (["score"].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateVideoField(key, value) {
value = this.parseVideoField(key, value);
let video = this.state.video;
video[key] = value;
this.setState({
video: video,
});
}
renderVideoContent() {
let task = {};
task.video = {
vid: this.state.video.videoId,
playAuth: this.state.video.playAuth,
cover: this.state.video.coverUrl,
videoWidth: 1920,
videoHeight: 1080,
width: "840px",
autoplay: false,
isLive: false,
rePlay: false,
playsinline: true,
preload: true,
controlBarVisibility: "hover",
useH5Prism: true,
};
return (
<div style={{marginTop: "10px", textAlign: "center"}}>
{/*{*/}
{/* JSON.stringify(this.state.video)*/}
{/*}*/}
<div style={{fontSize: 30, marginBottom: "20px"}}>
{
this.state.video.name
}
</div>
<Video task={task} />
</div>
)
}
renderVideo() {
return (
<Card size="small" title={
<div>
{i18next.t("video:Edit Video")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitVideoEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div>
} style={{marginLeft: '5px'}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}:
</Col>
<Col span={22} >
<Input value={this.state.video.name} onChange={e => {
this.updateVideoField('name', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Display name")}:
</Col>
<Col span={22} >
<Input value={this.state.video.displayName} onChange={e => {
this.updateVideoField('displayName', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Video ID")}:
</Col>
<Col span={22} >
<Input value={this.state.video.videoId} onChange={e => {
this.updateVideoField('videoId', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Cover")}:
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:URL")} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.video.coverUrl} onChange={e => {
this.updateVideoField('coverUrl', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.video.coverUrl}>
<img src={this.state.video.coverUrl} alt={this.state.video.coverUrl} height={90} style={{marginBottom: '20px'}}/>
</a>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Video")}:
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
{
this.state.video !== null ? this.renderVideoContent() : null
}
</Col>
</Row>
</Card>
)
}
submitVideoEdit() {
let video = Setting.deepCopy(this.state.video);
VideoBackend.updateVideo(this.state.video.owner, this.state.videoName, video)
.then((res) => {
if (res) {
Setting.showMessage("success", `Successfully saved`);
this.setState({
videoName: this.state.video.name,
});
this.props.history.push(`/videos/${this.state.video.name}`);
} else {
Setting.showMessage("error", `failed to save: server side failure`);
this.updateVideoField('name', this.state.videoName);
}
})
.catch(error => {
Setting.showMessage("error", `failed to save: ${error}`);
});
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.state.video !== null ? this.renderVideo() : null
}
</Col>
<Col span={1}>
</Col>
</Row>
<Row style={{margin: 10}}>
<Col span={2}>
</Col>
<Col span={18}>
<Button type="primary" size="large" onClick={this.submitVideoEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col>
</Row>
</div>
);
}
}
export default VideoEditPage;

172
web/src/VideoListPage.js Normal file
View File

@ -0,0 +1,172 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from 'antd';
import moment from "moment";
import * as Setting from "./Setting";
import * as VideoBackend from "./backend/VideoBackend";
import i18next from "i18next";
class VideoListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
videos: null,
};
}
componentWillMount() {
this.getVideos();
}
getVideos() {
VideoBackend.getVideos(this.props.account.name)
.then((res) => {
this.setState({
videos: res,
});
});
}
newVideo() {
return {
owner: this.props.account.name,
name: `video_${this.state.videos.length}`,
createdTime: moment().format(),
displayName: `Video ${this.state.videos.length}`,
videoId: "",
coverUrl: "",
playAuth: "",
}
}
addVideo() {
const newVideo = this.newVideo();
VideoBackend.addVideo(newVideo)
.then((res) => {
Setting.showMessage("success", `Video added successfully`);
this.setState({
videos: Setting.prependRow(this.state.videos, newVideo),
});
}
)
.catch(error => {
Setting.showMessage("error", `Video failed to add: ${error}`);
});
}
deleteVideo(i) {
VideoBackend.deleteVideo(this.state.videos[i])
.then((res) => {
Setting.showMessage("success", `Video deleted successfully`);
this.setState({
videos: Setting.deleteRow(this.state.videos, i),
});
}
)
.catch(error => {
Setting.showMessage("error", `Video failed to delete: ${error}`);
});
}
renderTable(videos) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: 'name',
key: 'name',
width: '140px',
sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => {
return (
<Link to={`/videos/${text}`}>
{text}
</Link>
)
}
},
{
title: i18next.t("general:Display name"),
dataIndex: 'displayName',
key: 'displayName',
width: '200px',
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
},
{
title: i18next.t("video:Video ID"),
dataIndex: 'videoId',
key: 'videoId',
width: '250px',
sorter: (a, b) => a.videoId.localeCompare(b.videoId),
},
{
title: i18next.t("video:Cover"),
dataIndex: 'coverUrl',
key: 'coverUrl',
width: '200px',
render: (text, record, index) => {
return (
<a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={150} />
</a>
)
}
},
{
title: i18next.t("general:Action"),
dataIndex: 'action',
key: 'action',
width: '80px',
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/videos/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete video: ${record.name} ?`}
onConfirm={() => this.deleteVideo(index)}
okText="OK"
cancelText="Cancel"
>
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
)
}
},
];
return (
<div>
<Table columns={columns} dataSource={videos} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{i18next.t("general:Videos")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addVideo.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={videos === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.videos)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default VideoListPage;

View File

@ -0,0 +1,56 @@
import * as Setting from "../Setting";
export function getGlobalVideos() {
return fetch(`${Setting.ServerUrl}/api/get-global-videos`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function getVideos(owner) {
return fetch(`${Setting.ServerUrl}/api/get-videos?owner=${owner}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function getVideo(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-video?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function getVideoGraph(owner, name, clusterNumber, distanceLimit) {
return fetch(`${Setting.ServerUrl}/api/get-video-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function updateVideo(owner, name, video) {
let newVideo = Setting.deepCopy(video);
return fetch(`${Setting.ServerUrl}/api/update-video?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newVideo),
}).then(res => res.json());
}
export function addVideo(video) {
let newVideo = Setting.deepCopy(video);
return fetch(`${Setting.ServerUrl}/api/add-video`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newVideo),
}).then(res => res.json());
}
export function deleteVideo(video) {
let newVideo = Setting.deepCopy(video);
return fetch(`${Setting.ServerUrl}/api/delete-video`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newVideo),
}).then(res => res.json());
}

View File

@ -22,6 +22,7 @@
"Save": "Save", "Save": "Save",
"URL": "URL", "URL": "URL",
"Vectorsets": "Vectorsets", "Vectorsets": "Vectorsets",
"Videos": "Videos",
"Wordsets": "Wordsets" "Wordsets": "Wordsets"
}, },
"vectorset": { "vectorset": {
@ -32,6 +33,12 @@
"File name": "File name", "File name": "File name",
"File size": "File size" "File size": "File size"
}, },
"video": {
"Cover": "Cover",
"Edit Video": "Edit Video",
"Video": "Video",
"Video ID": "Video ID"
},
"wordset": { "wordset": {
"All words": "All words", "All words": "All words",
"Distance limit": "Distance limit", "Distance limit": "Distance limit",

View File

@ -21,8 +21,9 @@
"Result": "结果", "Result": "结果",
"Save": "保存", "Save": "保存",
"URL": "链接", "URL": "链接",
"Vectorsets": "向量集", "Vectorsets": "我的向量集",
"Wordsets": "词汇集" "Videos": "我的视频",
"Wordsets": "我的词汇集"
}, },
"vectorset": { "vectorset": {
"Count": "个数", "Count": "个数",
@ -32,6 +33,12 @@
"File name": "文件名", "File name": "文件名",
"File size": "文件大小" "File size": "文件大小"
}, },
"video": {
"Cover": "封面图片",
"Edit Video": "编辑视频",
"Video": "视频",
"Video ID": "视频ID"
},
"wordset": { "wordset": {
"All words": "所有词汇", "All words": "所有词汇",
"Distance limit": "距离上限", "Distance limit": "距离上限",

View File

@ -2235,6 +2235,13 @@ ajv@^8.0.1:
require-from-string "^2.0.2" require-from-string "^2.0.2"
uri-js "^4.2.2" uri-js "^4.2.2"
aliplayer-react@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aliplayer-react/-/aliplayer-react-0.7.0.tgz#5560e992e5d06f43a10065c7316e2f60552af82d"
integrity sha512-KjjRrtRy48DOUiR4ZdvfiLgD+u1yaT6ywZsGrynxePuv4dWsGt8u5Zsd3V3dxIgKeeep3RoxV7zofhcER40JKQ==
dependencies:
fetch-js-from-cdn "^0.2.0"
alphanum-sort@^1.0.0: alphanum-sort@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@ -5159,6 +5166,11 @@ fb-watchman@^2.0.0:
dependencies: dependencies:
bser "2.1.1" bser "2.1.1"
fetch-js-from-cdn@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/fetch-js-from-cdn/-/fetch-js-from-cdn-0.2.0.tgz#0664242d20cae6f69be591bcf2b8dc7c606b7d14"
integrity sha512-u+adV8XpElTNrqPF6dcsMkmbP4FFW7W/8XPhU2HgfyXaiGA4NpFp3KXdF1/XMj++vjN65bA17RCxScK7Iw17Yw==
fflate@^0.3.8: fflate@^0.3.8:
version "0.3.11" version "0.3.11"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.3.11.tgz#2c440d7180fdeb819e64898d8858af327b042a5d" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.3.11.tgz#2c440d7180fdeb819e64898d8858af327b042a5d"