| @@ -0,0 +1,61 @@ | |||
| package controllers | |||
| import ( | |||
| "encoding/json" | |||
| "github.com/casbin/casbase/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() | |||
| } | |||
| @@ -3,6 +3,7 @@ module github.com/casbin/casbase | |||
| 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 | |||
| @@ -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= | |||
| @@ -94,4 +94,9 @@ func (a *Adapter) createTable() { | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| err = a.engine.Sync2(new(Video)) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| @@ -0,0 +1,98 @@ | |||
| package object | |||
| import ( | |||
| "fmt" | |||
| "github.com/casbin/casbase/util" | |||
| "github.com/casbin/casbase/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) | |||
| } | |||
| @@ -37,4 +37,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") | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| package video | |||
| var regionId = "" | |||
| var accessKeyId = "" | |||
| var accessKeySecret = "" | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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", | |||
| @@ -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 { | |||
| </Link> | |||
| </Menu.Item> | |||
| ); | |||
| res.push( | |||
| <Menu.Item key="/videos"> | |||
| <Link to="/videos"> | |||
| {i18next.t("general:Videos")} | |||
| </Link> | |||
| </Menu.Item> | |||
| ); | |||
| 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="/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="/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> | |||
| </div> | |||
| ) | |||
| @@ -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; | |||
| @@ -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")} | |||
| <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; | |||
| @@ -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")} | |||
| <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; | |||
| @@ -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()); | |||
| } | |||
| @@ -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", | |||
| @@ -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": "距离上限", | |||
| @@ -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" | |||