@@ -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" | |||