From c38af47c597b5ac1851a2e73d4c344c01cc1ace7 Mon Sep 17 00:00:00 2001 From: Haifeng Luo Date: Sat, 7 May 2022 15:01:50 +0800 Subject: [PATCH] Add video pages. --- controllers/video.go | 61 ++++++++++ go.mod | 1 + go.sum | 10 +- object/adapter.go | 5 + object/video.go | 98 +++++++++++++++ routers/router.go | 7 ++ video/conf.go | 5 + video/init.go | 18 +++ video/vod_api.go | 17 +++ web/package.json | 1 + web/src/App.js | 13 ++ web/src/Video.js | 92 ++++++++++++++ web/src/VideoEditPage.js | 207 ++++++++++++++++++++++++++++++++ web/src/VideoListPage.js | 172 ++++++++++++++++++++++++++ web/src/backend/VideoBackend.js | 56 +++++++++ web/src/locales/en/data.json | 7 ++ web/src/locales/zh/data.json | 11 +- web/yarn.lock | 12 ++ 18 files changed, 790 insertions(+), 3 deletions(-) create mode 100644 controllers/video.go create mode 100644 object/video.go create mode 100644 video/conf.go create mode 100644 video/init.go create mode 100644 video/vod_api.go create mode 100644 web/src/Video.js create mode 100644 web/src/VideoEditPage.js create mode 100644 web/src/VideoListPage.js create mode 100644 web/src/backend/VideoBackend.js diff --git a/controllers/video.go b/controllers/video.go new file mode 100644 index 0000000..9d4b2cd --- /dev/null +++ b/controllers/video.go @@ -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() +} diff --git a/go.mod b/go.mod index 7443e57..7abfe81 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/openbrainorg/openbrain go 1.16 require ( + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1585 github.com/astaxie/beego v1.12.3 github.com/casdoor/casdoor-go-sdk v0.3.3 github.com/danaugrs/go-tsne/tsne v0.0.0-20220306155740-2250969e057f diff --git a/go.sum b/go.sum index a7963ce..6d36b7a 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 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.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 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.1/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/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.7/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/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/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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 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-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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/object/adapter.go b/object/adapter.go index 3f457b8..3d9a7e2 100644 --- a/object/adapter.go +++ b/object/adapter.go @@ -94,4 +94,9 @@ func (a *Adapter) createTable() { if err != nil { panic(err) } + + err = a.engine.Sync2(new(Video)) + if err != nil { + panic(err) + } } diff --git a/object/video.go b/object/video.go new file mode 100644 index 0000000..65442f7 --- /dev/null +++ b/object/video.go @@ -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) +} diff --git a/routers/router.go b/routers/router.go index fecdfad..0394bba 100644 --- a/routers/router.go +++ b/routers/router.go @@ -38,4 +38,11 @@ func initAPI() { beego.Router("/api/update-vectorset", &controllers.ApiController{}, "POST:UpdateVectorset") beego.Router("/api/add-vectorset", &controllers.ApiController{}, "POST:AddVectorset") 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") } diff --git a/video/conf.go b/video/conf.go new file mode 100644 index 0000000..5eb7ee1 --- /dev/null +++ b/video/conf.go @@ -0,0 +1,5 @@ +package video + +var regionId = "" +var accessKeyId = "" +var accessKeySecret = "" diff --git a/video/init.go b/video/init.go new file mode 100644 index 0000000..af8d8bb --- /dev/null +++ b/video/init.go @@ -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 +} diff --git a/video/vod_api.go b/video/vod_api.go new file mode 100644 index 0000000..508cec5 --- /dev/null +++ b/video/vod_api.go @@ -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 +} diff --git a/web/package.json b/web/package.json index 5f195ea..8272518 100644 --- a/web/package.json +++ b/web/package.json @@ -5,6 +5,7 @@ "dependencies": { "@ant-design/icons": "4.6.2", "@craco/craco": "6.1.1", + "aliplayer-react": "^0.7.0", "antd": "4.15.5", "casdoor-js-sdk": "^0.2.7", "copy-to-clipboard": "^3.3.1", diff --git a/web/src/App.js b/web/src/App.js index 1b30f40..74c904a 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -13,6 +13,8 @@ import WordsetEditPage from "./WordsetEditPage"; import WordsetGraphPage from "./WordsetGraphPage"; import VectorsetListPage from "./VectorsetListPage"; import VectorsetEditPage from "./VectorsetEditPage"; +import VideoListPage from "./VideoListPage"; +import VideoEditPage from "./VideoEditPage"; import SigninPage from "./SigninPage"; import i18next from "i18next"; import SelectLanguageBox from "./SelectLanguageBox"; @@ -58,6 +60,8 @@ class App extends Component { this.setState({ selectedMenuKey: '/wordsets' }); } else if (uri.includes('/vectorsets')) { this.setState({ selectedMenuKey: '/vectorsets' }); + } else if (uri.includes('/videos')) { + this.setState({ selectedMenuKey: '/videos' }); } else { this.setState({selectedMenuKey: 'null'}); } @@ -231,6 +235,13 @@ class App extends Component { ); + res.push( + + + {i18next.t("general:Videos")} + + + ); return res; } @@ -286,6 +297,8 @@ class App extends Component { this.renderSigninIfNotSignedIn()}/> this.renderSigninIfNotSignedIn()}/> this.renderSigninIfNotSignedIn()}/> + this.renderSigninIfNotSignedIn()}/> + this.renderSigninIfNotSignedIn()}/> ) diff --git a/web/src/Video.js b/web/src/Video.js new file mode 100644 index 0000000..25927ac --- /dev/null +++ b/web/src/Video.js @@ -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 ( +
+ { + this.initPlayer(player); + }} + /> +
+ ) + } +} + +export default Video; diff --git a/web/src/VideoEditPage.js b/web/src/VideoEditPage.js new file mode 100644 index 0000000..50626fe --- /dev/null +++ b/web/src/VideoEditPage.js @@ -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 ( +
+ {/*{*/} + {/* JSON.stringify(this.state.video)*/} + {/*}*/} +
+ { + this.state.video.name + } +
+
+ ) + } + + renderVideo() { + return ( + + {i18next.t("video:Edit Video")}     + + + } style={{marginLeft: '5px'}} type="inner"> + + + {i18next.t("general:Name")}: + + + { + this.updateVideoField('name', e.target.value); + }} /> + + + + + {i18next.t("general:Display name")}: + + + { + this.updateVideoField('displayName', e.target.value); + }} /> + + + + + {i18next.t("video:Video ID")}: + + + { + this.updateVideoField('videoId', e.target.value); + }} /> + + + + + {i18next.t("video:Cover")}: + + + + + {i18next.t("general:URL")} : + + + } value={this.state.video.coverUrl} onChange={e => { + this.updateVideoField('coverUrl', e.target.value); + }} /> + + + + + {i18next.t("general:Preview")}: + + + + {this.state.video.coverUrl} + + + + + + + + {i18next.t("video:Video")}: + + + { + this.state.video !== null ? this.renderVideoContent() : null + } + + + + ) + } + + 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 ( +
+ + + + + { + this.state.video !== null ? this.renderVideo() : null + } + + + + + + + + + + + +
+ ); + } +} + +export default VideoEditPage; diff --git a/web/src/VideoListPage.js b/web/src/VideoListPage.js new file mode 100644 index 0000000..5a39fdb --- /dev/null +++ b/web/src/VideoListPage.js @@ -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 ( + + {text} + + ) + } + }, + { + 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 ( + + {text} + + ) + } + }, + { + title: i18next.t("general:Action"), + dataIndex: 'action', + key: 'action', + width: '80px', + render: (text, record, index) => { + return ( +
+ + this.deleteVideo(index)} + okText="OK" + cancelText="Cancel" + > + + +
+ ) + } + }, + ]; + + return ( +
+ ( +
+ {i18next.t("general:Videos")}     + +
+ )} + loading={videos === null} + /> + + ); + } + + render() { + return ( +
+ +
+ + + { + this.renderTable(this.state.videos) + } + + + + + + ); + } +} + +export default VideoListPage; diff --git a/web/src/backend/VideoBackend.js b/web/src/backend/VideoBackend.js new file mode 100644 index 0000000..7a9bd9c --- /dev/null +++ b/web/src/backend/VideoBackend.js @@ -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()); +} diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index 71ac758..61e2e89 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -22,6 +22,7 @@ "Save": "Save", "URL": "URL", "Vectorsets": "Vectorsets", + "Videos": "Videos", "Wordsets": "Wordsets" }, "vectorset": { @@ -32,6 +33,12 @@ "File name": "File name", "File size": "File size" }, + "video": { + "Cover": "Cover", + "Edit Video": "Edit Video", + "Video": "Video", + "Video ID": "Video ID" + }, "wordset": { "All words": "All words", "Distance limit": "Distance limit", diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index b94cb60..f2f8dcf 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -21,8 +21,9 @@ "Result": "结果", "Save": "保存", "URL": "链接", - "Vectorsets": "向量集", - "Wordsets": "词汇集" + "Vectorsets": "我的向量集", + "Videos": "我的视频", + "Wordsets": "我的词汇集" }, "vectorset": { "Count": "个数", @@ -32,6 +33,12 @@ "File name": "文件名", "File size": "文件大小" }, + "video": { + "Cover": "封面图片", + "Edit Video": "编辑视频", + "Video": "视频", + "Video ID": "视频ID" + }, "wordset": { "All words": "所有词汇", "Distance limit": "距离上限", diff --git a/web/yarn.lock b/web/yarn.lock index 81dfdb5..fa71414 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2235,6 +2235,13 @@ ajv@^8.0.1: require-from-string "^2.0.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: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -5159,6 +5166,11 @@ fb-watchman@^2.0.0: dependencies: 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: version "0.3.11" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.3.11.tgz#2c440d7180fdeb819e64898d8858af327b042a5d"