Reviewed-on: https://openi.pcl.ac.cn/OpenI/aiforge/pulls/3380pull/3396/head
@@ -1862,6 +1862,7 @@ func CreateCloudbrain(cloudbrain *Cloudbrain) (err error) { | |||
session.Commit() | |||
go IncreaseDatasetUseCount(cloudbrain.Uuid) | |||
go OperateRepoAITaskNum(cloudbrain.RepoID, 1) | |||
return nil | |||
} | |||
@@ -2017,9 +2018,29 @@ func DeleteJob(job *Cloudbrain) error { | |||
func deleteJob(e Engine, job *Cloudbrain) error { | |||
_, err := e.ID(job.ID).Delete(job) | |||
if err == nil { | |||
go updateAITaskNumWhenDeleteJob(job) | |||
} | |||
return err | |||
} | |||
func updateAITaskNumWhenDeleteJob(job *Cloudbrain) { | |||
repoId := job.RepoID | |||
if repoId == 0 { | |||
t := &Cloudbrain{} | |||
_, tempErr := x.ID(job.ID).Unscoped().Get(t) | |||
if tempErr != nil { | |||
log.Error("updateAITaskNumWhenDeleteJob error.%v", tempErr) | |||
return | |||
} | |||
repoId = t.RepoID | |||
} | |||
if repoId > 0 { | |||
go OperateRepoAITaskNum(repoId, -1) | |||
} | |||
} | |||
func GetCloudbrainByName(jobName string) (*Cloudbrain, error) { | |||
cb := &Cloudbrain{JobName: jobName} | |||
return getRepoCloudBrain(cb) | |||
@@ -2222,7 +2243,6 @@ func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) { | |||
} | |||
go IncreaseDatasetUseCount(new.Uuid) | |||
return nil | |||
} | |||
func CloudbrainAll(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { | |||
@@ -10,6 +10,26 @@ import ( | |||
"xorm.io/xorm" | |||
) | |||
type AvailablePageSize int | |||
const ( | |||
PageSize15 AvailablePageSize = 15 | |||
PageSize30 AvailablePageSize = 30 | |||
PageSize50 AvailablePageSize = 50 | |||
) | |||
func (s AvailablePageSize) IsLegal() bool { | |||
switch s { | |||
case PageSize30, PageSize50, PageSize15: | |||
return true | |||
} | |||
return false | |||
} | |||
func (s AvailablePageSize) Int() int { | |||
return int(s) | |||
} | |||
// ListOptions options to paginate results | |||
type ListOptions struct { | |||
PageSize int | |||
@@ -231,10 +231,43 @@ type Repository struct { | |||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
Hot int64 `xorm:"-"` | |||
Active int64 `xorm:"-"` | |||
Alias string `xorm:"INDEX"` | |||
LowerAlias string `xorm:"INDEX"` | |||
Hot int64 `xorm:"-"` | |||
Active int64 `xorm:"-"` | |||
Alias string `xorm:"INDEX"` | |||
LowerAlias string `xorm:"INDEX"` | |||
AiTaskCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
ModelCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
DatasetCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
LastMonthVisits int64 `xorm:"NOT NULL DEFAULT 0"` | |||
LastFourMonthCommits int64 `xorm:"NOT NULL DEFAULT 0"` | |||
} | |||
// Repository4Card format for front display | |||
type Repository4Card struct { | |||
ID int64 | |||
OwnerID int64 | |||
OwnerName string | |||
LowerName string | |||
Name string | |||
Alias string | |||
NumWatches int | |||
NumStars int | |||
NumForks int | |||
Description string | |||
Topics []string | |||
AiTaskCnt int64 | |||
ModelCnt int64 | |||
DatasetCnt int64 | |||
CreatedUnix timeutil.TimeStamp | |||
UpdatedUnix timeutil.TimeStamp | |||
PrimaryLanguage *LanguageStat | |||
RelAvatarLink string | |||
Contributors []*ContributorInfo | |||
IsPrivate bool | |||
IsFork bool | |||
IsMirror bool | |||
IsOwnerPrivate bool | |||
IsArchived bool | |||
} | |||
type RepositoryShow struct { | |||
@@ -243,6 +276,47 @@ type RepositoryShow struct { | |||
Alias string | |||
} | |||
func (repo *Repository) ToCardFormat() *Repository4Card { | |||
link := repo.RelAvatarLink() | |||
var isOwnerPrivate bool | |||
if repo.Owner != nil && repo.Owner.Visibility.IsPrivate() { | |||
isOwnerPrivate = true | |||
} | |||
result := &Repository4Card{ | |||
ID: repo.ID, | |||
OwnerID: repo.OwnerID, | |||
OwnerName: repo.OwnerName, | |||
LowerName: repo.LowerName, | |||
Name: repo.Name, | |||
NumWatches: repo.NumWatches, | |||
NumStars: repo.NumStars, | |||
NumForks: repo.NumForks, | |||
Description: repo.Description, | |||
Topics: repo.Topics, | |||
AiTaskCnt: repo.AiTaskCnt, | |||
ModelCnt: repo.ModelCnt, | |||
DatasetCnt: repo.DatasetCnt, | |||
CreatedUnix: repo.CreatedUnix, | |||
UpdatedUnix: repo.UpdatedUnix, | |||
PrimaryLanguage: repo.PrimaryLanguage, | |||
RelAvatarLink: link, | |||
Alias: repo.Alias, | |||
IsPrivate: repo.IsPrivate, | |||
IsFork: repo.IsFork, | |||
IsMirror: repo.IsMirror, | |||
IsOwnerPrivate: isOwnerPrivate, | |||
IsArchived: repo.IsArchived, | |||
} | |||
return result | |||
} | |||
type ContributorInfo struct { | |||
RelAvatarLink string | |||
UserName string | |||
Email string | |||
CommitCnt int | |||
} | |||
// SanitizedOriginalURL returns a sanitized OriginalURL | |||
func (repo *Repository) SanitizedOriginalURL() string { | |||
if repo.OriginalURL == "" { | |||
@@ -2379,6 +2453,75 @@ func CheckRepoStats(ctx context.Context) error { | |||
} | |||
} | |||
// ***** END: Repository.NumForks ***** | |||
// ***** START: Repository.DatasetCnt ***** | |||
desc = "repository count 'dataset_cnt'" | |||
results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.dataset_cnt!=(select count(1) from attachment inner join dataset on attachment.dataset_id = dataset.id where dataset.repo_id = repository.id)") | |||
if err != nil { | |||
log.Error("Select %s: %v", desc, err) | |||
} else { | |||
for _, result := range results { | |||
id := com.StrTo(result["id"]).MustInt64() | |||
select { | |||
case <-ctx.Done(): | |||
log.Warn("CheckRepoStats: Cancelled") | |||
return ErrCancelledf("during %s for repo ID %d", desc, id) | |||
default: | |||
} | |||
log.Trace("Updating %s: %d", desc, id) | |||
err = ResetRepoDatasetNum(id) | |||
if err != nil { | |||
log.Error("Update %s[%d]: %v", desc, id, err) | |||
} | |||
} | |||
} | |||
// ***** END: Repository.DatasetCnt ***** | |||
// ***** START: Repository.ModelCnt ***** | |||
desc = "repository count 'model_cnt'" | |||
results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.model_cnt!=(select count(1) from ai_model_manage where repository.id = ai_model_manage.repo_id and ai_model_manage.size > 0 )") | |||
if err != nil { | |||
log.Error("Select %s: %v", desc, err) | |||
} else { | |||
for _, result := range results { | |||
id := com.StrTo(result["id"]).MustInt64() | |||
select { | |||
case <-ctx.Done(): | |||
log.Warn("CheckRepoStats: Cancelled") | |||
return ErrCancelledf("during %s for repo ID %d", desc, id) | |||
default: | |||
} | |||
log.Trace("Updating %s: %d", desc, id) | |||
err = ResetRepoModelNum(id) | |||
if err != nil { | |||
log.Error("Update %s[%d]: %v", desc, id, err) | |||
} | |||
} | |||
} | |||
// ***** END: Repository.ModelCnt ***** | |||
// ***** START: Repository.AiTaskCnt ***** | |||
desc = "repository count 'ai_task_cnt'" | |||
results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.ai_task_cnt!=(select count(1) from cloudbrain where repository.id = cloudbrain.repo_id and (cloudbrain.deleted_at is null or cloudbrain.deleted_at = '0001-01-01 00:00:00') )") | |||
if err != nil { | |||
log.Error("Select %s: %v", desc, err) | |||
} else { | |||
for _, result := range results { | |||
id := com.StrTo(result["id"]).MustInt64() | |||
select { | |||
case <-ctx.Done(): | |||
log.Warn("CheckRepoStats: Cancelled") | |||
return ErrCancelledf("during %s for repo ID %d", desc, id) | |||
default: | |||
} | |||
log.Trace("Updating %s: %d", desc, id) | |||
err = ResetRepoAITaskNum(id) | |||
if err != nil { | |||
log.Error("Update %s[%d]: %v", desc, id, err) | |||
} | |||
} | |||
} | |||
// ***** END: Repository.AiTaskCnt ***** | |||
return nil | |||
} | |||
@@ -2775,3 +2918,85 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi | |||
} | |||
return &RepoFile{CommitId: commitId, Content: d}, nil | |||
} | |||
func ResetRepoAITaskNum(repoId int64) error { | |||
n, err := x.Where("repo_id = ? ", repoId).Count(&Cloudbrain{}) | |||
if err != nil { | |||
return err | |||
} | |||
r := Repository{ | |||
AiTaskCnt: n, | |||
} | |||
_, err = x.Cols("ai_task_cnt").Where("id = ?", repoId).Update(&r) | |||
return err | |||
} | |||
func ResetRepoDatasetNum(repoId int64) error { | |||
n, err := x.Table("attachment").Join("inner", "dataset", "attachment.dataset_id = dataset.id").Where("dataset.repo_id = ?", repoId).Count() | |||
if err != nil { | |||
return err | |||
} | |||
r := Repository{ | |||
DatasetCnt: n, | |||
} | |||
_, err = x.Cols("dataset_cnt").Where("id = ?", repoId).Update(&r) | |||
return err | |||
} | |||
func ResetRepoModelNum(repoId int64) error { | |||
_, err := x.Exec("update repository set model_cnt = (select count(1) from ai_model_manage where ai_model_manage.repo_id = ? and size > 0) where id = ?", repoId, repoId) | |||
return err | |||
} | |||
func operateRepoCol(repoId int64, colName string, amount int64, engines ...*xorm.Engine) error { | |||
var err error | |||
if amount == 0 { | |||
return nil | |||
} | |||
var ee *xorm.Engine | |||
if len(engines) == 0 { | |||
ee = x | |||
} else { | |||
ee = engines[0] | |||
} | |||
if amount > 0 { | |||
_, err = ee.Exec(fmt.Sprintf("update repository set %s = %s + ? where id = ?", colName, colName), amount, repoId) | |||
} else { | |||
_, err = ee.Exec(fmt.Sprintf("update repository set %s = %s - ? where id = ?", colName, colName), -1*amount, repoId) | |||
} | |||
return err | |||
} | |||
func OperateRepoDatasetNum(repoId int64, amount int64, engines ...*xorm.Engine) error { | |||
return operateRepoCol(repoId, "dataset_cnt", amount, engines...) | |||
} | |||
func OperateRepoModelNum(repoId int64, amount int64, engines ...*xorm.Engine) error { | |||
return operateRepoCol(repoId, "model_cnt", amount, engines...) | |||
} | |||
func OperateRepoAITaskNum(repoId int64, amount int64, engines ...*xorm.Engine) error { | |||
return operateRepoCol(repoId, "ai_task_cnt", amount, engines...) | |||
} | |||
func UpdateRepositoryLastFourMonthCommits(repoID int64, amount int64) error { | |||
_, err := x.Exec("update repository set last_four_month_commits = ? where id = ?", amount, repoID) | |||
return err | |||
} | |||
func UpdateRepositoryLastMonthVisits(repoID int64, amount int64) error { | |||
_, err := x.Exec("update repository set last_month_visits = ? where id = ?", amount, repoID) | |||
return err | |||
} | |||
func SyncStatDataToRepo(repo *Repository) { | |||
//Save the visit number of repository in the last month | |||
if lv, err := SumLastMonthNumVisits(repo.ID); err == nil { | |||
UpdateRepositoryLastMonthVisits(repo.ID, lv) | |||
} | |||
//Save the commits number of repository in the last four month | |||
if lc, err := SumLastFourMonthNumCommits(repo.ID); err == nil { | |||
UpdateRepositoryLastFourMonthCommits(repo.ID, lc) | |||
} | |||
} |
@@ -201,29 +201,41 @@ func (s SearchOrderBy) String() string { | |||
return string(s) | |||
} | |||
type FindReposResponse struct { | |||
Repos []*Repository4Card | |||
Page int | |||
PageSize int | |||
Total int64 | |||
} | |||
// Strings for sorting result | |||
const ( | |||
SearchOrderByAlphabetically SearchOrderBy = "name ASC" | |||
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" | |||
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" | |||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" | |||
SearchOrderByOldest SearchOrderBy = "created_unix ASC" | |||
SearchOrderByNewest SearchOrderBy = "created_unix DESC" | |||
SearchOrderBySize SearchOrderBy = "size ASC" | |||
SearchOrderBySizeReverse SearchOrderBy = "size DESC" | |||
SearchOrderByID SearchOrderBy = "id ASC" | |||
SearchOrderByIDReverse SearchOrderBy = "id DESC" | |||
SearchOrderByStars SearchOrderBy = "num_stars ASC" | |||
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" | |||
SearchOrderByForks SearchOrderBy = "num_forks ASC" | |||
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | |||
SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" | |||
SearchOrderByUseCount SearchOrderBy = "use_count ASC" | |||
SearchOrderByUseCountReverse SearchOrderBy = "use_count DESC" | |||
SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" | |||
SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" | |||
SearchOrderByWatches SearchOrderBy = "num_watches DESC" | |||
SearchOrderByDefault SearchOrderBy = "recommend desc,num_stars DESC,updated_unix DESC" | |||
SearchOrderByAlphabetically SearchOrderBy = "name ASC" | |||
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" | |||
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" | |||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" | |||
SearchOrderByOldest SearchOrderBy = "created_unix ASC" | |||
SearchOrderByNewest SearchOrderBy = "created_unix DESC" | |||
SearchOrderBySize SearchOrderBy = "size ASC" | |||
SearchOrderBySizeReverse SearchOrderBy = "size DESC" | |||
SearchOrderByID SearchOrderBy = "id ASC" | |||
SearchOrderByIDReverse SearchOrderBy = "id DESC" | |||
SearchOrderByStars SearchOrderBy = "num_stars ASC" | |||
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" | |||
SearchOrderByForks SearchOrderBy = "num_forks ASC" | |||
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | |||
SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" | |||
SearchOrderByUseCount SearchOrderBy = "use_count ASC" | |||
SearchOrderByUseCountReverse SearchOrderBy = "use_count DESC" | |||
SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" | |||
SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" | |||
SearchOrderByWatches SearchOrderBy = "num_watches DESC" | |||
SearchOrderByDefault SearchOrderBy = "recommend desc,num_stars DESC,updated_unix DESC" | |||
SearchOrderByAiTaskCntReverse SearchOrderBy = "ai_task_cnt desc" | |||
SearchOrderByModelCntReverse SearchOrderBy = "model_cnt desc" | |||
SearchOrderByDatasetCntReverse SearchOrderBy = "dataset_cnt desc" | |||
SearchOrderByLastMonthVisitsReverse SearchOrderBy = "last_month_visits desc" | |||
SearchOrderByLastFourMonthCommitsReverse SearchOrderBy = "last_four_month_commits desc" | |||
) | |||
// SearchRepositoryCondition creates a query condition according search repository options | |||
@@ -200,3 +200,23 @@ func UpdateRepoStatVisits(repoStat *RepoStatistic) error { | |||
_, err := xStatistic.Exec(sql, repoStat.NumVisits, repoStat.RepoID, repoStat.Date) | |||
return err | |||
} | |||
func SumRepoStatColumn(begin, end time.Time, repoId int64, columnName string) (int64, error) { | |||
res, err := xStatistic.Where("created_unix <= ? and created_unix >= ? and repo_id = ? ", end.Unix(), begin.Unix(), repoId).Sum(&RepoStatistic{}, columnName) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return int64(res), nil | |||
} | |||
func SumLastMonthNumVisits(repoId int64) (int64, error) { | |||
end := time.Now() | |||
begin := end.AddDate(0, 0, -30) | |||
return SumRepoStatColumn(begin, end, repoId, "num_visits") | |||
} | |||
func SumLastFourMonthNumCommits(repoId int64) (int64, error) { | |||
end := time.Now() | |||
begin := end.AddDate(0, 0, -120) | |||
return SumRepoStatColumn(begin, end, repoId, "num_commits_added") | |||
} |
@@ -4,6 +4,7 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"fmt" | |||
"xorm.io/builder" | |||
) | |||
type OfficialTag struct { | |||
@@ -166,3 +167,33 @@ func GetAllOfficialTags() ([]OfficialTag, error) { | |||
} | |||
return o, nil | |||
} | |||
type FindSelectedReposOpts struct { | |||
ListOptions | |||
OrgId int64 | |||
OnlyPublic bool | |||
} | |||
func GetSelectedRepos(opts FindSelectedReposOpts) ([]*Repository, error) { | |||
if opts.Page < 1 { | |||
opts.Page = 1 | |||
} | |||
var cond = builder.NewCond() | |||
cond = cond.And(builder.Eq{"official_tag.code": "selected"}) | |||
if opts.OrgId > 0 { | |||
cond = cond.And(builder.Eq{"official_tag_repos.org_id": opts.OrgId}) | |||
} | |||
if opts.OnlyPublic { | |||
cond = cond.And(builder.Eq{"repository.is_private": false}) | |||
} | |||
t := make([]*Repository, 0) | |||
err := x.Join("inner", "official_tag_repos", "repository.id = official_tag_repos.repo_id"). | |||
Join("inner", "official_tag", "official_tag.id = official_tag_repos.tag_id"). | |||
Where(cond).OrderBy("repository.updated_unix desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&t) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return t, nil | |||
} |
@@ -9,6 +9,7 @@ import ( | |||
"regexp" | |||
"strings" | |||
"unicode/utf8" | |||
"xorm.io/xorm" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
@@ -337,3 +338,16 @@ func GetOrgTopics(orgId int64) ([]Topic, error) { | |||
return result, nil | |||
} | |||
func UpdateRepoTopics(repoID int64, topicNames []string, sess ...*xorm.Engine) error { | |||
e := x | |||
if len(sess) > 0 { | |||
e = sess[0] | |||
} | |||
if _, err := e.ID(repoID).Cols("topics").Update(&Repository{ | |||
Topics: topicNames, | |||
}); err != nil { | |||
return err | |||
} | |||
return nil | |||
} |
@@ -198,6 +198,40 @@ type SearchOrganizationsOptions struct { | |||
All bool | |||
} | |||
type User4Front struct { | |||
ID int64 | |||
LowerName string `xorm:"UNIQUE NOT NULL"` | |||
Name string `xorm:"UNIQUE NOT NULL"` | |||
FullName string | |||
Email string `xorm:"NOT NULL"` | |||
Language string `xorm:"VARCHAR(5)"` | |||
Description string | |||
RelAvatarLink string | |||
NumMembers int | |||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
} | |||
func (u *User) ToFrontFormat() *User4Front { | |||
uf := &User4Front{ | |||
ID: u.ID, | |||
LowerName: u.LowerName, | |||
Name: u.Name, | |||
FullName: u.FullName, | |||
Email: u.Email, | |||
Language: u.Language, | |||
Description: u.Description, | |||
CreatedUnix: u.CreatedUnix, | |||
UpdatedUnix: u.UpdatedUnix, | |||
NumMembers: u.NumMembers, | |||
} | |||
if !u.KeepEmailPrivate { | |||
uf.Email = u.Email | |||
} | |||
uf.RelAvatarLink = u.RelAvatarLink() | |||
return uf | |||
} | |||
// GenerateRandomAvatar generates a random avatar for user. | |||
func (u *User) IsBindWechat() bool { | |||
return u.WechatOpenId != "" | |||
@@ -2443,3 +2443,9 @@ func GetContentFromPromote(url string) (string, error) { | |||
allLineStr := string(bytes) | |||
return allLineStr, nil | |||
} | |||
func QueryLast30DaysHighestIndexUsers(size int) ([]int64, error) { | |||
userIds := make([]int64, 0) | |||
err := xStatistic.Table("user_business_analysis_last30_day").Cols("id").OrderBy("user_index desc").Limit(size).Find(&userIds) | |||
return userIds, err | |||
} |
@@ -0,0 +1,9 @@ | |||
package redis_key | |||
import "fmt" | |||
const REPO_PREFIX = "repo" | |||
func RepoTopNContributors(repoId int64, N int) string { | |||
return KeyJoin(REPO_PREFIX, fmt.Sprint(repoId), fmt.Sprint(N), "top_n_contributor") | |||
} |
@@ -677,6 +677,10 @@ var ( | |||
CloudbrainStoppedTitle string | |||
CloudbrainStoppedRemark string | |||
//repo square config | |||
IncubationSourceOrgName string | |||
PaperRepoTopicName string | |||
//nginx proxy | |||
PROXYURL string | |||
RadarMap = struct { | |||
@@ -1585,6 +1589,10 @@ func NewContext() { | |||
CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果") | |||
CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。") | |||
sec = Cfg.Section("repo-square") | |||
IncubationSourceOrgName = sec.Key("INCUBATION_ORG_NAME").MustString("OpenI") | |||
PaperRepoTopicName = sec.Key("PAPER_REPO_TOPIC_NAME").MustString("openi-paper") | |||
sec = Cfg.Section("point") | |||
CloudBrainPaySwitch = sec.Key("CLOUDBRAIN_PAY_SWITCH").MustBool(false) | |||
CloudBrainPayDelay = sec.Key("CLOUDBRAIN_PAY_DELAY").MustDuration(30 * time.Minute) | |||
@@ -17,11 +17,12 @@ | |||
"core-js": "3.6.5", | |||
"css-loader": "3.5.3", | |||
"cssnano": "4.1.10", | |||
"dayjs": "1.10.7", | |||
"domino": "2.1.5", | |||
"dropzone": "5.7.2", | |||
"echarts": "3.8.5", | |||
"element-ui": "2.15.5", | |||
"esdk-obs-browserjs": "3.20.7", | |||
"esdk-obs-browserjs": "3.22.3", | |||
"esdk-obs-nodejs": "3.20.11", | |||
"fast-glob": "3.2.2", | |||
"file-loader": "6.0.0", | |||
@@ -622,20 +622,12 @@ function displayRepo(json){ | |||
for (var i = 0, iLen = repos.length; i < iLen; i++) { | |||
if (i >= 4) break; | |||
var repo = repos[i]; | |||
// <i class="ri-star-line"></i>${repo["NumStars"]}<i class="ri-git-branch-line am-ml-10"></i>${repo["NumForks"]}</span> <div class="ui tags nowrap am-mt-10"></div> | |||
html += `<div class="ui fluid card" style="border-radius:6px;"> | |||
<div class="content"> | |||
${repo["Avatar"] ? `<img class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img class="left floated mini ui image" avatar="${repo["OwnerName"]}">`} | |||
<div class="content" style="position:relative;"> | |||
${repo["Avatar"] ? `<img style="border-radius:100%;" class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img style="border-radius:100%;" class="left floated mini ui image" avatar="${repo["OwnerName"]}">`} | |||
<a class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;" href="/${repo["OwnerName"]}/${repo["Name"]}" title="${repo["Alias"]}">${repo["Alias"]}</a> | |||
<div class="description nowrap-2" style="rgba(136,136,136,1);;font-size:12px;" title="${repo["Description"]}">${repo["Description"]}</div> | |||
`; | |||
// if (repo["Topics"] != null) { | |||
// for(var j = 0; j < repo["Topics"].length; j++){ | |||
// var topic = repo["Topics"][j]; | |||
// var url = "/explore/repos?q=" + (topic) + "&topic=" | |||
// html += `<a class="ui small label topic" href=" ${url}">${topic}</a>`; | |||
// } | |||
// } | |||
<a href="/${repo["OwnerName"]}/${repo["Name"]}" style="height:100%;width:100%;position:absolute;left:0;top:0"></a>`; | |||
html += ` | |||
</div> | |||
</div>`; | |||
@@ -307,3 +307,37 @@ func RefreshHistorySpec(ctx *context.Context) { | |||
r["total"] = total | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
} | |||
func RefreshReposHistoryCnt(ctx *context.Context) { | |||
scope := ctx.Query("scope") | |||
list := ctx.Query("list") | |||
var scopeAll = false | |||
if scope == "all" { | |||
scopeAll = true | |||
} | |||
var ids = make([]int64, 0) | |||
if list != "" { | |||
strs := strings.Split(list, "|") | |||
for _, s := range strs { | |||
i, err := strconv.ParseInt(s, 10, 64) | |||
if err != nil { | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ids = append(ids, i) | |||
} | |||
} | |||
total, success, err := resource.RefreshHistorySpec(scopeAll, ids) | |||
if err != nil { | |||
log.Error("RefreshHistorySpec error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
r := make(map[string]interface{}, 0) | |||
r["success"] = success | |||
r["total"] = total | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
} |
@@ -177,13 +177,25 @@ func AddTopic(ctx *context.APIContext) { | |||
return | |||
} | |||
_, err = models.AddTopic(ctx.Repo.Repository.ID, topicName) | |||
topic, err := models.AddTopic(ctx.Repo.Repository.ID, topicName) | |||
if err != nil { | |||
log.Error("AddTopic failed: %v", err) | |||
ctx.InternalServerError(err) | |||
return | |||
} | |||
found := false | |||
topicNames := make([]string, len(topics)) | |||
for i, t := range topics { | |||
topicNames[i] = t.Name | |||
if strings.EqualFold(topic.Name, t.Name) { | |||
found = true | |||
break | |||
} | |||
} | |||
if !found && topic.Name != "" { | |||
topicNames = append(topicNames, topic.Name) | |||
} | |||
models.UpdateRepoTopics(ctx.Repo.Repository.ID, topicNames) | |||
ctx.Status(http.StatusNoContent) | |||
} | |||
@@ -7,6 +7,7 @@ package routers | |||
import ( | |||
"bytes" | |||
"code.gitea.io/gitea/routers/response" | |||
"encoding/json" | |||
"net/http" | |||
"strconv" | |||
@@ -43,6 +44,8 @@ const ( | |||
tplHomeTerm base.TplName = "terms" | |||
tplHomePrivacy base.TplName = "privacy" | |||
tplResoruceDesc base.TplName = "resource_desc" | |||
tplRepoSquare base.TplName = "explore/repos/square" | |||
tplRepoSearch base.TplName = "explore/repos/search" | |||
) | |||
// Home render home page | |||
@@ -296,6 +299,109 @@ func ExploreRepos(ctx *context.Context) { | |||
}) | |||
} | |||
func GetRepoSquarePage(ctx *context.Context) { | |||
ctx.Data["SquareBanners"] = repository.GetBanners() | |||
ctx.Data["SquareTopics"] = repository.GetTopics() | |||
ctx.Data["SquareRecommendRepos"] = repository.GetRecommendRepos() | |||
repos, _ := repository.GetPreferredRepos() | |||
ctx.Data["SquarePreferredRepos"] = repos | |||
ctx.HTML(200, tplRepoSquare) | |||
} | |||
func GetRepoSearchPage(ctx *context.Context) { | |||
ctx.Data["SquareTopics"] = repository.GetTopics() | |||
ctx.HTML(200, tplRepoSearch) | |||
} | |||
func RepoSquare(ctx *context.Context) { | |||
var result []*models.Repository4Card | |||
var err error | |||
switch ctx.Query("type") { | |||
case "preferred": | |||
result, err = repository.GetPreferredRepos() | |||
case "incubation": | |||
result, err = repository.GetIncubationRepos() | |||
case "hot-paper": | |||
result, err = repository.GetHotPaperRepos() | |||
default: | |||
result, err = repository.GetPreferredRepos() | |||
} | |||
if err != nil { | |||
ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||
return | |||
} | |||
resultMap := make(map[string]interface{}, 0) | |||
resultMap["Repos"] = result | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
} | |||
func ActiveUser(ctx *context.Context) { | |||
var err error | |||
var currentUserId int64 | |||
if ctx.User != nil { | |||
currentUserId = ctx.User.ID | |||
} | |||
result, err := repository.GetActiveUser4Square(currentUserId) | |||
if err != nil { | |||
log.Error("ActiveUser err. %v", err) | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
return | |||
} | |||
resultMap := make(map[string]interface{}, 0) | |||
resultMap["Users"] = result | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
} | |||
func ActiveOrg(ctx *context.Context) { | |||
result, err := repository.GetActiveOrgs() | |||
if err != nil { | |||
log.Error("ActiveOrg err. %v", err) | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
return | |||
} | |||
resultMap := make(map[string]interface{}, 0) | |||
resultMap["Orgs"] = result | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
} | |||
func RepoFind(ctx *context.Context) { | |||
keyword := strings.Trim(ctx.Query("q"), " ") | |||
topic := strings.Trim(ctx.Query("topic"), " ") | |||
sort := strings.Trim(ctx.Query("sort"), " ") | |||
page := ctx.QueryInt("page") | |||
pageSize := ctx.QueryInt("pageSize") | |||
if pageSize == 0 { | |||
pageSize = 15 | |||
} | |||
if pageSize > 100 { | |||
ctx.JSON(http.StatusOK, response.ServerError("pageSize illegal")) | |||
return | |||
} | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
var ownerID int64 | |||
if ctx.User != nil && !ctx.User.IsAdmin { | |||
ownerID = ctx.User.ID | |||
} | |||
result, err := repository.FindRepos(repository.FindReposOptions{ | |||
ListOptions: models.ListOptions{Page: page, PageSize: pageSize}, | |||
Actor: ctx.User, | |||
Sort: sort, | |||
Keyword: keyword, | |||
Topic: topic, | |||
Private: ctx.User != nil, | |||
OwnerID: ownerID, | |||
}) | |||
if err != nil { | |||
log.Error("RepoFind error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(result)) | |||
} | |||
func ExploreDatasets(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
@@ -6,6 +6,7 @@ | |||
package private | |||
import ( | |||
"code.gitea.io/gitea/services/repository" | |||
"strings" | |||
"code.gitea.io/gitea/routers/admin" | |||
@@ -55,7 +56,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Post("/task/history_handle/duration", repo.HandleTaskWithNoDuration) | |||
m.Post("/task/history_handle/aicenter", repo.HandleTaskWithAiCenter) | |||
m.Post("/resources/specification/handle_historical_task", admin.RefreshHistorySpec) | |||
m.Post("/repos/cnt_stat/handle_historical_task", admin.RefreshHistorySpec) | |||
m.Post("/duration_statisctic/history_handle", repo.CloudbrainUpdateHistoryData) | |||
m.Post("/square/repo/stat/refresh", repository.RefreshRepoStatData) | |||
}, CheckInternalToken) | |||
} |
@@ -2,6 +2,7 @@ package repo | |||
import ( | |||
"archive/zip" | |||
"code.gitea.io/gitea/services/repository" | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
@@ -170,10 +171,17 @@ func updateStatus(id string, modelSize int64, status int, modelPath string, stat | |||
if len(statusDesc) > 400 { | |||
statusDesc = statusDesc[0:400] | |||
} | |||
m, _ := models.QueryModelById(id) | |||
err := models.ModifyModelStatus(id, modelSize, status, modelPath, statusDesc) | |||
if err != nil { | |||
log.Info("update status error." + err.Error()) | |||
} | |||
if m != nil { | |||
if modelSize > 0 && m.Size == 0 { | |||
go repository.ResetRepoModelNum(m.RepoId) | |||
} | |||
} | |||
} | |||
func SaveNewNameModel(ctx *context.Context) { | |||
@@ -308,13 +316,14 @@ func getSize(files []storage.FileInfo) int64 { | |||
func UpdateModelSize(modeluuid string) { | |||
model, err := models.QueryModelById(modeluuid) | |||
if err == nil { | |||
var size int64 | |||
if model.Type == models.TypeCloudBrainOne { | |||
if strings.HasPrefix(model.Path, setting.Attachment.Minio.Bucket+"/"+Model_prefix) { | |||
files, err := storage.GetAllObjectByBucketAndPrefixMinio(setting.Attachment.Minio.Bucket, model.Path[len(setting.Attachment.Minio.Bucket)+1:]) | |||
if err != nil { | |||
log.Info("Failed to query model size from minio. id=" + modeluuid) | |||
} | |||
size := getSize(files) | |||
size = getSize(files) | |||
models.ModifyModelSize(modeluuid, size) | |||
} | |||
} else if model.Type == models.TypeCloudBrainTwo { | |||
@@ -323,10 +332,13 @@ func UpdateModelSize(modeluuid string) { | |||
if err != nil { | |||
log.Info("Failed to query model size from obs. id=" + modeluuid) | |||
} | |||
size := getSize(files) | |||
size = getSize(files) | |||
models.ModifyModelSize(modeluuid, size) | |||
} | |||
} | |||
if model.Size == 0 && size > 0 { | |||
go repository.ResetRepoModelNum(model.RepoId) | |||
} | |||
} else { | |||
log.Info("not found model,uuid=" + modeluuid) | |||
} | |||
@@ -441,13 +453,14 @@ func DeleteModelFile(ctx *context.Context) { | |||
fileName := ctx.Query("fileName") | |||
model, err := models.QueryModelById(id) | |||
if err == nil { | |||
var totalSize int64 | |||
if model.ModelType == MODEL_LOCAL_TYPE { | |||
if model.Type == models.TypeCloudBrainOne { | |||
bucketName := setting.Attachment.Minio.Bucket | |||
objectName := model.Path[len(bucketName)+1:] + fileName | |||
log.Info("delete bucket=" + bucketName + " path=" + objectName) | |||
if strings.HasPrefix(model.Path, bucketName+"/"+Model_prefix) { | |||
totalSize := storage.MinioGetFilesSize(bucketName, []string{objectName}) | |||
totalSize = storage.MinioGetFilesSize(bucketName, []string{objectName}) | |||
err := storage.Attachments.DeleteDir(objectName) | |||
if err != nil { | |||
log.Info("Failed to delete model. id=" + id) | |||
@@ -467,7 +480,7 @@ func DeleteModelFile(ctx *context.Context) { | |||
objectName := model.Path[len(setting.Bucket)+1:] + fileName | |||
log.Info("delete bucket=" + setting.Bucket + " path=" + objectName) | |||
if strings.HasPrefix(model.Path, bucketName+"/"+Model_prefix) { | |||
totalSize := storage.ObsGetFilesSize(bucketName, []string{objectName}) | |||
totalSize = storage.ObsGetFilesSize(bucketName, []string{objectName}) | |||
err := storage.ObsRemoveObject(bucketName, objectName) | |||
if err != nil { | |||
log.Info("Failed to delete model. id=" + id) | |||
@@ -484,6 +497,9 @@ func DeleteModelFile(ctx *context.Context) { | |||
} | |||
} | |||
} | |||
if (model.Size - totalSize) <= 0 { | |||
go repository.ResetRepoModelNum(model.RepoId) | |||
} | |||
} | |||
ctx.JSON(200, map[string]string{ | |||
"code": "0", | |||
@@ -552,6 +568,9 @@ func deleteModelByID(ctx *context.Context, id string) error { | |||
} | |||
} | |||
} | |||
if model.Size > 0 { | |||
go repository.ResetRepoModelNum(model.RepoId) | |||
} | |||
} | |||
} | |||
return err | |||
@@ -29,6 +29,7 @@ import ( | |||
"code.gitea.io/gitea/modules/storage" | |||
"code.gitea.io/gitea/modules/upload" | |||
"code.gitea.io/gitea/modules/worker" | |||
repo_service "code.gitea.io/gitea/services/repository" | |||
gouuid "github.com/satori/go.uuid" | |||
) | |||
@@ -180,6 +181,7 @@ func DeleteAttachment(ctx *context.Context) { | |||
ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err)) | |||
return | |||
} | |||
go repo_service.DecreaseRepoDatasetNum(attach.DatasetID) | |||
attachjson, _ := json.Marshal(attach) | |||
labelmsg.SendDeleteAttachToLabelSys(string(attachjson)) | |||
@@ -894,6 +896,7 @@ func CompleteMultipart(ctx *context.Context) { | |||
return | |||
} | |||
attachment.UpdateDatasetUpdateUnix() | |||
go repo_service.IncreaseRepoDatasetNum(dataset.ID) | |||
repository, _ := models.GetRepositoryByID(dataset.RepoID) | |||
notification.NotifyOtherTask(ctx.User, repository, fmt.Sprint(repository.IsPrivate, attachment.IsPrivate), attachment.Name, models.ActionUploadAttachment) | |||
if attachment.DatasetID != 0 { | |||
@@ -14,7 +14,13 @@ import ( | |||
) | |||
func CloudbrainDurationStatisticHour() { | |||
if setting.IsCloudbrainTimingEnabled { | |||
defer func() { | |||
err := recover() | |||
if err == nil { | |||
return | |||
} | |||
}() | |||
if setting.IsCloudbrainTimingEnabled { | |||
var statisticTime time.Time | |||
var count int64 | |||
recordDurationUpdateTime, err := models.GetDurationRecordUpdateTime() | |||
@@ -166,6 +166,8 @@ func RepoStatisticDaily(date string) { | |||
repoStat.NumIssuesGrowth = repoStat.NumIssues - repoStatisticFourMonthsAgo.NumIssues | |||
} | |||
models.SyncStatDataToRepo(repo) | |||
if _, err = models.InsertRepoStat(&repoStat); err != nil { | |||
log.Error("InsertRepoStat failed(%s): %v", projectName, err) | |||
log.Error("failed statistic: %s", projectName) | |||
@@ -371,7 +371,18 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/images/custom", repo.GetCustomImages) | |||
m.Get("/images/star", repo.GetStarImages) | |||
m.Get("/repos", routers.ExploreRepos) | |||
m.Group("/repos", func() { | |||
//m.Get("", routers.ExploreRepos) | |||
m.Get("", routers.GetRepoSearchPage) | |||
m.Group("/square", func() { | |||
m.Get("", routers.GetRepoSquarePage) | |||
m.Get("/tab", routers.RepoSquare) | |||
m.Get("/active-user", routers.ActiveUser) | |||
m.Get("/active-org", routers.ActiveOrg) | |||
}) | |||
m.Get("/search", routers.RepoFind) | |||
}) | |||
m.Get("/datasets", routers.ExploreDatasets) | |||
m.Get("/users", routers.ExploreUsers) | |||
m.Get("/organizations", routers.ExploreOrganizations) | |||
@@ -0,0 +1,88 @@ | |||
package repository | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/redis/redis_client" | |||
"code.gitea.io/gitea/modules/redis/redis_key" | |||
"encoding/json" | |||
"github.com/patrickmn/go-cache" | |||
"time" | |||
) | |||
var repoContributorCache = cache.New(5*time.Minute, 1*time.Minute) | |||
type ContributorCacheVal struct { | |||
Contributors []*models.ContributorInfo | |||
Total int | |||
} | |||
func GetRepoTopNContributors(repo *models.Repository, N int) ([]*models.ContributorInfo, int) { | |||
val, _ := redis_client.Get(redis_key.RepoTopNContributors(repo.ID, N)) | |||
if val != "" { | |||
log.Debug("Get RepoTopNContributors from redis,repo.ID = %d value = %v", repo.ID, val) | |||
temp := &ContributorCacheVal{} | |||
json.Unmarshal([]byte(val), temp) | |||
return temp.Contributors, temp.Total | |||
} | |||
contributorInfos, total := getRepoTopNContributorsFromDisk(repo, N) | |||
log.Debug("Get RepoTopNContributors from disk,repo.ID = %d ", repo.ID) | |||
jsonVal, err := json.Marshal(&ContributorCacheVal{Contributors: contributorInfos, Total: total}) | |||
if err == nil { | |||
redis_client.Setex(redis_key.RepoTopNContributors(repo.ID, N), string(jsonVal), 2*time.Minute) | |||
} | |||
return contributorInfos, total | |||
} | |||
func getRepoTopNContributorsFromDisk(repo *models.Repository, N int) ([]*models.ContributorInfo, int) { | |||
contributorInfos := make([]*models.ContributorInfo, 0) | |||
branchName := GetDefaultBranchName(repo) | |||
if branchName == "" { | |||
return contributorInfos, 0 | |||
} | |||
contributors, err := git.GetContributors(repo.RepoPath(), branchName) | |||
if err == nil && contributors != nil { | |||
contributorInfoHash := make(map[string]*models.ContributorInfo) | |||
for _, c := range contributors { | |||
if len(contributorInfos) >= N { | |||
break | |||
} | |||
if c.Email == "" { | |||
continue | |||
} | |||
// get user info from committer email | |||
user, err := models.GetUserByActivateEmail(c.Email) | |||
if err == nil { | |||
// committer is system user, get info through user's primary email | |||
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok { | |||
// existed: same primary email, different committer name | |||
existedContributorInfo.CommitCnt += c.CommitCnt | |||
} else { | |||
// new committer info | |||
var newContributor = &models.ContributorInfo{ | |||
user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt, | |||
} | |||
contributorInfos = append(contributorInfos, newContributor) | |||
contributorInfoHash[user.Email] = newContributor | |||
} | |||
} else { | |||
// committer is not system user | |||
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok { | |||
// existed: same primary email, different committer name | |||
existedContributorInfo.CommitCnt += c.CommitCnt | |||
} else { | |||
var newContributor = &models.ContributorInfo{ | |||
"", "", c.Email, c.CommitCnt, | |||
} | |||
contributorInfos = append(contributorInfos, newContributor) | |||
contributorInfoHash[c.Email] = newContributor | |||
} | |||
} | |||
} | |||
} | |||
return contributorInfos, len(contributors) | |||
} |
@@ -5,18 +5,19 @@ | |||
package repository | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"net/http" | |||
"os" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/notification" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/setting" | |||
pull_service "code.gitea.io/gitea/services/pull" | |||
"fmt" | |||
"io/ioutil" | |||
"net/http" | |||
"os" | |||
"strings" | |||
"xorm.io/xorm" | |||
) | |||
const SHELL_FLAG_ON = 1 | |||
@@ -328,3 +329,47 @@ func IsUploadFileInvalidErr(err error) bool { | |||
_, ok := err.(UploadFileInvalidErr) | |||
return ok | |||
} | |||
func IncreaseRepoDatasetNum(datasetID int64, engines ...*xorm.Engine) error { | |||
dataset, err := models.GetDatasetByID(datasetID) | |||
if err != nil { | |||
return err | |||
} | |||
return models.OperateRepoDatasetNum(dataset.RepoID, 1, engines...) | |||
} | |||
func IncreaseRepoModelNum(repoId int64, engines ...*xorm.Engine) error { | |||
return models.OperateRepoModelNum(repoId, 1, engines...) | |||
} | |||
func ResetRepoModelNum(repoId int64) error { | |||
return models.ResetRepoModelNum(repoId) | |||
} | |||
func DecreaseRepoDatasetNum(datasetID int64, engines ...*xorm.Engine) error { | |||
dataset, err := models.GetDatasetByID(datasetID) | |||
if err != nil { | |||
return err | |||
} | |||
return models.OperateRepoDatasetNum(dataset.RepoID, -1, engines...) | |||
} | |||
func DecreaseRepoModelNum(repoId int64, engines ...*xorm.Engine) error { | |||
return models.OperateRepoModelNum(repoId, -1, engines...) | |||
} | |||
func GetDefaultBranchName(repo *models.Repository) string { | |||
gitRepo, err := git.OpenRepository(repo.RepoPath()) | |||
if err != nil { | |||
return "" | |||
} | |||
defer gitRepo.Close() | |||
if len(repo.DefaultBranch) > 0 && gitRepo.IsBranchExist(repo.DefaultBranch) { | |||
return repo.DefaultBranch | |||
} | |||
brs, _, err := gitRepo.GetBranches(0, 0) | |||
if len(brs) > 0 { | |||
return brs[0] | |||
} | |||
return "" | |||
} |
@@ -0,0 +1,315 @@ | |||
package repository | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"encoding/json" | |||
"github.com/patrickmn/go-cache" | |||
"time" | |||
) | |||
var repoSquareCache = cache.New(2*time.Minute, 1*time.Minute) | |||
const ( | |||
RREFERED_CACHE = "PreferredRepos" | |||
REPO_BANNER_CACHE = "RepoBanner" | |||
TOPICS_CACHE = "RepoTopics" | |||
RECOMMEND_CACHE = "RecommendRepos" | |||
) | |||
func GetBanners() []map[string]string { | |||
v, success := repoSquareCache.Get(REPO_BANNER_CACHE) | |||
if success { | |||
log.Debug("GetBanners from cache,value = %v", v) | |||
if v == nil { | |||
return nil | |||
} | |||
r := v.([]map[string]string) | |||
return r | |||
} | |||
repoMap := getMapContent("repos/square_banner") | |||
repoSquareCache.Set(REPO_BANNER_CACHE, repoMap, 1*time.Minute) | |||
return repoMap | |||
} | |||
func GetTopics() []string { | |||
v, success := repoSquareCache.Get(TOPICS_CACHE) | |||
if success { | |||
log.Debug("GetTopics from cache,value = %v", v) | |||
if v == nil { | |||
return nil | |||
} | |||
r := v.([]string) | |||
return r | |||
} | |||
topics := getArrayContent("repos/recommend_topics") | |||
repoSquareCache.Set(TOPICS_CACHE, topics, 1*time.Minute) | |||
return topics | |||
} | |||
func getMapContent(fileName string) []map[string]string { | |||
url := setting.RecommentRepoAddr + fileName | |||
result, err := RecommendContentFromPromote(url) | |||
remap := make([]map[string]string, 0) | |||
if err == nil { | |||
json.Unmarshal([]byte(result), &remap) | |||
} | |||
return remap | |||
} | |||
func getArrayContent(fileName string) []string { | |||
url := setting.RecommentRepoAddr + fileName | |||
result, err := RecommendContentFromPromote(url) | |||
r := make([]string, 0) | |||
if err == nil { | |||
json.Unmarshal([]byte(result), &r) | |||
} | |||
return r | |||
} | |||
func GetRecommendRepos() []map[string]interface{} { | |||
v, success := repoSquareCache.Get(RECOMMEND_CACHE) | |||
if success { | |||
log.Debug("GetRecommendRepos from cache,value = %v", v) | |||
if v == nil { | |||
return nil | |||
} | |||
r := v.([]map[string]interface{}) | |||
return r | |||
} | |||
repoMap := getMapContent("home/projects") | |||
r, _ := GetRecommendRepoFromPromote(repoMap) | |||
repoSquareCache.Set(RECOMMEND_CACHE, r, 1*time.Minute) | |||
return r | |||
} | |||
func GetPreferredRepos() ([]*models.Repository4Card, error) { | |||
v, success := repoSquareCache.Get(RREFERED_CACHE) | |||
if success { | |||
log.Debug("GetPreferredRepos from cache,value = %v", v) | |||
if v == nil { | |||
return nil, nil | |||
} | |||
r := v.([]*models.Repository4Card) | |||
return r, nil | |||
} | |||
repos, err := models.GetSelectedRepos(models.FindSelectedReposOpts{ | |||
ListOptions: models.ListOptions{ | |||
PageSize: 10, | |||
Page: 1, | |||
}, | |||
OnlyPublic: true, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
result := make([]*models.Repository4Card, len(repos)) | |||
for i, r := range repos { | |||
result[i] = r.ToCardFormat() | |||
} | |||
repoSquareCache.Set(RREFERED_CACHE, result, 1*time.Minute) | |||
return result, nil | |||
} | |||
func GetIncubationRepos() ([]*models.Repository4Card, error) { | |||
org, err := models.GetOrgByName(setting.IncubationSourceOrgName) | |||
if models.IsErrOrgNotExist(err) { | |||
return make([]*models.Repository4Card, 0), nil | |||
} | |||
if err != nil { | |||
return nil, err | |||
} | |||
repos, err := models.GetSelectedRepos(models.FindSelectedReposOpts{ | |||
ListOptions: models.ListOptions{ | |||
PageSize: 10, | |||
Page: 1, | |||
}, | |||
OrgId: org.ID, | |||
OnlyPublic: true, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
result := make([]*models.Repository4Card, len(repos)) | |||
for i, r := range repos { | |||
result[i] = r.ToCardFormat() | |||
} | |||
return result, nil | |||
} | |||
func GetHotPaperRepos() ([]*models.Repository4Card, error) { | |||
rlist, _, err := models.SearchRepository(&models.SearchRepoOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: 1, | |||
PageSize: 10, | |||
}, | |||
OrderBy: models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated, | |||
TopicOnly: true, | |||
TopicName: setting.PaperRepoTopicName, | |||
AllPublic: true, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
result := make([]*models.Repository4Card, len(rlist)) | |||
for i, r := range rlist { | |||
result[i] = r.ToCardFormat() | |||
} | |||
return result, nil | |||
} | |||
type FindReposOptions struct { | |||
models.ListOptions | |||
Actor *models.User | |||
Sort string | |||
Keyword string | |||
Topic string | |||
Private bool | |||
OwnerID int64 | |||
} | |||
func FindRepos(opts FindReposOptions) (*models.FindReposResponse, error) { | |||
var ( | |||
repos []*models.Repository | |||
count int64 | |||
err error | |||
orderBy models.SearchOrderBy | |||
) | |||
switch opts.Sort { | |||
//1.近期热门:按最近1个月浏览量倒序排序,最近1个月浏览量>最近更新>项目名称升序 | |||
case "mostpopular": | |||
orderBy = models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//2.近期活跃:按提交增长量(最近4个月commit数)倒序排序,提交增长量>最近更新>项目名称升序。 | |||
case "mostactive": | |||
orderBy = models.SearchOrderByLastFourMonthCommitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//3.最近更新:按最近更新>项目名称升序排序。 | |||
case "recentupdate": | |||
orderBy = models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//4.最近创建:按项目创建时间排序,最近的排前面。最近创建>项目名称升序。 | |||
case "newest": | |||
orderBy = models.SearchOrderByNewest + "," + models.SearchOrderByAlphabetically | |||
//5.点赞最多:按点赞数倒序排序。点赞数>最近更新>项目名称升序。 | |||
case "moststars": | |||
orderBy = models.SearchOrderByStarsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//6.派生最多:按派生数倒序排序。派生数>最近更新>项目名称升序。 | |||
case "mostforks": | |||
orderBy = models.SearchOrderByForksReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//7.数据集最多:按项目包含的数据集文件数量倒序排序,数据集文件数>最近更新>项目名称升序。 | |||
case "mostdatasets": | |||
orderBy = models.SearchOrderByDatasetCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//8.AI任务最多:按项目包含的AI任务数量倒序排序,AI任务数>最近更新>项目名称升序。 | |||
case "mostaitasks": | |||
orderBy = models.SearchOrderByAiTaskCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
//9.模型最多:按项目包含的模型数量倒序排序,模型大小为0则不统计。模型数>最近更新>项目名称升序。 | |||
case "mostmodels": | |||
orderBy = models.SearchOrderByModelCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
default: | |||
orderBy = models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
} | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
ListOptions: opts.ListOptions, | |||
Actor: opts.Actor, | |||
OrderBy: orderBy, | |||
Private: opts.Private, | |||
Keyword: opts.Keyword, | |||
OwnerID: opts.OwnerID, | |||
AllPublic: true, | |||
AllLimited: true, | |||
TopicName: opts.Topic, | |||
IncludeDescription: setting.UI.SearchRepoDescription, | |||
}) | |||
if err != nil { | |||
log.Error("FindRepos error when SearchRepository.%v", err) | |||
return nil, err | |||
} | |||
result := make([]*models.Repository4Card, len(repos)) | |||
for i, r := range repos { | |||
t := r.ToCardFormat() | |||
contributors, _ := GetRepoTopNContributors(r, 6) | |||
t.Contributors = contributors | |||
result[i] = t | |||
} | |||
return &models.FindReposResponse{ | |||
Repos: result, | |||
Total: count, | |||
Page: opts.Page, | |||
PageSize: opts.PageSize, | |||
}, nil | |||
} | |||
type ActiveUser struct { | |||
User *models.User4Front | |||
Followed bool | |||
ShowButton bool | |||
} | |||
func GetActiveUser4Square(currentUserId int64) ([]*ActiveUser, error) { | |||
result := make([]*ActiveUser, 0) | |||
userIds, err := models.QueryLast30DaysHighestIndexUsers(5) | |||
if err != nil { | |||
log.Error("ActiveUser err. %v", err) | |||
return result, err | |||
} | |||
if len(userIds) == 0 { | |||
return result, nil | |||
} | |||
users, err := models.GetUsersByIDs(userIds) | |||
if err != nil { | |||
return result, nil | |||
} | |||
usersMap := make(map[int64]*models.User) | |||
for _, v := range users { | |||
usersMap[v.ID] = v | |||
} | |||
for i := 0; i < len(userIds); i++ { | |||
userId := userIds[i] | |||
user := usersMap[userId] | |||
if user == nil { | |||
continue | |||
} | |||
isFollowed := false | |||
if currentUserId != 0 { | |||
isFollowed = models.IsFollowing(currentUserId, userId) | |||
} | |||
a := &ActiveUser{ | |||
Followed: isFollowed, | |||
User: user.ToFrontFormat(), | |||
ShowButton: currentUserId != userId, | |||
} | |||
result = append(result, a) | |||
} | |||
return result, nil | |||
} | |||
func GetActiveOrgs() ([]*models.User4Front, error) { | |||
orgScores, err := models.FindTopNOpenIOrgs(5) | |||
if err != nil { | |||
return nil, err | |||
} | |||
orgs := make([]*models.User4Front, len(orgScores)) | |||
for i, v := range orgScores { | |||
orgs[i] = v.ToFrontFormat() | |||
} | |||
return orgs, nil | |||
} | |||
func RefreshRepoStatData() { | |||
repos, err := models.GetAllRepositories() | |||
if err != nil { | |||
log.Error("RefreshRepoStatData GetAllRepositories failed: %v", err.Error()) | |||
return | |||
} | |||
for _, repo := range repos { | |||
models.SyncStatDataToRepo(repo) | |||
} | |||
} |
@@ -35,7 +35,7 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -48,7 +48,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -75,7 +75,7 @@ | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -89,7 +89,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" > | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -100,7 +100,7 @@ | |||
</div> | |||
</div> | |||
{{else if .IsLandingPageExplore}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
{{else if .IsLandingPageOrganizations}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
{{end}} | |||
@@ -32,7 +32,7 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -45,7 +45,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -71,7 +71,7 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -84,7 +84,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -95,7 +95,7 @@ | |||
</div> | |||
</div> | |||
{{else if .IsLandingPageExplore}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
{{else if .IsLandingPageOrganizations}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
{{end}} | |||
@@ -24,7 +24,7 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -37,7 +37,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -64,7 +64,7 @@ | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -77,7 +77,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -88,7 +88,7 @@ | |||
</div> | |||
</div> | |||
{{else if .IsLandingPageExplore}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
{{else if .IsLandingPageOrganizations}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
{{end}} | |||
@@ -34,7 +34,7 @@ | |||
</div> | |||
</div> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
<div class="ui simple dropdown item" > | |||
{{.i18n.Tr "repo.model_manager"}} | |||
@@ -47,7 +47,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -87,7 +87,7 @@ | |||
{{.i18n.Tr "explore"}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" > | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
{{if .IsOperator}} | |||
@@ -98,7 +98,7 @@ | |||
</div> | |||
</div> | |||
{{else if .IsLandingPageExplore}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
{{else if .IsLandingPageOrganizations}} | |||
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
{{end}} | |||
@@ -1,6 +1,6 @@ | |||
<div class="tablet only mobile only sixteen wide mobile sixteen wide tablet column row"> | |||
<div class="ui secondary pointing tabular top attached borderless menu navbar"> | |||
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos"> | |||
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos/square"> | |||
{{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}} | |||
</a> | |||
<a class="{{if .PageIsDatasets}}active{{end}} item" href="{{AppSubUrl}}/explore/datasets"> | |||
@@ -24,7 +24,7 @@ | |||
<div class="computer only three wide computer column"> | |||
<div class="ui grid"> | |||
<div class="sixteen wide column ui secondary sticky pointing tabular vertical menu"> | |||
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos"> | |||
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos/square"> | |||
{{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}} | |||
</a> | |||
<a class="{{if .PageIsDatasets}}active{{end}} item" href="{{AppSubUrl}}/explore/datasets"> | |||
@@ -0,0 +1,8 @@ | |||
{{template "base/head_home" .}} | |||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-repos-search.css?v={{MD5 AppVer}}" /> | |||
<script> | |||
var staticSquareTopics = {{ .SquareTopics }}; | |||
</script> | |||
<div id="__vue-root"></div> | |||
<script src="{{StaticUrlPrefix}}/js/vp-repos-search.js?v={{MD5 AppVer}}"></script> | |||
{{template "base/footer" .}} |
@@ -0,0 +1,16 @@ | |||
{{template "base/head_home" .}} | |||
{{ if .SquareBanners }} | |||
{{ range .SquareBanners }} | |||
<img preload style="height:0;width:0;position:absolute;left:-2000px;" src="{{.src}}" /> | |||
{{ end }} | |||
{{ end }} | |||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-repos-square.css?v={{MD5 AppVer}}" /> | |||
<script> | |||
var staticSquareBanners = {{ .SquareBanners }}; | |||
var staticSquarePreferredRepos = {{ .SquarePreferredRepos }}; | |||
var staticSquareTopics = {{ .SquareTopics }}; | |||
var staticSquareRecommendRepos = {{ .SquareRecommendRepos }}; | |||
</script> | |||
<div id="__vue-root"></div> | |||
<script src="{{StaticUrlPrefix}}/js/vp-repos-square.js?v={{MD5 AppVer}}"></script> | |||
{{template "base/footer" .}} |
@@ -10,7 +10,7 @@ | |||
{{.i18n.Tr "home.wecome_AI_plt"}} | |||
</div> | |||
<div class="content"> | |||
<p class="ui text grey">{{.i18n.Tr "home.explore_AI"}} <a href="{{AppSubUrl}}/explore/repos"> {{.i18n.Tr "home.repositories"}}</a> {{.i18n.Tr "home.or_t"}} <a href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "home.datasets"}}</a></p> | |||
<p class="ui text grey">{{.i18n.Tr "home.explore_AI"}} <a href="{{AppSubUrl}}/explore/repos/square"> {{.i18n.Tr "home.repositories"}}</a> {{.i18n.Tr "home.or_t"}} <a href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "home.datasets"}}</a></p> | |||
<p><span class="ui text grey">{{.i18n.Tr "home.use_plt__fuction"}}</span> <a class="mini ui blue button" href="{{AppSubUrl}}/repo/create{{if .ContextUser.IsOrganization}}?org={{.ContextUser.ID}}{{end}}" >{{.i18n.Tr "repo.create_repo"}}</a></p> | |||
<p class="ui text grey">{{.i18n.Tr "home.provide_resoure"}}</p> | |||
</div> | |||
@@ -50,7 +50,7 @@ import initImage from "./features/images.js"; | |||
import selectDataset from "./components/dataset/selectDataset.vue"; | |||
import referenceDataset from "./components/dataset/referenceDataset.vue"; | |||
// import $ from 'jquery.js' | |||
import router from "./router/index.js"; | |||
// import router from "./router/index.js"; | |||
import { Message } from "element-ui"; | |||
import { i18nVue } from "./features/i18nVue.js"; | |||
@@ -5214,7 +5214,7 @@ function initTopToHome() { | |||
$(window).scroll(function (e) { | |||
const scrollTop = $(document).scrollTop(); | |||
const winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; | |||
if (scrollTop > winHeight * 1.2) { | |||
if (scrollTop > winHeight * 0.5) { | |||
topToHomeEl.fadeIn(); | |||
} else { | |||
topToHomeEl.fadeOut(); | |||
@@ -0,0 +1,12 @@ | |||
import service from '../service'; | |||
// 获取promote配置数据 | |||
export const getPromoteData = (filePathName) => { | |||
return service({ | |||
url: '/dashboard/invitation', | |||
method: 'get', | |||
params: { | |||
filename: filePathName | |||
}, | |||
}); | |||
} |
@@ -0,0 +1,69 @@ | |||
import service from '../service'; | |||
// 获取首页数据 | |||
export const getHomePageData = () => { | |||
return service({ | |||
url: '/recommend/home', | |||
method: 'get', | |||
params: {}, | |||
}); | |||
} | |||
// 获取项目广场上方tab数据 tab=preferred 项目优选|incubation 启智孵化管道|hot-paper 热门论文项目 | |||
export const getReposSquareTabData = (tab) => { | |||
return service({ | |||
url: '/explore/repos/square/tab', | |||
method: 'get', | |||
params: { | |||
type: tab | |||
}, | |||
}); | |||
} | |||
// 搜索项目 | |||
// q string 否 关键词 | |||
// topic string 否 标签名 | |||
// sort string 是 mostpopular 近期热门 | mostactive 近期活跃 | recentupdate 最近更新 | newest 最近创建 | |||
// moststars 点赞最多 | mostforks 派生最多 | mostdatasets 数据集最多 | mostaitasks AI任务最多 | mostmodels 模型最多 | |||
// pageSize int 是 每页大小,可选值为15 | 30 | 50 | |||
// page int 是 页码 | |||
export const getReposListData = (params) => { | |||
return service({ | |||
url: '/explore/repos/search', | |||
method: 'get', | |||
params: { | |||
q: params.q || '', | |||
topic: params.topic || '', | |||
sort: params.sort || 'mostpopular', | |||
pageSize: params.pageSize || 15, | |||
page: params.page || 1, | |||
}, | |||
}); | |||
} | |||
// 获取活跃用户列表 | |||
export const getActiveUsers = () => { | |||
return service({ | |||
url: '/explore/repos/square/active-user', | |||
method: 'get', | |||
params: {}, | |||
}); | |||
} | |||
// 关注用户 | |||
export const followingUsers = (userName, isFollowing) => { | |||
return service({ | |||
url: `/api/v1/user/following/${userName}`, | |||
method: isFollowing ? 'put' : 'delete', | |||
params: {}, | |||
}); | |||
} | |||
// 获取活跃组织列表 | |||
export const getActiveOrgs = () => { | |||
return service({ | |||
url: '/explore/repos/square/active-org', | |||
method: 'get', | |||
params: {}, | |||
}); | |||
} |
@@ -177,21 +177,21 @@ const en = { | |||
Activated: 'Activated', | |||
notActive: 'Not active', | |||
}, | |||
tranformImageFailed:'Picture desensitization failed', | |||
originPicture:'Origin picture', | |||
desensitizationPicture:'Desensitization picture', | |||
desensitizationObject:'Desensitization object', | |||
example:'Example', | |||
startDesensitization:'Start desensitization', | |||
all:'All', | |||
onlyFace:'Only face', | |||
onlyLicensePlate:'Only license plate', | |||
dragThePictureHere:'Drag the picture here', | |||
or:' or ', | |||
clickUpload:'Click upload', | |||
dataDesensitizationModelExperience:'Data desensitization model experience', | |||
dataDesensitizationModelDesc:'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project', | |||
limitFilesUpload:'Only jpg/jpeg/png files can be uploaded', | |||
tranformImageFailed: 'Picture desensitization failed', | |||
originPicture: 'Origin picture', | |||
desensitizationPicture: 'Desensitization picture', | |||
desensitizationObject: 'Desensitization object', | |||
example: 'Example', | |||
startDesensitization: 'Start desensitization', | |||
all: 'All', | |||
onlyFace: 'Only face', | |||
onlyLicensePlate: 'Only license plate', | |||
dragThePictureHere: 'Drag the picture here', | |||
or: ' or ', | |||
clickUpload: 'Click upload', | |||
dataDesensitizationModelExperience: 'Data desensitization model experience', | |||
dataDesensitizationModelDesc: 'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project', | |||
limitFilesUpload: 'Only jpg/jpeg/png files can be uploaded', | |||
limitSizeUpload: 'The size of the uploaded file cannot exceed 20M!', | |||
notebook: { | |||
createNewNotebook: "Create new notebook debug task", | |||
@@ -285,6 +285,59 @@ const en = { | |||
modelAccessPublic:'Public', | |||
modelAccessPrivate:'Private', | |||
}, | |||
repos: { | |||
activeOrganization: 'Active Organization', | |||
activeUsers: 'Active Users', | |||
follow: 'Follow', | |||
unFollow: 'Unfollow', | |||
selectedFields: 'Recommend Repositories', | |||
mostPopular: 'Most Popular', | |||
mostActive: 'Most Active', | |||
newest: 'Newest', | |||
recentlyUpdated: 'Recently Updated', | |||
mostStars: 'Most Stars', | |||
mostForks: 'Most Forks', | |||
mostDatasets: 'Most Datasets', | |||
mostAiTasks: 'Most AI Tasks', | |||
mostModels: 'Most Models', | |||
dataset: 'Datasets', | |||
model: 'Models', | |||
aiTask: 'AI Tasks', | |||
updated: 'Updated', | |||
contributors: 'Contributors', | |||
searchRepositories: 'Search Repositories', | |||
search: 'Search', | |||
allFields: 'All Fields', | |||
preferred: 'Preferred', | |||
openIIncubation: 'OpenI Incubation', | |||
hotPapers: 'Hot Papers', | |||
watch: 'Watch', | |||
star: 'Star', | |||
fork: 'Fork', | |||
noReposfound: 'No matching repositories found.', | |||
}, | |||
timeObj: { | |||
ago: '{msg} ago', | |||
from_now: '{msg} from now', | |||
now: 'now', | |||
future: 'future', | |||
'1s': '1 second', | |||
'1m': '1 minute', | |||
'1h': '1 hour', | |||
'1d': '1 day', | |||
'1w': '1 week', | |||
'1mon': '1 month', | |||
'1y': '1 year', | |||
seconds: '{msg} seconds', | |||
minutes: '{msg} minutes', | |||
hours: '{msg} hours', | |||
days: '{msg} days', | |||
weeks: '{msg} weeks', | |||
months: '{msg} months', | |||
years: '{msg} years', | |||
raw_seconds: 'seconds', | |||
raw_minutes: 'minutes', | |||
}, | |||
} | |||
export default en; |
@@ -220,8 +220,24 @@ const zh = { | |||
graphicMemory: "显存", | |||
memory: "内存", | |||
sharedMemory: "共享内存", | |||
tips:'本次新建的调试任务会放在您名下项目openi-notebook中,如果没有该项目系统会自动新建一个。' | |||
tips: '本次新建的调试任务会放在您名下项目openi-notebook中,如果没有该项目系统会自动新建一个。' | |||
}, | |||
tranformImageFailed: '图片脱敏失败', | |||
originPicture: '原始图片', | |||
desensitizationPicture: '脱敏图片', | |||
desensitizationObject: '脱敏对象', | |||
example: '示例', | |||
startDesensitization: '开始处理', | |||
all: '全部', | |||
onlyFace: '仅人脸', | |||
onlyLicensePlate: '仅车牌', | |||
dragThePictureHere: '拖动图片到这里', | |||
or: '或', | |||
clickUpload: '点击上传', | |||
dataDesensitizationModelExperience: '数据脱敏模型体验', | |||
dataDesensitizationModelDesc: '利用人工智能AI技术,把图片中的人脸、车牌号码进行脱敏处理。该模型更多信息请访问项目', | |||
limitFilesUpload: '只能上传 jpg/jpeg/png 格式的文件', | |||
limitSizeUpload: '上传文件大小不能超过 20M !', | |||
modelManage: { | |||
modelManage: '模型管理', | |||
modelName: '模型名称', | |||
@@ -286,8 +302,59 @@ const zh = { | |||
modelAccessPublic:'公开', | |||
modelAccessPrivate:'私有', | |||
}, | |||
}; | |||
repos: { | |||
activeOrganization: '活跃组织', | |||
activeUsers: '活跃用户', | |||
follow: '关注', | |||
unFollow: '取消关注', | |||
selectedFields: '领域精选', | |||
mostPopular: '近期热门', | |||
mostActive: '近期活跃', | |||
newest: '最近创建', | |||
recentlyUpdated: '最近更新', | |||
mostStars: '点赞最多', | |||
mostForks: '派生最多', | |||
mostDatasets: '数据集最多', | |||
mostAiTasks: 'AI任务最多', | |||
mostModels: '模型最多', | |||
dataset: '数据集', | |||
model: '模型', | |||
aiTask: 'AI任务', | |||
updated: '最后更新于', | |||
contributors: '贡献者', | |||
searchRepositories: '搜项目', | |||
search: '搜索', | |||
allFields: '全部领域', | |||
preferred: '项目优选', | |||
openIIncubation: '启智孵化管道', | |||
hotPapers: '热门论文项目', | |||
watch: '关注', | |||
star: '点赞', | |||
fork: '派生', | |||
noReposfound: '未找到匹配的项目。', | |||
}, | |||
timeObj: { | |||
ago: '{msg}前', | |||
from_now: '{msg} 之后', | |||
now: '现在', | |||
future: '将来', | |||
'1s': '1 秒', | |||
'1m': '1 分钟', | |||
'1h': '1 小时', | |||
'1d': '1 天', | |||
'1w': '1 周', | |||
'1mon': '1 个月', | |||
'1y': '1 年', | |||
seconds: '{msg} 秒', | |||
minutes: '{msg} 分钟', | |||
hours: '{msg} 小时', | |||
days: '{msg} 天', | |||
weeks: '{msg} 周', | |||
months: '{msg} 个月', | |||
years: '{msg} 年', | |||
raw_seconds: '秒', | |||
raw_minutes: '分钟', | |||
}, | |||
} | |||
export default zh; |
@@ -0,0 +1,123 @@ | |||
<template> | |||
<div> | |||
<div class="container"> | |||
<div class="title"> | |||
<i style="margin-left:10px;margin-right:8px;font-size:20px;" class="ri-blaze-line"></i> | |||
<span>{{ $t('repos.activeOrganization') }}</span> | |||
</div> | |||
<div class="content"> | |||
<div class="item" v-for="(item, index) in list" :key="index"> | |||
<div class="item-l"> | |||
<a class="name" :href="`/${item.Name}`" :title="item.Name"> | |||
<img class="avatar" :src="item.RelAvatarLink"> | |||
<div class="name-c"><span>{{ item.Name }}</span></div> | |||
</a> | |||
</div> | |||
<div class="item-r"> | |||
<i class="ri-user-2-line" style="color:rgb(250, 140, 22);margin-right:4px;"></i> | |||
<span>{{ item.NumMembers }}</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { getActiveOrgs } from '~/apis/modules/repos'; | |||
export default { | |||
name: "ActiveOrgs", | |||
props: {}, | |||
components: {}, | |||
data() { | |||
return { | |||
list: [], | |||
}; | |||
}, | |||
methods: {}, | |||
mounted() { | |||
getActiveOrgs().then(res => { | |||
res = res.data; | |||
if (res.Code == 0) { | |||
this.list = res.Data.Orgs || []; | |||
} else { | |||
this.list = []; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.list = []; | |||
}); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 43px; | |||
border-color: rgba(16, 16, 16, 0.05); | |||
border-width: 1px 0px; | |||
border-style: solid; | |||
display: flex; | |||
align-items: center; | |||
font-size: 18px; | |||
color: rgba(47, 9, 69, 0.74); | |||
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-1.007%2C%200.0010000000000001494%2C%20-0.000018400023883213844%2C%20-1.007%2C%201.003%2C%200.008)%22%3E%3Cstop%20stop-color%3D%22%23eee9da%22%20stop-opacity%3D%220%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23f3e7f7%22%20stop-opacity%3D%220.26%22%20offset%3D%220.29%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23d0e7ff%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
} | |||
.content { | |||
padding: 6px 0; | |||
} | |||
.item { | |||
display: flex; | |||
align-items: center; | |||
height: 48px; | |||
padding: 0 8px; | |||
font-size: 14px; | |||
color: rgba(16, 16, 16, 1); | |||
} | |||
.item>div { | |||
display: flex; | |||
align-items: center; | |||
} | |||
.item-l { | |||
flex: 1; | |||
overflow: hidden; | |||
a { | |||
display: flex; | |||
align-items: center; | |||
overflow: hidden; | |||
} | |||
} | |||
.item-r { | |||
width: 80px; | |||
justify-content: flex-end; | |||
} | |||
.item .avatar { | |||
width: 32px; | |||
height: 32px; | |||
border-radius: 50%; | |||
margin-right: 10px; | |||
} | |||
.item .name-c { | |||
flex: 1; | |||
overflow: hidden; | |||
width: 100%; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.item .name { | |||
color: rgba(16, 16, 16, 1); | |||
&:hover { | |||
opacity: 0.8; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,156 @@ | |||
<template> | |||
<div> | |||
<div class="container"> | |||
<div class="title"> | |||
<i style="margin-left:10px;margin-right:8px;font-size:20px;" class="ri-account-pin-circle-line"></i> | |||
<span>{{ $t('repos.activeUsers') }}</span> | |||
</div> | |||
<div class="content"> | |||
<div class="item" v-for="(item, index) in list" :key="index"> | |||
<div class="item-l"> | |||
<a class="name" :href="`/${item.User.Name}`" :title="(item.User.FullName || item.User.Name)"> | |||
<img class="avatar" :src="item.User.RelAvatarLink"> | |||
<div class="name-c"> | |||
{{ item.User.FullName || item.User.Name }} | |||
</div> | |||
</a> | |||
</div> | |||
<div class="item-r"> | |||
<template v-if="item.ShowButton"> | |||
<a class="op-btn" v-if="!item.Followed" href="javascript:;" @click="following(item, index, true)"> | |||
{{ $t('repos.follow') }}</a> | |||
<a class="op-btn" v-if="item.Followed" href="javascript:;" @click="following(item, index, false)"> | |||
{{ $t('repos.unFollow') }}</a> | |||
</template> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { getActiveUsers, followingUsers } from '~/apis/modules/repos'; | |||
export default { | |||
name: "ActiveUsers", | |||
props: {}, | |||
components: {}, | |||
data() { | |||
return { | |||
list: [], | |||
}; | |||
}, | |||
methods: { | |||
following(userInfo, index, followingOrNot) { | |||
followingUsers(userInfo.User.Name, followingOrNot).then(res => { | |||
if (res.status == 204) { // 成功 | |||
userInfo.Followed = !userInfo.Followed; | |||
} else { | |||
console.log(res); | |||
} | |||
}).catch(err => { | |||
if (err.response.status == 401) { // 未登陆 | |||
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href)}`; | |||
} else { | |||
console.log(err); | |||
} | |||
}); | |||
} | |||
}, | |||
mounted() { | |||
getActiveUsers().then(res => { | |||
res = res.data; | |||
if (res.Code == 0) { | |||
this.list = res.Data.Users || []; | |||
} else { | |||
this.list = []; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.list = []; | |||
}); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 43px; | |||
border-color: rgba(16, 16, 16, 0.05); | |||
border-width: 1px 0px; | |||
border-style: solid; | |||
display: flex; | |||
align-items: center; | |||
font-size: 18px; | |||
color: rgba(47, 9, 69, 0.74); | |||
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-1.007%2C%200.0010000000000001494%2C%20-0.000018400023883213844%2C%20-1.007%2C%201.003%2C%200.008)%22%3E%3Cstop%20stop-color%3D%22%23eee9da%22%20stop-opacity%3D%220%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23f3e7f7%22%20stop-opacity%3D%220.26%22%20offset%3D%220.29%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23d0e7ff%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
} | |||
.content { | |||
padding: 6px 0; | |||
} | |||
.item { | |||
display: flex; | |||
align-items: center; | |||
height: 48px; | |||
padding: 0 8px; | |||
font-size: 14px; | |||
color: rgba(16, 16, 16, 1); | |||
} | |||
.item>div { | |||
display: flex; | |||
align-items: center; | |||
} | |||
.item-l { | |||
flex: 1; | |||
overflow: hidden; | |||
a { | |||
display: flex; | |||
align-items: center; | |||
overflow: hidden; | |||
} | |||
} | |||
.item-r { | |||
width: 80px; | |||
justify-content: flex-end; | |||
} | |||
.item .avatar { | |||
width: 32px; | |||
height: 32px; | |||
border-radius: 50%; | |||
margin-right: 10px; | |||
} | |||
.item .name-c { | |||
flex: 1; | |||
overflow: hidden; | |||
width: 100%; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.item .name { | |||
color: rgba(16, 16, 16, 1); | |||
&:hover { | |||
opacity: 0.8; | |||
} | |||
} | |||
.item .op-btn { | |||
border-color: rgb(50, 145, 248); | |||
border-width: 1px; | |||
border-style: solid; | |||
color: rgb(50, 145, 248); | |||
font-size: 12px; | |||
padding: 0px 6px; | |||
border-radius: 3px; | |||
} | |||
</style> |
@@ -0,0 +1,151 @@ | |||
<template> | |||
<div class="repo-selected-bg"> | |||
<div class="ui container _repo_container _repo-selected-container" style="padding-top:3rem;padding-bottom:3rem;"> | |||
<div class="_repo_title"><span>{{ $t('repos.selectedFields') }}</span></div> | |||
<div class="_repo-selected-list"> | |||
<div class="swiper-wrapper" id="_repo-selected"></div> | |||
<div class="swiper-pagination _repo-selected-swiper-pagination"></div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { getHomePageData } from '~/apis/modules/repos'; | |||
import LetterAvatar from '~/utils/letteravatar'; | |||
export default { | |||
name: "RecommendRepos", | |||
props: { | |||
static: { type: Boolean, default: false }, | |||
staticSwiperData: { type: Array, default: () => [] }, | |||
}, | |||
components: {}, | |||
data() { | |||
return { | |||
swiperHandler: null, | |||
}; | |||
}, | |||
methods: { | |||
renderRepos(json) { | |||
var selectedRepoEl = document.getElementById("_repo-selected"); | |||
var html = ""; | |||
if (json != null && json.length > 0) { | |||
var repoMap = {}; | |||
for (var i = 0, iLen = json.length; i < iLen; i++) { | |||
var repo = json[i]; | |||
var label = repo.Label; | |||
if (repoMap[label]) { | |||
repoMap[label].push(repo); | |||
} else { | |||
repoMap[label] = [repo]; | |||
} | |||
} | |||
for (var label in repoMap) { | |||
var repos = repoMap[label]; | |||
var labelSearch = repos[0].Label; | |||
html += `<div class="swiper-slide"><div><a style="color:rgb(50, 145, 248);font-size:16px;font-weight:550;" href="/explore/repos?q=&topic=${labelSearch}&sort=hot"># ${label}</a></div>`; | |||
for (var i = 0, iLen = repos.length; i < iLen; i++) { | |||
if (i >= 4) break; | |||
var repo = repos[i]; | |||
html += `<div class="ui fluid card"> | |||
<div class="content"> | |||
${repo["Avatar"] ? `<img style="border-radius: 100%;" class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img style="border-radius: 100%;" class="left floated mini ui image" avatar="${repo["OwnerName"]}">`} | |||
<span class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;" href="javascript:;" title="${repo["Alias"]}">${repo["Alias"]}</span> | |||
<div class="description nowrap-2" style="rgba(136,136,136,1);;font-size:12px;" title="${repo["Description"]}">${repo["Description"]}</div> | |||
</div> | |||
<a style="position:absolute;height:100%;width:100%;" href="/${repo["OwnerName"]}/${repo["Name"]}"></a> | |||
</div>`; | |||
} | |||
html += '</div>' | |||
} | |||
this.swiperHandler = new Swiper("._repo-selected-list", { | |||
slidesPerView: 1, | |||
spaceBetween: 25, | |||
pagination: { | |||
el: "._repo-selected-swiper-pagination", | |||
clickable: true, | |||
}, | |||
autoplay: { | |||
delay: 4500, | |||
disableOnInteraction: false, | |||
}, | |||
breakpoints: { | |||
768: { | |||
slidesPerView: 3, | |||
}, | |||
1024: { | |||
slidesPerView: 4, | |||
}, | |||
1200: { | |||
slidesPerView: 4, | |||
}, | |||
1600: { | |||
slidesPerView: 4, | |||
} | |||
}, | |||
}); | |||
selectedRepoEl.innerHTML = html; | |||
this.swiperHandler.updateSlides(); | |||
this.swiperHandler.updateProgress(); | |||
LetterAvatar.transform(); | |||
} | |||
} | |||
}, | |||
mounted() { | |||
if (this.static) { | |||
this.renderRepos(this.staticSwiperData); | |||
} else { | |||
getHomePageData().then(res => { | |||
this.renderRepos(res.data.repo); | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.repo-selected-bg { | |||
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.11899999999999993%2C%201.217%2C%20-0.24039506172839506%2C%200.11899999999999993%2C%200.269%2C%20-0.22)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.47%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23e5e7eb%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
} | |||
._repo_title { | |||
font-size: 18px; | |||
color: rgb(16, 16, 16); | |||
text-align: center; | |||
margin-bottom: 1em; | |||
font-weight: bold; | |||
} | |||
._repo-selected-list { | |||
overflow: hidden; | |||
padding: 1em 1em 3em 1em; | |||
text-align: left; | |||
position: relative; | |||
} | |||
/deep/._repo-selected-swiper-pagination .swiper-pagination-bullet { | |||
width: 8px; | |||
height: 8px; | |||
border-radius: 100%; | |||
background: #76cbed; | |||
} | |||
/deep/._repo-selected-swiper-pagination .swiper-pagination-bullet-active { | |||
width: 40px; | |||
border-radius: 4px; | |||
} | |||
/deep/ ._repo-selected-list .card { | |||
border-radius: 6px; | |||
background-color: #FFF; | |||
box-shadow: 0px 5px 10px 0px rgba(105, 192, 255, .3); | |||
border: 1px solid rgba(105, 192, 255, .4); | |||
} | |||
/deep/ ._repo-selected-list .header { | |||
line-height: 40px !important; | |||
} | |||
</style> |
@@ -0,0 +1,115 @@ | |||
<template> | |||
<div> | |||
<div class="item" :class="(focusIndex == index) ? 'item-focus' : ''" v-for="(item, index) in list" :key="item.key"> | |||
<a href="javascript:;" @click="changeFilters(item, index)">{{ item.label }}</a> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: "ReposFilters", | |||
props: { | |||
defaultsort: { type: String, default: 'mostpopular' }, | |||
}, | |||
components: {}, | |||
data() { | |||
return { | |||
focusIndex: 0, | |||
list: [{ | |||
key: 'mostpopular', | |||
label: this.$t('repos.mostPopular'), | |||
}, { | |||
key: 'mostactive', | |||
label: this.$t('repos.mostActive'), | |||
}, { | |||
key: 'recentupdate', | |||
label: this.$t('repos.recentlyUpdated'), | |||
}, { | |||
key: 'newest', | |||
label: this.$t('repos.newest'), | |||
}, { | |||
key: 'moststars', | |||
label: this.$t('repos.mostStars'), | |||
}, { | |||
key: 'mostforks', | |||
label: this.$t('repos.mostForks'), | |||
}, { | |||
key: 'mostdatasets', | |||
label: this.$t('repos.mostDatasets'), | |||
}, { | |||
key: 'mostaitasks', | |||
label: this.$t('repos.mostAiTasks'), | |||
}, { | |||
key: 'mostmodels', | |||
label: this.$t('repos.mostModels'), | |||
}] | |||
}; | |||
}, | |||
methods: { | |||
changeFilters(item, index) { | |||
this.focusIndex = index; | |||
this.$emit('change', this.list[this.focusIndex]); | |||
}, | |||
setDefaultFilter(sort) { | |||
const index = this.list.findIndex((item) => item.key == sort); | |||
this.focusIndex = index >= 0 ? index : 0; | |||
} | |||
}, | |||
mounted() { | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.item { | |||
height: 40px; | |||
border-color: rgba(16, 16, 16, 0.05); | |||
border-width: 0px 0px 1px; | |||
border-style: solid; | |||
color: rgba(16, 16, 16, 0.8); | |||
font-size: 14px; | |||
padding: 0px; | |||
display: flex; | |||
align-items: center; | |||
justify-content: flex-end; | |||
position: relative; | |||
} | |||
.item a { | |||
color: inherit; | |||
height: 100%; | |||
display: flex; | |||
align-items: center; | |||
&:hover { | |||
opacity: 0.8; | |||
} | |||
} | |||
.item-focus { | |||
font-weight: bold; | |||
border-color: rgba(0, 108, 205, 0.3); | |||
color: rgb(50, 145, 248); | |||
a { | |||
cursor: default; | |||
&:hover { | |||
opacity: 1; | |||
} | |||
} | |||
} | |||
.item-focus:before { | |||
content: ""; | |||
position: absolute; | |||
width: 7px; | |||
height: 7px; | |||
bottom: -4px; | |||
background: rgb(178, 211, 240); | |||
left: 0; | |||
border-radius: 100%; | |||
} | |||
</style> |
@@ -0,0 +1,316 @@ | |||
<template> | |||
<div> | |||
<div class="item"> | |||
<div class="item-top"> | |||
<img v-if="data.RelAvatarLink" class="avatar" :src="data.RelAvatarLink" /> | |||
<img v-else class="avatar" :avatar="data.OwnerName" /> | |||
<div class="content"> | |||
<div class="title"> | |||
<div class="title-l"> | |||
<a :href="`/${data.OwnerName}/${data.Name}`" :title="`${data.OwnerName}/${data.Name}`"> | |||
<span class="title-1">{{ data.OwnerName }}</span> | |||
<span class="title-1"> / </span> | |||
<span class="title-2" v-html="data.NameShow"></span> | |||
</a> | |||
<i v-if="data.IsArchived" class="archive icon archived-icon"></i> | |||
<svg v-if="data.IsFork" class="svg octicon-repo-forked" width="15" height="15" aria-hidden="true"> | |||
<use xlink:href="#octicon-repo-forked"></use> | |||
</svg> | |||
<svg v-if="data.IsMirror" class="svg octicon-repo-clone" width="15" height="15" aria-hidden="true"> | |||
<use xlink:href="#octicon-repo-clone"></use> | |||
</svg> | |||
<svg v-if="(data.IsPrivate || data.IsOwnerPrivate)" style="color:#a1882b!important" class="svg octicon-lock" width="15" height="15" | |||
aria-hidden="true"> | |||
<use xlink:href="#octicon-lock"></use> | |||
</svg> | |||
</div> | |||
<span class="title-r"> | |||
<span class="t-item" :title="$t('repos.watch')"> | |||
<i class="ri-eye-line"></i> | |||
<span>{{ data.NumWatches }}</span> | |||
</span> | |||
<span class="t-item" :title="$t('repos.star')"> | |||
<i class="ri-star-line"></i> | |||
<span>{{ data.NumStars }}</span> | |||
</span> | |||
<span class="t-item" :title="$t('repos.fork')"> | |||
<svg class="svg octicon-repo-forked" width="13" height="13" aria-hidden="true"> | |||
<use xlink:href="#octicon-repo-forked"></use> | |||
</svg> | |||
<span>{{ data.NumForks }}</span></span> | |||
</span> | |||
</div> | |||
<div class="descr" v-show="data.DescriptionShow" v-html="data.DescriptionShow"></div> | |||
<div class="tags" v-show="data.Topics && data.Topics.length"> | |||
<a v-for="(item, index) in data.TopicsShow" :key="index" class="tag" | |||
:class="(item.topic.toLocaleLowerCase() == topic.toLocaleLowerCase() ? 'tag-focus' : '')" | |||
:href="`/explore/repos?q=&topic=${item.topic}&sort=hot`" v-html="item.topicShow"></a> | |||
</div> | |||
<div class="repo-datas" v-show="(data.DatasetCnt > 0) || (data.ModelCnt > 0) || (data.AiTaskCnt > 0)"> | |||
<span class="repo-datas-item" v-show="(data.DatasetCnt > 0)"> | |||
<i class="ri-stack-line"></i> | |||
<span class="label">{{ $t('repos.dataset') }}:</span> | |||
<span class="value">{{ data.DatasetCnt }}</span> | |||
</span> | |||
<span class="repo-datas-item" v-show="(data.ModelCnt > 0)"> | |||
<i class="ri-send-plane-2-line"></i> | |||
<span class="label">{{ $t('repos.model') }}:</span> | |||
<span class="value">{{ data.ModelCnt }}</span> | |||
</span> | |||
<span class="repo-datas-item" v-show="(data.AiTaskCnt > 0)"> | |||
<i class="ri-order-play-line"></i> | |||
<span class="label">{{ $t('repos.aiTask') }}:</span> | |||
<span class="value">{{ data.AiTaskCnt }}</span> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="item-bottom"> | |||
<div> | |||
<span>{{ $t('repos.updated') }}</span> | |||
<el-tooltip effect="dark" :content="dateFormat(data.UpdatedUnix)" placement="top-start"> | |||
<span>{{ calcFromNow(data.UpdatedUnix) }}</span> | |||
</el-tooltip> | |||
<span style="margin-left:8px;" v-if="data.PrimaryLanguage"><i class="color-icon" | |||
:style="{ backgroundColor: data.PrimaryLanguage.Color }"></i>{{ data.PrimaryLanguage.Language }}</span> | |||
</div> | |||
<div class="contributors"> | |||
<span class="contributors-count" v-show="data.Contributors && data.Contributors.length"> | |||
{{ $t('repos.contributors') }} | |||
</span> | |||
<span class="contributors-avatar"> | |||
<a :href="item.UserName ? `/${item.UserName}` : `mailto:${item.Email}`" class="avatar-c" | |||
v-for="(item, index) in data.Contributors" :key="index"> | |||
<img class="avatar" v-show="item.UserName" :src="item.RelAvatarLink"> | |||
<span class="avatar" v-show="!item.UserName" :style="{ backgroundColor: item.bgColor }"> | |||
{{ item.Email[0].toLocaleUpperCase() }}</span> | |||
</a> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import relativeTime from 'dayjs/plugin/relativeTime'; | |||
import localizedFormat from 'dayjs/plugin/localizedFormat'; | |||
import 'dayjs/locale/zh-cn'; | |||
import 'dayjs/locale/en'; | |||
import dayjs from 'dayjs'; | |||
import { lang } from '~/langs'; | |||
import { timeSinceUnix } from '~/utils'; | |||
dayjs.locale(lang == 'zh-CN' ? 'zh-cn' : 'en'); | |||
dayjs.extend(relativeTime); | |||
dayjs.extend(localizedFormat); | |||
export default { | |||
name: "ReposItem", | |||
props: { | |||
data: { type: Object, default: () => ({}) }, | |||
topic: { type: String, default: '' } | |||
}, | |||
components: {}, | |||
data() { | |||
return { | |||
contributors: [], | |||
}; | |||
}, | |||
methods: { | |||
calcFromNow(unix) { | |||
// return dayjs(unix * 1000).fromNow(); | |||
return timeSinceUnix(unix, Date.now() / 1000); | |||
}, | |||
dateFormat(unix) { | |||
return lang == 'zh-CN' ? dayjs(unix * 1000).format('YYYY年MM月DD日 HH时mm分ss秒') : | |||
dayjs(unix * 1000).format('ddd, D MMM YYYY HH:mm:ss [CST]'); | |||
} | |||
}, | |||
mounted() { }, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.item { | |||
width: 100%; | |||
border-color: rgba(157, 197, 226, 0.4); | |||
border-width: 1px; | |||
border-style: solid; | |||
box-shadow: rgb(157 197 226 / 20%) 0px 5px 10px 0px; | |||
border-radius: 15px; | |||
font-size: 14px; | |||
padding: 20px 26px 10px 26px; | |||
margin-bottom: 40px; | |||
} | |||
.item-top { | |||
display: flex; | |||
} | |||
.item-top .avatar { | |||
width: 38px; | |||
height: 38px; | |||
margin-right: 10px; | |||
border-radius: 100%; | |||
} | |||
.content { | |||
flex: 1; | |||
overflow: hidden; | |||
} | |||
.content .title { | |||
display: flex; | |||
align-items: center; | |||
height: 30px; | |||
margin: 4px 0 8px; | |||
} | |||
.content .title-l { | |||
flex: 1; | |||
overflow: hidden; | |||
width: 100%; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.content .title-1 { | |||
font-size: 18px; | |||
color: rgba(16, 16, 16, 0.6); | |||
} | |||
.content .title-2 { | |||
font-size: 18px; | |||
color: rgba(16, 16, 16, 1); | |||
font-weight: bold; | |||
margin-right: 3px; | |||
} | |||
.content .title-r { | |||
display: flex; | |||
align-items: center; | |||
font-weight: 400; | |||
font-size: 12px; | |||
color: rgba(26, 40, 51, 1); | |||
justify-content: flex-end; | |||
} | |||
.content .t-item { | |||
margin-left: 12px; | |||
display: flex; | |||
align-items: center; | |||
} | |||
.content .t-item i { | |||
margin-right: 4px; | |||
} | |||
.content .descr { | |||
font-weight: 300; | |||
font-size: 14px; | |||
color: rgba(16, 16, 16, 0.8); | |||
margin-bottom: 16px; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
word-break: break-all; | |||
display: -webkit-box; | |||
-webkit-box-orient: vertical; | |||
-webkit-line-clamp: 6; | |||
max-height: 120px; | |||
white-space: break-spaces; | |||
} | |||
.content .tags { | |||
margin-bottom: 16px; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.content .tag { | |||
color: rgba(16, 16, 16, 0.8); | |||
border-radius: 4px; | |||
font-size: 14px; | |||
background: rgba(232, 232, 232, 0.6); | |||
padding: 2px 6px; | |||
margin-right: 8px; | |||
&.tag-focus { | |||
color: red; | |||
} | |||
} | |||
.content .repo-datas { | |||
display: flex; | |||
align-items: center; | |||
margin-top: 20px; | |||
margin-bottom: 10px; | |||
} | |||
.content .repo-datas-item { | |||
display: flex; | |||
align-items: center; | |||
margin-right: 24px; | |||
} | |||
.content .repo-datas-item i { | |||
color: rgba(2, 107, 251, 0.54); | |||
margin-right: 4px; | |||
font-size: 16px; | |||
} | |||
.content .repo-datas-item .label { | |||
color: rgba(2, 107, 251, 0.54); | |||
margin-right: 4px; | |||
} | |||
.content .repo-datas-item .value { | |||
font-weight: bold; | |||
} | |||
.item-bottom { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
border-top: 1px solid rgba(157, 197, 226, 0.2); | |||
margin-top: 10px; | |||
padding-top: 10px; | |||
font-size: 12px; | |||
color: rgba(16, 16, 16, 0.6); | |||
} | |||
.item-bottom .contributors { | |||
display: flex; | |||
align-items: center; | |||
} | |||
.item-bottom .contributors-avatar { | |||
display: flex; | |||
align-items: center; | |||
margin-left: 16px; | |||
.avatar-c { | |||
img[src=""], | |||
img:not([src]) { | |||
// opacity: 0; | |||
} | |||
} | |||
} | |||
.item-bottom .avatar { | |||
display: block; | |||
width: 25px; | |||
height: 25px; | |||
margin-left: -6px; | |||
border-radius: 100%; | |||
border: 1px solid white; | |||
font-size: 16px; | |||
line-height: 24px; | |||
text-align: center; | |||
color: white; | |||
background-color: white; | |||
font-weight: bold; | |||
} | |||
</style> |
@@ -0,0 +1,161 @@ | |||
<template> | |||
<div class="list-container"> | |||
<div style="min-height:540px;" v-loading="loading"> | |||
<div class="repos-item-container" v-for="(item, index) in list" :key="item.ID"> | |||
<ReposItem :data="item" :topic="topic"></ReposItem> | |||
</div> | |||
<div v-show="(!list.length && !loading)" class="repos-no-data">{{ $t('repos.noReposfound') }}</div> | |||
</div> | |||
<div class="center"> | |||
<el-pagination ref="paginationRef" background @current-change="currentChange" @size-change="sizeChange" | |||
:current-page.sync="iPage" :page-sizes="iPageSizes" :page-size.sync="iPageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import ReposItem from '../components/ReposItem.vue'; | |||
import { getReposListData } from '~/apis/modules/repos'; | |||
import LetterAvatar from '~/utils/letteravatar'; | |||
export default { | |||
name: "ReposList", | |||
props: { | |||
q: { type: String, default: '' }, | |||
sort: { type: String, default: 'mostpopular' }, | |||
topic: { type: String, default: '' }, | |||
page: { type: Number, default: 1 }, | |||
pageSize: { type: Number, default: 15 }, | |||
pageSizes: { type: Array, default: () => [15, 30, 50] } | |||
}, | |||
components: { ReposItem }, | |||
data() { | |||
return { | |||
loading: false, | |||
list: [], | |||
iPageSizes: [15, 30, 50], | |||
iPageSize: 15, | |||
iPage: 1, | |||
total: 0, | |||
}; | |||
}, | |||
methods: { | |||
getListData() { | |||
this.loading = true; | |||
getReposListData({ | |||
q: this.q || '', | |||
topic: this.topic || '', | |||
sort: this.sort || 'mostpopular', | |||
pageSize: this.iPageSize || 15, | |||
page: this.iPage || 1, | |||
}).then(res => { | |||
res = res.data; | |||
this.loading = false; | |||
if (res.Code == 0) { | |||
const list = res.Data.Repos || []; | |||
this.list = list.map((item) => { | |||
item.Contributors = (item.Contributors || []).map((_item) => { | |||
return { | |||
..._item, | |||
bgColor: this.randomColor(_item.Email[0].toLocaleUpperCase()), | |||
} | |||
}); | |||
const contributors = item.Contributors || []; | |||
return { | |||
...item, | |||
NameShow: this.handlerSearchStr(item.Alias, this.q), | |||
DescriptionShow: this.handlerSearchStr(item.Description, this.q), | |||
TopicsShow: (item.Topics || []).map((_item) => { | |||
return { | |||
topic: _item, | |||
topicShow: this.handlerSearchStr(_item, this.q) | |||
} | |||
}), | |||
} | |||
}); | |||
this.total = res.Data.Total; | |||
this.iPage = this.iPage; | |||
this.iPageSize = this.iPageSize; | |||
this.$nextTick(() => { | |||
LetterAvatar.transform(); | |||
}); | |||
} else { | |||
this.list = []; | |||
this.total = 0; | |||
this.iPage = this.iPage; | |||
this.iPageSize = this.iPageSize; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
this.list = []; | |||
this.total = 0; | |||
this.iPage = this.iPage; | |||
this.iPageSize = this.iPageSize; | |||
}); | |||
}, | |||
search() { | |||
this.getListData(); | |||
}, | |||
currentChange(page) { | |||
this.iPage = page; | |||
this.$emit('current-change', { | |||
page: this.iPage, | |||
pageSize: this.iPageSize, | |||
}); | |||
}, | |||
sizeChange(pageSize) { | |||
this.iPageSize = pageSize; | |||
this.$emit('size-change', { | |||
page: this.iPage, | |||
pageSize: this.iPageSize, | |||
}); | |||
}, | |||
handlerSearchStr(oStr, searchKey) { | |||
if (!searchKey) return oStr; | |||
return oStr.replace(new RegExp(`(${searchKey})`, 'ig'), `<font color="red">$1</font>`); | |||
}, | |||
randomColor(t) { | |||
const tIndex = t.charCodeAt(0); | |||
const colorList = ["#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", | |||
"#2c3e50", "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"]; | |||
return colorList[tIndex % colorList.length]; | |||
} | |||
}, | |||
watch: { | |||
page: { | |||
handler(val) { | |||
this.iPage = val; | |||
}, | |||
immediate: true, | |||
}, | |||
pageSize: { | |||
handler(val) { | |||
this.iPageSize = val; | |||
}, | |||
immediate: true, | |||
} | |||
}, | |||
mounted() { }, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.list-container { | |||
margin-left: 12px; | |||
margin-right: 12px; | |||
} | |||
.center { | |||
text-align: center; | |||
} | |||
.repos-no-data { | |||
height: 60px; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
} | |||
</style> |
@@ -0,0 +1,255 @@ | |||
<template> | |||
<div> | |||
<div class="_repo_search"> | |||
<div class="_repo_search_input_c"> | |||
<div class="_repo_search_input"> | |||
<input type="text" v-model="searchInputValue" :placeholder="$t('repos.searchRepositories')" autocomplete="off" | |||
@keyup.enter="search" /> | |||
</div> | |||
<div class="_repo_search_btn" @click="search"> | |||
<svg xmlns="http://www.w3.org/2000/svg" | |||
class="styles__StyledSVGIconPathComponent-sc-16fsqc8-0 kdvdTY svg-icon-path-icon fill" viewBox="0 0 32 32" | |||
width="24" height="24"> | |||
<defs data-reactroot=""> | |||
<linearGradient id="ilac9fwnydq3lcx1,1,rs,1,f0dwf0xj,ezu9f0dw,f000,00lwrsktrs,rs1bhv8urs" x1="0" x2="100%" | |||
y1="0" y2="0" | |||
gradientTransform="matrix(-0.7069999999999999, -0.707, 0.707, -0.7069999999999999, 16, 38.624)" | |||
gradientUnits="userSpaceOnUse"> | |||
<stop stop-color="#c9ffbf" stop-opacity="1" offset="0"></stop> | |||
<stop stop-color="#0ca451" stop-opacity="1" offset="1"></stop> | |||
</linearGradient> | |||
</defs> | |||
<g> | |||
<path fill="url(#ilac9fwnydq3lcx1,1,rs,1,f0dwf0xj,ezu9f0dw,f000,00lwrsktrs,rs1bhv8urs)" | |||
d="M14.667 2.667c6.624 0 12 5.376 12 12s-5.376 12-12 12-12-5.376-12-12 5.376-12 12-12zM14.667 24c5.156 0 9.333-4.177 9.333-9.333 0-5.157-4.177-9.333-9.333-9.333-5.157 0-9.333 4.176-9.333 9.333 0 5.156 4.176 9.333 9.333 9.333zM25.98 24.095l3.772 3.771-1.887 1.887-3.771-3.772 1.885-1.885z"> | |||
</path> | |||
</g> | |||
</svg> | |||
<span style="margin-left:10px;">{{ $t('repos.search') }}</span> | |||
</div> | |||
</div> | |||
<div class="_repo_search_label_c"> | |||
<div class="_repo_search_label"> | |||
<a v-if="type == 'square'" :href="`/explore/repos?q=${searchInputValue.trim()}&topic=${item.v}&sort=${sort}`" | |||
:style="{ backgroundColor: topicColors[index % topicColors.length] }" v-for="(item, index) in topics" | |||
:key="index">{{ item.v }}</a> | |||
<a v-if="type == 'search'" href="javascript:;" @click="changeTopic({ k: '', v: '' })" | |||
style="font-weight:bold;" | |||
:style="{ backgroundColor: selectTopic == '' ? selectedColor : defaultColor, color: selectTopic == '' ? 'white' : '#40485b' }">{{ | |||
$t('repos.allFields') | |||
}}</a> | |||
<a v-if="type == 'search'" href="javascript:;" @click="changeTopic(item)" | |||
:style="{ backgroundColor: selectTopic.toLocaleLowerCase() == item.k ? selectedColor : defaultColor, color: selectTopic.toLocaleLowerCase() == item.k ? 'white' : '#40485b' }" | |||
v-for="(item, index) in topics" :key="index">{{ item.v }}</a> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { getPromoteData } from '~/apis/modules/common'; | |||
const COLOR_LIST = [ | |||
'rgb(255, 104, 104)', | |||
'rgb(22, 132, 252)', | |||
'rgb(2, 202, 253)', | |||
'rgb(164, 145, 215)', | |||
'rgb(232, 64, 247)', | |||
'rgb(245, 182, 110)', | |||
'rgb(54, 187, 166)', | |||
'rgb(123, 50, 178)' | |||
]; | |||
export default { | |||
name: "SearchBar", | |||
props: { | |||
type: { type: String, default: 'square' }, // square|search | |||
searchValue: { type: String, default: '' }, | |||
topic: { type: String, default: '' }, | |||
sort: { type: String, default: '' }, | |||
static: { type: Boolean, default: false }, | |||
staticTopicsData: { type: String, default: '[]' }, | |||
}, | |||
components: {}, | |||
data() { | |||
return { | |||
searchInputValue: '', | |||
topicColors: COLOR_LIST, | |||
defaultColor: '#F6F6F6', | |||
selectedColor: '', | |||
selectTopic: '', | |||
topicOri: [], | |||
topics: [], | |||
}; | |||
}, | |||
methods: { | |||
setDefaultSearch(params) { | |||
this.searchInputValue = params.q || ''; | |||
this.selectTopic = params.topic || ''; | |||
this.selectTopic && this.changeTopic({ | |||
k: this.selectTopic.toLocaleLowerCase(), | |||
v: this.selectTopic, | |||
}, true); | |||
}, | |||
changeTopic(topicItem, noSearch) { | |||
const index_ori = this.topicOri.findIndex((item) => { | |||
return item.k == this.selectTopic.toLocaleLowerCase(); | |||
}); | |||
if (index_ori < 0 && this.selectTopic) { | |||
const index = this.topics.findIndex((item) => { | |||
return item.k == this.selectTopic.toLocaleLowerCase(); | |||
}); | |||
if (index > -1) { | |||
this.topics.splice(index, 1); | |||
} | |||
} | |||
this.selectTopic = topicItem.v; | |||
if (this.selectTopic && this.topics.indexOf(this.selectTopic) < 0) { | |||
const index = this.topics.findIndex(item => { | |||
return item.k == this.selectTopic.toLocaleLowerCase(); | |||
}) | |||
if (index < 0) { | |||
this.topics.push({ | |||
k: this.selectTopic.toLocaleLowerCase(), | |||
v: this.selectTopic, | |||
}); | |||
} | |||
} | |||
!noSearch && this.search(); | |||
}, | |||
handlerTopicsData(data) { | |||
try { | |||
const topicsData = JSON.parse(data); | |||
const topics = topicsData.map((item) => { | |||
return { | |||
k: item.trim().toLocaleLowerCase(), | |||
v: item.trim(), | |||
} | |||
}); | |||
this.topicOri = JSON.parse(JSON.stringify(topics)); | |||
this.topics = topics; | |||
const selectTopic_key = this.selectTopic.toLocaleLowerCase(); | |||
if (selectTopic_key) { | |||
const index = this.topics.findIndex((item) => { | |||
return item.k == selectTopic_key; | |||
}); | |||
if (index < 0) { | |||
this.topics.push({ | |||
k: this.selectTopic.toLocaleLowerCase(), | |||
v: this.selectTopic, | |||
}); | |||
} | |||
} | |||
} catch (err) { | |||
console.log(err); | |||
} | |||
}, | |||
search() { | |||
this.searchInputValue = this.searchInputValue.trim(); | |||
if (this.type == 'square') { | |||
window.location.href = `/explore/repos?q=${this.searchInputValue}&sort=${this.sort}&topic=${this.selectTopic}`; | |||
} else { | |||
this.$emit('change', { | |||
q: this.searchInputValue, | |||
topic: this.selectTopic, | |||
}); | |||
} | |||
} | |||
}, | |||
mounted() { | |||
if (this.static) { | |||
try { | |||
this.handlerTopicsData(this.staticTopicsData); | |||
} catch (err) { | |||
console.log(err); | |||
} | |||
} else { | |||
getPromoteData('/repos/recommend_topics').then(res => { | |||
const data = res.data; | |||
this.handlerTopicsData(data); | |||
}).catch(err => { | |||
console.log(err); | |||
this.handlerTopicsData('[]'); | |||
}); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
._repo_search { | |||
margin: 54px 0; | |||
} | |||
._repo_search_input_c { | |||
margin: 0 0 35px; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
} | |||
._repo_search_input { | |||
display: flex; | |||
align-items: center; | |||
width: 437px; | |||
height: 41px; | |||
border-color: rgba(47, 9, 69, 0.64); | |||
border-width: 1px; | |||
border-style: solid; | |||
color: rgba(16, 16, 16, 0.5); | |||
border-radius: 20px; | |||
font-size: 14px; | |||
padding: 20px; | |||
text-align: left; | |||
line-height: 20px; | |||
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736765e-17%2C%20-0.9999999999999999%2C%200.008802475794500678%2C%206.123233995736765e-17%2C%201%2C%201.003)%22%3E%3Cstop%20stop-color%3D%22%23eeeade%22%20stop-opacity%3D%220.2%22%20offset%3D%220.76%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ece0e9%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
} | |||
._repo_search_input input { | |||
width: 100%; | |||
height: 30px; | |||
border: none; | |||
background: transparent; | |||
outline: none; | |||
} | |||
._repo_search_btn { | |||
margin-left: 10px; | |||
width: 110px; | |||
height: 40px; | |||
border-style: none; | |||
border-color: unset; | |||
color: rgb(255, 255, 255); | |||
border-radius: 21px; | |||
font-size: 14px; | |||
text-align: center; | |||
font-weight: normal; | |||
font-style: normal; | |||
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.17728531855955676%2C%206.123233995736766e-17%2C%201%2C%200)%22%3E%3Cstop%20stop-color%3D%22%232f0945%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23341a7b%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
cursor: pointer; | |||
} | |||
._repo_search_label_c { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
._repo_search_label { | |||
display: flex; | |||
justify-content: center; | |||
flex-wrap: wrap; | |||
max-width: 1200px; | |||
} | |||
._repo_search_label a { | |||
background-color: rgb(2, 202, 253); | |||
color: rgb(255, 255, 255); | |||
border-radius: 5px; | |||
margin: 0 5px 10px 5px; | |||
padding: 5px 10px; | |||
cursor: pointer; | |||
font-size: 12px; | |||
} | |||
</style> |
@@ -0,0 +1,363 @@ | |||
<template> | |||
<div class="ui _repo_container_bg"> | |||
<div class="ui container _repo_container _content_container"> | |||
<div class="_repo_top_left"> | |||
<a :href="bannerData[0] ? bannerData[0].url : ''"> | |||
<div class="_repo_top_left_img"> | |||
<img :src="bannerData[0] ? bannerData[0].src : ''"> | |||
</div> | |||
</a> | |||
</div> | |||
<div class="_repo_top_middle"> | |||
<div class="_repo_top_middle_header"> | |||
<div class="_repo_top_mid_item" :class="(tabIndex == index) ? '_foucs' : ''" v-for="(item, index) in tabs" | |||
:key="index" @click="changeTab(item, index)"> | |||
<i :class="item.icon"></i> | |||
<span>{{ item.label }}</span> | |||
</div> | |||
</div> | |||
<div class="_repo_top_middle_content"> | |||
<div class="_repo_top_mid_repo_list"> | |||
<div class="swiper-wrapper" id="_repo_top_mid_repo"></div> | |||
<div class="swiper-pagination _repo_top-swiper-pagination"></div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="_repo_top_right"> | |||
<a :href="bannerData[1] ? bannerData[1].url : ''"> | |||
<div class="_repo_top_right_img"> | |||
<img :src="bannerData[1] ? bannerData[1].src : ''"> | |||
</div> | |||
</a> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { getPromoteData } from '~/apis/modules/common'; | |||
import { getReposSquareTabData } from '~/apis/modules/repos'; | |||
import LetterAvatar from '~/utils/letteravatar'; | |||
export default { | |||
name: "SquareTop", | |||
props: { | |||
static: { type: Boolean, default: false }, | |||
staticSwiperData: { type: Array, default: () => [] }, | |||
staticBannerData: { type: String, default: '[]' }, | |||
}, | |||
components: {}, | |||
data() { | |||
return { | |||
swiperHandler: null, | |||
tabIndex: 0, | |||
tabs: [{ | |||
key: 'preferred', | |||
icon: 'ri-fire-line', | |||
label: this.$t('repos.preferred'), | |||
}, { | |||
key: 'incubation', | |||
icon: 'ri-award-line', | |||
label: this.$t('repos.openIIncubation'), | |||
}, { | |||
key: 'hot-paper', | |||
icon: 'ri-file-damage-line', | |||
label: this.$t('repos.hotPapers'), | |||
}], | |||
bannerData: [], | |||
}; | |||
}, | |||
methods: { | |||
initSwiper() { | |||
this.swiperHandler = new Swiper("._repo_top_mid_repo_list", { | |||
slidesPerView: 1, | |||
spaceBetween: 20, | |||
pagination: { | |||
el: "._repo_top-swiper-pagination", | |||
clickable: true, | |||
}, | |||
autoplay: { | |||
delay: 4500, | |||
disableOnInteraction: false, | |||
}, | |||
breakpoints: { | |||
768: { | |||
slidesPerView: 2, | |||
}, | |||
1024: { | |||
slidesPerView: 2, | |||
}, | |||
1200: { | |||
slidesPerView: 3, | |||
}, | |||
}, | |||
}); | |||
}, | |||
renderSwiper(data) { | |||
const swiperEl = document.getElementById("_repo_top_mid_repo"); | |||
let html = ''; | |||
const width = swiperEl.parentNode.clientWidth; | |||
for (let i = 0, iLen = data.length; i < iLen; i++) { | |||
html += `<div class="swiper-slide">`; | |||
for (let j = i; j < i + 2; j++) { | |||
let dataJ = data[j]; | |||
if (dataJ === undefined) break; | |||
html += `<div class="_repo_sw_card"><div style="display:flex;"> | |||
${dataJ["RelAvatarLink"] ? `<img style="border-radius:100%;width:35px;height:35px;margin-bottom:0.6em;" class="left floated mini ui image" src="${dataJ["RelAvatarLink"]}">` | |||
: `<img style="border-radius:100%;width:35px;height:35px;margin-bottom:0.6em;" class="left floated mini ui image" avatar="${dataJ["OwnerName"]}">`} | |||
<span class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;height:35px;line-height:35px;" href="javascript:;" title="${dataJ["Alias"]}">${dataJ["Alias"]}</span> | |||
</div><div class="_repo_sw_card_descr _repo_nowrap_line_3" title="${dataJ.Description}">${dataJ.Description}</div> | |||
<a href="/${dataJ.OwnerName}/${dataJ.Name}" style="position:absolute;width:100%;height:100%;top:0;left:0;"></a> | |||
</div>`; | |||
} | |||
html += `</div>`; | |||
i++; | |||
} | |||
this.swiperHandler.removeAllSlides(); | |||
swiperEl.innerHTML = html; | |||
this.swiperHandler.updateSlides(); | |||
this.swiperHandler.updateProgress(); | |||
LetterAvatar.transform(); | |||
}, | |||
getBannerData() { | |||
getPromoteData('/repos/square_banner').then(res => { | |||
const data = res.data; | |||
try { | |||
const list = JSON.parse(data); | |||
this.bannerData = list; | |||
} catch (err) { | |||
console.log(err); | |||
} | |||
}).catch(err => { | |||
this.bannerData = []; | |||
console.log(err); | |||
}); | |||
}, | |||
getTabData() { | |||
getReposSquareTabData(this.tabs[this.tabIndex].key).then(res => { | |||
res = res.data; | |||
if (res.Code == 0) { | |||
const data = res.Data.Repos || []; | |||
this.renderSwiper(data); | |||
} else { | |||
this.renderSwiper([]); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.renderSwiper([]); | |||
}); | |||
}, | |||
changeTab(item, index) { | |||
this.tabIndex = index; | |||
this.getTabData(); | |||
}, | |||
}, | |||
mounted() { | |||
this.initSwiper(); | |||
if (this.static) { | |||
try { | |||
this.bannerData = JSON.parse(this.staticBannerData); | |||
} catch (err) { | |||
console.log(err); | |||
} | |||
this.renderSwiper(this.staticSwiperData); | |||
} else { | |||
this.getBannerData(); | |||
this.getTabData(); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
._repo_container_bg { | |||
width: 100%; | |||
height: 450px; | |||
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.4999999999999999%2C%20-0.8660000000000001%2C%200.07722000385802469%2C%200.4999999999999999%2C%20-0.183%2C%200.683)%22%3E%3Cstop%20stop-color%3D%22%239aceec%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23eeeeee%22%20stop-opacity%3D%221%22%20offset%3D%220.99%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
position: relative; | |||
} | |||
._content_container { | |||
display: flex !important; | |||
margin: 0 auto !important; | |||
height: 100% !important; | |||
} | |||
._repo_top_left { | |||
width: 243px; | |||
height: 100%; | |||
position: relative; | |||
} | |||
._repo_top_left_img { | |||
width: 100%; | |||
height: 346px; | |||
position: absolute; | |||
bottom: 0; | |||
border-top-left-radius: 10px; | |||
overflow: hidden; | |||
} | |||
._repo_top_left_img img { | |||
height: 100%; | |||
width: 100%; | |||
} | |||
._content_container img[src=""], | |||
img:not([src]) { | |||
opacity: 0; | |||
} | |||
._repo_top_right { | |||
width: 243px; | |||
height: 100%; | |||
position: relative; | |||
} | |||
._repo_top_right_img { | |||
width: 100%; | |||
height: 346px; | |||
position: absolute; | |||
bottom: 0; | |||
border-top-right-radius: 10px; | |||
overflow: hidden; | |||
} | |||
._repo_top_right_img img { | |||
height: 100%; | |||
width: 100%; | |||
} | |||
._repo_top_middle { | |||
flex: 1; | |||
height: 100%; | |||
position: relative; | |||
} | |||
._repo_top_middle_header { | |||
width: 100%; | |||
height: 42px; | |||
position: absolute; | |||
bottom: 346px; | |||
display: flex; | |||
align-items: center; | |||
background: raba(0, 0, 255, 0.3); | |||
padding: 0 20px; | |||
} | |||
._repo_top_mid_item { | |||
padding: 0px 16px; | |||
font-size: 18px; | |||
color: rgba(47, 9, 69, 0.64); | |||
height: 100%; | |||
display: flex; | |||
align-items: center; | |||
cursor: pointer; | |||
box-sizing: border-box; | |||
border-bottom: 3px solid transparent; | |||
} | |||
._repo_top_mid_item i { | |||
margin-right: 5px; | |||
} | |||
._repo_top_mid_item._foucs { | |||
background-color: rgba(255, 255, 255, 0.3); | |||
color: rgb(16, 16, 16); | |||
cursor: default; | |||
border-bottom: 3px solid rgba(16, 16, 16, 0.8); | |||
} | |||
._repo_top_middle_content { | |||
width: 100%; | |||
height: 346px; | |||
position: absolute; | |||
bottom: 0; | |||
background: rgba(255, 255, 255, 0.3); | |||
padding: 20px 20px; | |||
overflow: hidden; | |||
} | |||
._repo_top_mid_repo_list { | |||
overflow: hidden; | |||
} | |||
/deep/._repo_sw_card { | |||
height: 128px; | |||
border-color: rgb(255, 255, 255); | |||
border-width: 1px; | |||
border-style: solid; | |||
font-size: 14px; | |||
padding: 12px; | |||
background: rgba(255, 255, 255, 0.6); | |||
margin-bottom: 20px; | |||
box-sizing: border-box; | |||
position: relative; | |||
} | |||
/deep/._repo_nowrap { | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
/deep/._repo_nowrap_line_3 { | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
word-break: break-all; | |||
font-size: 12px; | |||
color: rgb(136, 136, 136); | |||
display: -webkit-box; | |||
-webkit-box-orient: vertical; | |||
-webkit-line-clamp: 3; | |||
max-height: 65px; | |||
white-space: break-spaces; | |||
} | |||
/deep/._repo_sw_card a { | |||
color: inherit; | |||
} | |||
/deep/._repo_sw_card_title { | |||
font-weight: 700; | |||
font-size: 14px; | |||
color: rgba(26, 40, 51, 1); | |||
margin-bottom: 10px; | |||
} | |||
/deep/._repo_sw_card_descr { | |||
font-size: 12px; | |||
color: rgba(80, 85, 89, 1); | |||
margin-bottom: 10px; | |||
min-height: 42px; | |||
} | |||
/deep/._repo_sw_card_label { | |||
color: rgb(26, 40, 51); | |||
font-size: 14px; | |||
display: flex; | |||
width: 100%; | |||
position: relative; | |||
} | |||
/deep/._repo_sw_card_label span { | |||
border-radius: 4px; | |||
background: rgba(232, 232, 232, 0.6); | |||
padding: 0px 6px 2px; | |||
margin-right: 10px; | |||
max-width: 50%; | |||
} | |||
/deep/._repo_top_mid_repo_list .swiper-pagination-bullet { | |||
width: 8px; | |||
height: 8px; | |||
border-radius: 100%; | |||
background: #76cbed; | |||
} | |||
/deep/._repo_top_mid_repo_list .swiper-pagination-bullet-active { | |||
width: 40px; | |||
border-radius: 4px; | |||
} | |||
</style> |
@@ -0,0 +1,123 @@ | |||
<template> | |||
<div> | |||
<div class="ui container"> | |||
<SearchBar ref="searchBarRef" type="search" :static="true" :staticTopicsData="staticSquareTopics" | |||
:sort="reposListSortType" :searchValue="reposListQurey" :topic="reposListTopic" @change="searchBarChange"> | |||
</SearchBar> | |||
</div> | |||
<div class="ui container"> | |||
<div class="ui grid"> | |||
<div class="computer only ui two wide computer column"> | |||
<ReposFilters ref="reposFiltersRef" @change="filtersChange"></ReposFilters> | |||
</div> | |||
<div class="ui sixteen wide mobile twelve wide tablet ten wide computer column"> | |||
<ReposList ref="reposListRef" :sort="reposListSortType" :q="reposListQurey" :topic="reposListTopic" | |||
:page="page" :pageSize="pageSize" :pageSizes="pageSizes" @current-change="currentChange" | |||
@size-change="sizeChange"> | |||
</ReposList> | |||
</div> | |||
<div class="computer only ui four wide computer column"> | |||
<div> | |||
<ActiveUsers></ActiveUsers> | |||
</div> | |||
<div class="active-org-c"> | |||
<ActiveOrgs></ActiveOrgs> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import SearchBar from '../components/SearchBar.vue'; | |||
import ReposFilters from '../components/ReposFilters.vue'; | |||
import ReposList from '../components/ReposList.vue'; | |||
import ActiveUsers from '../components/ActiveUsers.vue'; | |||
import ActiveOrgs from '../components/ActiveOrgs.vue'; | |||
import { getUrlSearchParams } from '~/utils'; | |||
const staticSquareTopics = JSON.stringify(window.staticSquareTopics || []); | |||
export default { | |||
data() { | |||
return { | |||
reposListSortType: 'mostpopular', | |||
reposListQurey: '', | |||
reposListTopic: '', | |||
page: 1, | |||
pageSize: 15, | |||
pageSizes: [15, 30, 50], | |||
staticSquareTopics: staticSquareTopics, | |||
}; | |||
}, | |||
components: { | |||
SearchBar, | |||
ReposFilters, | |||
ReposList, | |||
ActiveUsers, | |||
ActiveOrgs, | |||
}, | |||
methods: { | |||
filtersChange(condition) { | |||
this.page = 1; | |||
this.reposListSortType = condition.key; | |||
this.search(); | |||
}, | |||
searchBarChange(params) { | |||
this.page = 1; | |||
this.reposListQurey = params.q || ''; | |||
this.reposListTopic = params.topic || ''; | |||
this.search(); | |||
}, | |||
currentChange({ page, pageSize }) { | |||
this.page = page; | |||
this.search(); | |||
}, | |||
sizeChange({ page, pageSize }) { | |||
this.page = 1; | |||
this.pageSize = pageSize; | |||
this.search(); | |||
}, | |||
search() { | |||
window.location.href = `/explore/repos?q=${this.reposListQurey.trim()}&sort=${this.reposListSortType}&topic=${this.reposListTopic.trim()}&page=${this.page}&pageSize=${this.pageSize}`; | |||
} | |||
}, | |||
beforeMount() { | |||
const urlParams = getUrlSearchParams(); | |||
this.reposListQurey = urlParams.q || ''; | |||
this.reposListTopic = urlParams.topic || ''; | |||
this.reposListSortType = urlParams.sort || 'mostpopular'; | |||
this.page = Number(urlParams.page) || 1; | |||
this.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15; | |||
}, | |||
mounted() { | |||
this.$nextTick(() => { | |||
this.$refs.reposFiltersRef.setDefaultFilter(this.reposListSortType); | |||
this.$refs.searchBarRef.setDefaultSearch({ | |||
q: this.reposListQurey, | |||
topic: this.reposListTopic, | |||
}); | |||
this.$refs.reposListRef.search(); | |||
}); | |||
window.addEventListener('pageshow', function (e) { | |||
if (e.persisted) { | |||
window.location.reload(); | |||
} | |||
}, false); | |||
}, | |||
beforeDestroy() { }, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.recommend-repos-c { | |||
margin: 0 0 54px; | |||
} | |||
.active-org-c { | |||
margin-top: 32px; | |||
} | |||
</style> |
@@ -0,0 +1,17 @@ | |||
import Vue from 'vue'; | |||
import ElementUI from 'element-ui'; | |||
import 'element-ui/lib/theme-chalk/index.css'; | |||
import localeEn from 'element-ui/lib/locale/lang/en'; | |||
import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||
import { i18n, lang } from '~/langs'; | |||
import App from './index.vue'; | |||
Vue.use(ElementUI, { | |||
locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
size: 'small', | |||
}); | |||
new Vue({ | |||
i18n, | |||
render: (h) => h(App), | |||
}).$mount('#__vue-root'); |
@@ -0,0 +1,146 @@ | |||
<template> | |||
<div> | |||
<div> | |||
<SquareTop :static="true" :staticBannerData="staticSquareBanners" :staticSwiperData="staticSquarePreferredRepos"> | |||
</SquareTop> | |||
</div> | |||
<div class="ui container"> | |||
<SearchBar :static="true" :staticTopicsData="staticSquareTopics" ref="searchBarRef" type="square" :sort="``" | |||
:searchValue="reposListQurey" :topic="``" @change="searchBarChange"></SearchBar> | |||
</div> | |||
<div class="recommend-repos-c"> | |||
<RecommendRepos :static="true" :staticSwiperData="staticSquareRecommendRepos"></RecommendRepos> | |||
<a name="search"></a> | |||
</div> | |||
<div class="ui container"> | |||
<div class="ui grid"> | |||
<div class="computer only ui two wide computer column"> | |||
<ReposFilters ref="reposFiltersRef" @change="filtersChange"></ReposFilters> | |||
</div> | |||
<div class="ui sixteen wide mobile twelve wide tablet ten wide computer column"> | |||
<ReposList ref="reposListRef" :sort="reposListSortType" :q="reposListQurey" :topic="reposListTopic" | |||
:page="page" :pageSize="pageSize" :pageSizes="pageSizes" @current-change="currentChange" | |||
@size-change="sizeChange"> | |||
</ReposList> | |||
</div> | |||
<div class="computer only ui four wide computer column"> | |||
<div> | |||
<ActiveUsers></ActiveUsers> | |||
</div> | |||
<div class="active-org-c"> | |||
<ActiveOrgs></ActiveOrgs> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import SquareTop from '../components/SquareTop.vue'; | |||
import SearchBar from '../components/SearchBar.vue'; | |||
import RecommendRepos from '../components/RecommendRepos.vue'; | |||
import ReposFilters from '../components/ReposFilters.vue'; | |||
import ReposList from '../components/ReposList.vue'; | |||
import ActiveUsers from '../components/ActiveUsers.vue'; | |||
import ActiveOrgs from '../components/ActiveOrgs.vue'; | |||
import { getUrlSearchParams } from '~/utils'; | |||
const staticSquareBanners = JSON.stringify(window.staticSquareBanners || []); | |||
const staticSquarePreferredRepos = window.staticSquarePreferredRepos || []; | |||
const staticSquareTopics = JSON.stringify(window.staticSquareTopics || []); | |||
const staticSquareRecommendRepos = window.staticSquareRecommendRepos || []; | |||
export default { | |||
data() { | |||
return { | |||
reposListSortType: 'mostpopular', | |||
reposListQurey: '', | |||
reposListTopic: '', | |||
page: 1, | |||
pageSize: 15, | |||
pageSizes: [15, 30, 50], | |||
staticSquareBanners: staticSquareBanners, | |||
staticSquarePreferredRepos: staticSquarePreferredRepos, | |||
staticSquareTopics: staticSquareTopics, | |||
staticSquareRecommendRepos: staticSquareRecommendRepos, | |||
}; | |||
}, | |||
components: { | |||
SquareTop, | |||
SearchBar, | |||
RecommendRepos, | |||
ReposFilters, | |||
ReposList, | |||
ActiveUsers, | |||
ActiveOrgs, | |||
}, | |||
methods: { | |||
filtersChange(condition) { | |||
this.page = 1; | |||
this.reposListSortType = condition.key; | |||
this.search(); | |||
}, | |||
searchBarChange(params) { | |||
this.page = 1; | |||
this.reposListQurey = params.q || ''; | |||
this.reposListTopic = params.topic || ''; | |||
this.search(); | |||
}, | |||
currentChange({ page, pageSize }) { | |||
this.page = page; | |||
this.search(); | |||
}, | |||
sizeChange({ page, pageSize }) { | |||
this.page = 1; | |||
this.pageSize = pageSize; | |||
this.search(); | |||
}, | |||
search() { | |||
window.location.href = `/explore/repos/square?q=${this.reposListQurey.trim()}&sort=${this.reposListSortType}&topic=${this.reposListTopic.trim()}&page=${this.page}&pageSize=${this.pageSize}`; | |||
} | |||
}, | |||
beforeMount() { | |||
const urlParams = getUrlSearchParams(); | |||
this.reposListQurey = urlParams.q || ''; | |||
this.reposListTopic = urlParams.topic || ''; | |||
this.reposListSortType = urlParams.sort || 'mostpopular'; | |||
this.page = Number(urlParams.page) || 1; | |||
this.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15; | |||
}, | |||
mounted() { | |||
this.$nextTick(() => { | |||
this.$refs.reposFiltersRef.setDefaultFilter(this.reposListSortType); | |||
this.$refs.searchBarRef.setDefaultSearch({ | |||
q: this.reposListQurey, | |||
topic: this.reposListTopic, | |||
}); | |||
const urlParams = getUrlSearchParams(); | |||
const page = Number(urlParams.page) || 1; | |||
const reposListSortType = urlParams.sort; | |||
if (page != 1 || reposListSortType) { | |||
window.location.href = '#search'; | |||
} | |||
this.$refs.reposListRef.search(); | |||
}); | |||
window.addEventListener('pageshow', function (e) { | |||
if (e.persisted) { | |||
window.location.reload(); | |||
} | |||
}, false); | |||
}, | |||
beforeDestroy() { }, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.recommend-repos-c { | |||
margin: 0 0 54px; | |||
} | |||
.active-org-c { | |||
margin-top: 32px; | |||
} | |||
</style> |
@@ -0,0 +1,17 @@ | |||
import Vue from 'vue'; | |||
import ElementUI from 'element-ui'; | |||
import 'element-ui/lib/theme-chalk/index.css'; | |||
import localeEn from 'element-ui/lib/locale/lang/en'; | |||
import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||
import { i18n, lang } from '~/langs'; | |||
import App from './index.vue'; | |||
Vue.use(ElementUI, { | |||
locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
size: 'small', | |||
}); | |||
new Vue({ | |||
i18n, | |||
render: (h) => h(App), | |||
}).$mount('#__vue-root'); |
@@ -27,7 +27,7 @@ export const transFileSize = (srcSize) => { | |||
srcSize = parseFloat(srcSize); | |||
const index = Math.floor(Math.log(srcSize) / Math.log(1024)); | |||
const size = (srcSize / Math.pow(1024, index)).toFixed(2); | |||
return size + ' ' + unitArr[index]; | |||
return size + ' ' + unitArr[index]; | |||
}; | |||
export const renderSpecStr = (spec, showPoint) => { | |||
@@ -39,3 +39,91 @@ export const renderSpecStr = (spec, showPoint) => { | |||
var specStr = `${ngpu}, CPU: ${spec.CpuCores}, ${gpuMemStr}${i18n.t('resourcesManagement.mem')}: ${spec.MemGiB}GB${sharedMemStr}${pointStr}`; | |||
return specStr; | |||
}; | |||
const Minute = 60; | |||
const Hour = 60 * Minute; | |||
const Day = 24 * Hour; | |||
const Week = 7 * Day; | |||
const Month = 30 * Day; | |||
const Year = 12 * Month; | |||
const computeTimeDiff = (diff) => { | |||
let diffStr = ''; | |||
switch (true) { | |||
case diff <= 0: | |||
diff = 0; | |||
diffStr = i18n.t('timeObj.now'); | |||
break; | |||
case diff < 2: | |||
diff = 0; | |||
diffStr = i18n.t('timeObj.1s'); | |||
break; | |||
case diff < 1 * Minute: | |||
diffStr = i18n.t('timeObj.seconds', { msg: Math.floor(diff) }); | |||
diff = 0; | |||
break; | |||
case diff < 2 * Minute: | |||
diff -= 1 * Minute; | |||
diffStr = i18n.t('timeObj.1m'); | |||
break; | |||
case diff < 1 * Hour: | |||
diffStr = i18n.t('timeObj.minutes', { msg: Math.floor(diff / Minute) }); | |||
diff -= diff / Minute * Minute; | |||
break; | |||
case diff < 2 * Hour: | |||
diff -= 1 * Hour; | |||
diffStr = i18n.t('timeObj.1h'); | |||
break; | |||
case diff < 1 * Day: | |||
diffStr = i18n.t('timeObj.hours', { msg: Math.floor(diff / Hour) }); | |||
diff -= diff / Hour * Hour; | |||
break; | |||
case diff < 2 * Day: | |||
diff -= 1 * Day; | |||
diffStr = i18n.t('timeObj.1d'); | |||
break; | |||
case diff < 1 * Week: | |||
diffStr = i18n.t('timeObj.days', { msg: Math.floor(diff / Day) }); | |||
diff -= diff / Day * Day; | |||
break; | |||
case diff < 2 * Week: | |||
diff -= 1 * Week; | |||
diffStr = i18n.t('timeObj.1w'); | |||
break; | |||
case diff < 1 * Month: | |||
diffStr = i18n.t('timeObj.weeks', { msg: Math.floor(diff / Week) }); | |||
diff -= diff / Week * Week; | |||
break; | |||
case diff < 2 * Month: | |||
diff -= 1 * Month; | |||
diffStr = i18n.t('timeObj.1mon'); | |||
break; | |||
case diff < 1 * Year: | |||
diffStr = i18n.t('timeObj.months', { msg: Math.floor(diff / Month) }); | |||
diff -= diff / Month * Month; | |||
break; | |||
case diff < 2 * Year: | |||
diff -= 1 * Year; | |||
diffStr = i18n.t('timeObj.1y'); | |||
break; | |||
default: | |||
diffStr = i18n.t('timeObj.years', { msg: Math.floor(diff / Year) }); | |||
diff -= (diff / Year) * Year; | |||
break; | |||
} | |||
return { diff, diffStr }; | |||
}; | |||
export const timeSinceUnix = (then, now) => { | |||
let lbl = 'timeObj.ago'; | |||
let diff = now - then; | |||
if (then > now) { | |||
lbl = 'timeObj.from_now'; | |||
diff = then - now; | |||
} | |||
if (diff <= 0) { | |||
return i18n.t('timeObj.now'); | |||
} | |||
const out = computeTimeDiff(diff); | |||
return i18n.t(lbl, { msg: out.diffStr }); | |||
}; |
@@ -0,0 +1,75 @@ | |||
function LetterAvatar(name, size, color) { | |||
name = name || ""; | |||
size = size || 60; | |||
var colours = [ | |||
"#1abc9c", | |||
"#2ecc71", | |||
"#3498db", | |||
"#9b59b6", | |||
"#34495e", | |||
"#16a085", | |||
"#27ae60", | |||
"#2980b9", | |||
"#8e44ad", | |||
"#2c3e50", | |||
"#f1c40f", | |||
"#e67e22", | |||
"#e74c3c", | |||
"#00bcd4", | |||
"#95a5a6", | |||
"#f39c12", | |||
"#d35400", | |||
"#c0392b", | |||
"#bdc3c7", | |||
"#7f8c8d", | |||
], | |||
nameSplit = String(name).split(" "), | |||
initials, | |||
charIndex, | |||
colourIndex, | |||
canvas, | |||
context, | |||
dataURI; | |||
if (nameSplit.length == 1) { | |||
initials = nameSplit[0] ? nameSplit[0].charAt(0) : "?"; | |||
} else { | |||
initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0); | |||
} | |||
let initials1 = initials.toUpperCase(); | |||
if (window.devicePixelRatio) { | |||
size = size * window.devicePixelRatio; | |||
} | |||
charIndex = (initials == "?" ? 72 : initials.charCodeAt(0)) - 64; | |||
colourIndex = charIndex % 20; | |||
canvas = document.createElement("canvas"); | |||
canvas.width = size; | |||
canvas.height = size; | |||
context = canvas.getContext("2d"); | |||
context.fillStyle = color ? color : colours[colourIndex - 1]; | |||
context.fillRect(0, 0, canvas.width, canvas.height); | |||
context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'"; | |||
context.textAlign = "center"; | |||
context.fillStyle = "#FFF"; | |||
context.fillText(initials1, size / 2, size / 1.5); | |||
dataURI = canvas.toDataURL(); | |||
canvas = null; | |||
return dataURI; | |||
} | |||
LetterAvatar.transform = function () { | |||
Array.prototype.forEach.call( | |||
document.querySelectorAll("img[avatar]"), | |||
function (img, name, color) { | |||
name = img.getAttribute("avatar"); | |||
color = img.getAttribute("color"); | |||
img.src = LetterAvatar(name, img.getAttribute("width"), color); | |||
img.removeAttribute("avatar"); | |||
img.setAttribute("alt", name); | |||
} | |||
); | |||
}; | |||
export default LetterAvatar; |
@@ -22,8 +22,8 @@ for (const path of glob('web_src/less/themes/*.less')) { | |||
const standalone = {}; | |||
const stadalonePaths = [ | |||
...glob('web_src/js/standalone/*.js'), | |||
...glob('web_src/less/standalone/*.less'), | |||
...glob('web_src/js/standalone/**/*.js'), | |||
...glob('web_src/less/standalone/**/*.less'), | |||
]; | |||
for (const path of stadalonePaths) { | |||
standalone[parse(path).name] = [path]; | |||
@@ -22,8 +22,8 @@ for (const path of glob('web_src/less/themes/*.less')) { | |||
const standalone = {}; | |||
const stadalonePaths = [ | |||
...glob('web_src/js/standalone/*.js'), | |||
...glob('web_src/less/standalone/*.less'), | |||
...glob('web_src/js/standalone/**/*.js'), | |||
...glob('web_src/less/standalone/**/*.less'), | |||
]; | |||
for (const path of stadalonePaths) { | |||
standalone[parse(path).name] = [path]; | |||