Browse Source

Sort / Move project boards (#14634)

Sort Project board (#14533)
tags/v1.15.0-dev
Romain GitHub 4 years ago
parent
commit
f0c4188745
6 changed files with 73 additions and 16 deletions
  1. +29
    -5
      models/project_board.go
  2. +4
    -4
      modules/forms/repo_form.go
  3. +8
    -4
      routers/repo/projects.go
  4. +2
    -2
      routers/routes/web.go
  5. +1
    -1
      templates/repo/projects/view.tmpl
  6. +29
    -0
      web_src/js/features/projects.js

+ 29
- 5
models/project_board.go View File

@@ -36,6 +36,7 @@ type ProjectBoard struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Title string Title string
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
Sorting int8 `xorm:"DEFAULT 0"`


ProjectID int64 `xorm:"INDEX NOT NULL"` ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"`
@@ -157,15 +158,24 @@ func getProjectBoard(e Engine, boardID int64) (*ProjectBoard, error) {
return board, nil return board, nil
} }


// UpdateProjectBoard updates the title of a project board
// UpdateProjectBoard updates a project board
func UpdateProjectBoard(board *ProjectBoard) error { func UpdateProjectBoard(board *ProjectBoard) error {
return updateProjectBoard(x, board) return updateProjectBoard(x, board)
} }


func updateProjectBoard(e Engine, board *ProjectBoard) error { func updateProjectBoard(e Engine, board *ProjectBoard) error {
_, err := e.ID(board.ID).Cols(
"title",
).Update(board)
var fieldToUpdate []string

if board.Sorting != 0 {
fieldToUpdate = append(fieldToUpdate, "sorting")
}

if board.Title != "" {
fieldToUpdate = append(fieldToUpdate, "title")
}

_, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board)

return err return err
} }


@@ -178,7 +188,7 @@ func GetProjectBoards(projectID int64) (ProjectBoardList, error) {
func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) { func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) {
var boards = make([]*ProjectBoard, 0, 5) var boards = make([]*ProjectBoard, 0, 5)


if err := e.Where("project_id=? AND `default`=?", projectID, false).Find(&boards); err != nil {
if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil {
return nil, err return nil, err
} }


@@ -277,3 +287,17 @@ func (bs ProjectBoardList) LoadIssues() (IssueList, error) {
} }
return issues, nil return issues, nil
} }

// UpdateProjectBoardSorting update project board sorting
func UpdateProjectBoardSorting(bs ProjectBoardList) error {
for i := range bs {
_, err := x.ID(bs[i].ID).Cols(
"sorting",
).Update(bs[i])

if err != nil {
return err
}
}
return nil
}

+ 4
- 4
modules/forms/repo_form.go View File

@@ -487,10 +487,10 @@ type UserCreateProjectForm struct {
UID int64 `binding:"Required"` UID int64 `binding:"Required"`
} }


// EditProjectBoardTitleForm is a form for editing the title of a project's
// board
type EditProjectBoardTitleForm struct {
Title string `binding:"Required;MaxSize(100)"`
// EditProjectBoardForm is a form for editing a project board
type EditProjectBoardForm struct {
Title string `binding:"Required;MaxSize(100)"`
Sorting int8
} }


// _____ .__.__ __ // _____ .__.__ __


+ 8
- 4
routers/repo/projects.go View File

@@ -403,7 +403,7 @@ func DeleteProjectBoard(ctx *context.Context) {


// AddBoardToProjectPost allows a new board to be added to a project. // AddBoardToProjectPost allows a new board to be added to a project.
func AddBoardToProjectPost(ctx *context.Context) { func AddBoardToProjectPost(ctx *context.Context) {
form := web.GetForm(ctx).(*auth.EditProjectBoardTitleForm)
form := web.GetForm(ctx).(*auth.EditProjectBoardForm)
if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) { if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) {
ctx.JSON(403, map[string]string{ ctx.JSON(403, map[string]string{
"message": "Only authorized users are allowed to perform this action.", "message": "Only authorized users are allowed to perform this action.",
@@ -481,9 +481,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project,
return project, board return project, board
} }


// EditProjectBoardTitle allows a project board's title to be updated
func EditProjectBoardTitle(ctx *context.Context) {
form := web.GetForm(ctx).(*auth.EditProjectBoardTitleForm)
// EditProjectBoard allows a project board's to be updated
func EditProjectBoard(ctx *context.Context) {
form := web.GetForm(ctx).(*auth.EditProjectBoardForm)
_, board := checkProjectBoardChangePermissions(ctx) _, board := checkProjectBoardChangePermissions(ctx)
if ctx.Written() { if ctx.Written() {
return return
@@ -493,6 +493,10 @@ func EditProjectBoardTitle(ctx *context.Context) {
board.Title = form.Title board.Title = form.Title
} }


if form.Sorting != 0 {
board.Sorting = form.Sorting
}

if err := models.UpdateProjectBoard(board); err != nil { if err := models.UpdateProjectBoard(board); err != nil {
ctx.ServerError("UpdateProjectBoard", err) ctx.ServerError("UpdateProjectBoard", err)
return return


+ 2
- 2
routers/routes/web.go View File

@@ -853,7 +853,7 @@ func RegisterRoutes(m *web.Route) {
m.Get("/new", repo.NewProject) m.Get("/new", repo.NewProject)
m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Post("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.AddBoardToProjectPost)
m.Post("", bindIgnErr(auth.EditProjectBoardForm{}), repo.AddBoardToProjectPost)
m.Post("/delete", repo.DeleteProject) m.Post("/delete", repo.DeleteProject)


m.Get("/edit", repo.EditProject) m.Get("/edit", repo.EditProject)
@@ -861,7 +861,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/{action:open|close}", repo.ChangeProjectStatus) m.Post("/{action:open|close}", repo.ChangeProjectStatus)


m.Group("/{boardID}", func() { m.Group("/{boardID}", func() {
m.Put("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.EditProjectBoardTitle)
m.Put("", bindIgnErr(auth.EditProjectBoardForm{}), repo.EditProjectBoard)
m.Delete("", repo.DeleteProjectBoard) m.Delete("", repo.DeleteProjectBoard)
m.Post("/default", repo.SetDefaultProjectBoard) m.Post("/default", repo.SetDefaultProjectBoard)




+ 1
- 1
templates/repo/projects/view.tmpl View File

@@ -72,7 +72,7 @@
<div class="board"> <div class="board">
{{ range $board := .Boards }} {{ range $board := .Boards }}


<div class="ui segment board-column">
<div class="ui segment board-column" data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}">
<div class="board-column-header"> <div class="board-column-header">
<div class="ui large label board-label">{{.Title}}</div> <div class="ui large label board-label">{{.Title}}</div>
{{if and $.CanWriteProjects (not $.Repository.IsArchived) $.PageIsProjects (ne .ID 0)}} {{if and $.CanWriteProjects (not $.Repository.IsArchived) $.PageIsProjects (ne .ID 0)}}


+ 29
- 0
web_src/js/features/projects.js View File

@@ -8,6 +8,34 @@ export default async function initProject() {
const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs'); const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs');
const boardColumns = document.getElementsByClassName('board-column'); const boardColumns = document.getElementsByClassName('board-column');


new Sortable(
document.getElementsByClassName('board')[0],
{
group: 'board-column',
draggable: '.board-column',
animation: 150,
onSort: () => {
const board = document.getElementsByClassName('board')[0];
const boardColumns = board.getElementsByClassName('board-column');

boardColumns.forEach((column, i) => {
if (parseInt($(column).data('sorting')) !== i) {
$.ajax({
url: $(column).data('url'),
data: JSON.stringify({sorting: i}),
headers: {
'X-Csrf-Token': csrf,
'X-Remote': true,
},
contentType: 'application/json',
method: 'PUT',
});
}
});
},
},
);

for (const column of boardColumns) { for (const column of boardColumns) {
new Sortable( new Sortable(
column.getElementsByClassName('board')[0], column.getElementsByClassName('board')[0],
@@ -74,6 +102,7 @@ export default async function initProject() {


window.location.reload(); window.location.reload();
}); });

$('.delete-project-board').each(function () { $('.delete-project-board').each(function () {
$(this).click(function (e) { $(this).click(function (e) {
e.preventDefault(); e.preventDefault();


Loading…
Cancel
Save