mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-11 02:13:41 +09:00
Merge pull request #1561 from anytypeio/feat-GO-212-changes-for-kanban
changes for kanban
This commit is contained in:
commit
8eaa291f94
9 changed files with 4987 additions and 3860 deletions
|
@ -410,6 +410,8 @@ func (s *State) fillChanges(msgs []simple.EventMessage) {
|
|||
updMsgs = append(updMsgs, msg.Msg)
|
||||
case *pb.EventMessageValueOfBlockDataViewGroupOrderUpdate:
|
||||
updMsgs = append(updMsgs, msg.Msg)
|
||||
case *pb.EventMessageValueOfBlockDataViewObjectOrderUpdate:
|
||||
updMsgs = append(updMsgs, msg.Msg)
|
||||
default:
|
||||
log.Errorf("unexpected event - can't convert to changes: %v", msg.Msg)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package state
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-middleware/util/pbtypes"
|
||||
|
||||
"github.com/anytypeio/go-anytype-middleware/core/block/simple/latex"
|
||||
"github.com/anytypeio/go-anytype-middleware/core/block/simple/table"
|
||||
|
@ -212,6 +213,26 @@ func (s *State) applyEvent(ev *pb.EventMessage) (err error) {
|
|||
}); err != nil {
|
||||
return
|
||||
}
|
||||
case *pb.EventMessageValueOfBlockDataViewObjectOrderUpdate:
|
||||
if err = apply(o.BlockDataViewObjectOrderUpdate.Id, func(b simple.Block) error {
|
||||
if f, ok := b.(dataview.Block); ok {
|
||||
|
||||
for _, order := range b.Model().GetDataview().ObjectOrders {
|
||||
if order.ViewId == o.BlockDataViewObjectOrderUpdate.ViewId && order.GroupId == o.BlockDataViewObjectOrderUpdate.GroupId {
|
||||
changes := o.BlockDataViewObjectOrderUpdate.GetSliceChanges()
|
||||
changedIds := slice.ApplyChanges(order.ObjectIds, pbtypes.EventsToSliceChange(changes))
|
||||
order.ObjectIds = changedIds
|
||||
}
|
||||
}
|
||||
|
||||
f.SetViewObjectOrder(b.Model().GetDataview().ObjectOrders)
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("not a dataview block")
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -112,6 +112,42 @@ func (d *Dataview) Diff(b simple.Block) (msgs []simple.EventMessage, err error)
|
|||
}
|
||||
}
|
||||
|
||||
for _, order2 := range dv.content.ObjectOrders {
|
||||
var found bool
|
||||
var changes []slice.Change
|
||||
for _, order1 := range d.content.ObjectOrders {
|
||||
if order1.ViewId == order2.ViewId && order1.GroupId == order2.GroupId {
|
||||
found = true
|
||||
changes = slice.Diff(order1.ObjectIds, order2.ObjectIds)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
msgs = append(msgs,
|
||||
simple.EventMessage{
|
||||
Msg: &pb.EventMessage{Value: &pb.EventMessageValueOfBlockDataViewObjectOrderUpdate{
|
||||
&pb.EventBlockDataviewObjectOrderUpdate{
|
||||
Id: dv.Id,
|
||||
ViewId: order2.ViewId,
|
||||
GroupId: order2.GroupId,
|
||||
SliceChanges: []*pb.EventBlockDataviewSliceChange{{Op: pb.EventBlockDataview_SliceOperationAdd, Ids: order2.ObjectIds}},
|
||||
}}}})
|
||||
}
|
||||
|
||||
if len(changes) > 0 {
|
||||
msgs = append(msgs,
|
||||
simple.EventMessage{
|
||||
Msg: &pb.EventMessage{Value: &pb.EventMessageValueOfBlockDataViewObjectOrderUpdate{
|
||||
&pb.EventBlockDataviewObjectOrderUpdate{
|
||||
Id: dv.Id,
|
||||
ViewId: order2.ViewId,
|
||||
GroupId: order2.GroupId,
|
||||
SliceChanges: pbtypes.SliceChangeToEvents(changes),
|
||||
}}}})
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: rewrite for optimised compare
|
||||
for _, view2 := range dv.content.Views {
|
||||
var found bool
|
||||
|
|
7128
docs/proto.md
7128
docs/proto.md
File diff suppressed because it is too large
Load diff
1368
pb/events.pb.go
1368
pb/events.pb.go
File diff suppressed because it is too large
Load diff
|
@ -68,6 +68,7 @@ message Event {
|
|||
Block.Dataview.RelationDelete blockDataviewRelationDelete = 24;
|
||||
Block.Dataview.RelationSet blockDataviewRelationSet = 23;
|
||||
Block.Dataview.GroupOrderUpdate blockDataViewGroupOrderUpdate = 38;
|
||||
Block.Dataview.ObjectOrderUpdate blockDataViewObjectOrderUpdate = 39;
|
||||
|
||||
|
||||
User.Block.Join userBlockJoin = 31;
|
||||
|
@ -740,6 +741,27 @@ message Event {
|
|||
string id = 1; // dataview block's id
|
||||
anytype.model.Block.Content.Dataview.GroupOrder groupOrder = 2;
|
||||
}
|
||||
|
||||
message ObjectOrderUpdate {
|
||||
string id = 1; // dataview block's id
|
||||
string viewId = 2;
|
||||
string groupId = 3;
|
||||
repeated SliceChange sliceChanges = 4;
|
||||
}
|
||||
|
||||
message SliceChange {
|
||||
SliceOperation op = 1;
|
||||
repeated string ids = 2;
|
||||
string afterId = 3;
|
||||
}
|
||||
|
||||
enum SliceOperation {
|
||||
SliceOperationNone = 0; // not used
|
||||
SliceOperationAdd = 1;
|
||||
SliceOperationMove = 2;
|
||||
SliceOperationRemove = 3;
|
||||
SliceOperationReplace = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pbtypes
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-middleware/pb"
|
||||
"sync"
|
||||
|
||||
"github.com/anytypeio/go-anytype-middleware/util/slice"
|
||||
|
@ -202,3 +203,38 @@ func StructNotNilKeys(st *types.Struct) (keys []string) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func EventsToSliceChange(changes []*pb.EventBlockDataviewSliceChange) []slice.Change {
|
||||
sliceOpMap := map[pb.EventBlockDataviewSliceOperation]slice.DiffOperation{
|
||||
pb.EventBlockDataview_SliceOperationNone: slice.OperationNone,
|
||||
pb.EventBlockDataview_SliceOperationAdd: slice.OperationAdd,
|
||||
pb.EventBlockDataview_SliceOperationMove: slice.OperationMove,
|
||||
pb.EventBlockDataview_SliceOperationRemove: slice.OperationRemove,
|
||||
pb.EventBlockDataview_SliceOperationReplace: slice.OperationReplace,
|
||||
}
|
||||
|
||||
var res []slice.Change
|
||||
for _, eventCh := range changes {
|
||||
res = append(res, slice.Change{Op: sliceOpMap[eventCh.Op], Ids: eventCh.Ids, AfterId: eventCh.AfterId})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func SliceChangeToEvents(changes []slice.Change) []*pb.EventBlockDataviewSliceChange {
|
||||
eventsOpMap := map[slice.DiffOperation]pb.EventBlockDataviewSliceOperation {
|
||||
slice.OperationNone: pb.EventBlockDataview_SliceOperationNone,
|
||||
slice.OperationAdd: pb.EventBlockDataview_SliceOperationAdd,
|
||||
slice.OperationMove: pb.EventBlockDataview_SliceOperationMove,
|
||||
slice.OperationRemove: pb.EventBlockDataview_SliceOperationRemove,
|
||||
slice.OperationReplace: pb.EventBlockDataview_SliceOperationReplace,
|
||||
}
|
||||
|
||||
var res []*pb.EventBlockDataviewSliceChange
|
||||
for _, sliceCh := range changes {
|
||||
res = append(res, &pb.EventBlockDataviewSliceChange{Op: eventsOpMap[sliceCh.Op], Ids: sliceCh.Ids, AfterId: sliceCh.AfterId})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
|
134
util/slice/diff.go
Normal file
134
util/slice/diff.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package slice
|
||||
|
||||
import (
|
||||
"github.com/mb0/diff"
|
||||
)
|
||||
|
||||
type DiffOperation int
|
||||
|
||||
const (
|
||||
OperationNone DiffOperation = iota
|
||||
OperationAdd
|
||||
OperationMove
|
||||
OperationRemove
|
||||
OperationReplace
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Op DiffOperation
|
||||
Ids []string
|
||||
AfterId string
|
||||
}
|
||||
|
||||
type MixedInput struct {
|
||||
A []string
|
||||
B []string
|
||||
}
|
||||
|
||||
func (m *MixedInput) Equal(a, b int) bool {
|
||||
return m.A[a] == m.B[b]
|
||||
}
|
||||
|
||||
func Diff(origin, changed []string) []Change {
|
||||
m := &MixedInput{
|
||||
origin,
|
||||
changed,
|
||||
}
|
||||
|
||||
var result []Change
|
||||
|
||||
changes := diff.Diff(len(m.A), len(m.B), m)
|
||||
delMap := make(map[string]bool)
|
||||
for _, c := range changes {
|
||||
if c.Del > 0 {
|
||||
for _, id := range m.A[c.A:c.A+c.Del] {
|
||||
delMap[id] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range changes {
|
||||
if c.Ins > 0 {
|
||||
inserts := m.B[c.B:c.B+c.Ins]
|
||||
afterId := ""
|
||||
if c.A > 0 {
|
||||
afterId = m.A[c.A-1]
|
||||
}
|
||||
var oneCh Change
|
||||
for _, id := range inserts {
|
||||
if delMap[id] { // move
|
||||
if oneCh.Op != OperationMove {
|
||||
if len(oneCh.Ids) > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
oneCh = Change{Op: OperationMove, AfterId: afterId}
|
||||
}
|
||||
oneCh.Ids = append(oneCh.Ids, id)
|
||||
delete(delMap, id)
|
||||
} else { // insert new
|
||||
if oneCh.Op != OperationAdd {
|
||||
if len(oneCh.Ids) > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
oneCh = Change{Op: OperationAdd, AfterId: afterId}
|
||||
}
|
||||
oneCh.Ids = append(oneCh.Ids, id)
|
||||
}
|
||||
afterId = id
|
||||
}
|
||||
|
||||
if len(oneCh.Ids) > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(delMap) > 0 { // remove
|
||||
delIds := make([]string, 0, len(delMap))
|
||||
for id := range delMap {
|
||||
delIds = append(delIds, id)
|
||||
}
|
||||
result = append(result, Change{Op: OperationRemove, Ids: delIds})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func ApplyChanges(origin []string, changes []Change) []string {
|
||||
result := make([]string, len(origin))
|
||||
copy(result, origin)
|
||||
|
||||
for _, ch := range changes {
|
||||
switch ch.Op {
|
||||
case OperationAdd:
|
||||
pos := -1
|
||||
if ch.AfterId != "" {
|
||||
pos = FindPos(result, ch.AfterId)
|
||||
if pos < 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
result = Insert(result, pos+1, ch.Ids...)
|
||||
case OperationMove:
|
||||
withoutMoved := Filter(result, func(id string) bool {
|
||||
return FindPos(ch.Ids, id) < 0
|
||||
})
|
||||
pos := -1
|
||||
if ch.AfterId != "" {
|
||||
pos = FindPos(withoutMoved, ch.AfterId)
|
||||
if pos < 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
result = Insert(withoutMoved, pos+1, ch.Ids...)
|
||||
case OperationRemove:
|
||||
result = Filter(result, func(id string) bool{
|
||||
return FindPos(ch.Ids, id) < 0
|
||||
})
|
||||
case OperationReplace:
|
||||
result = ch.Ids
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
100
util/slice/diff_test.go
Normal file
100
util/slice/diff_test.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package slice
|
||||
|
||||
import (
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Diff(t *testing.T) {
|
||||
origin := []string{"000", "001", "002", "003", "004", "005", "006", "007", "008", "009"}
|
||||
changed := []string{"000", "008", "001", "002", "003", "005", "006", "007", "009", "004"}
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
|
||||
assert.Equal(t, chs, []Change{
|
||||
{Op: OperationMove, Ids: []string{"008"}, AfterId: "000"},
|
||||
{Op: OperationMove, Ids: []string{"004"}, AfterId: "009"}},
|
||||
)
|
||||
}
|
||||
|
||||
func Test_ChangesApply(t *testing.T) {
|
||||
origin := []string{"000", "001", "002", "003", "004", "005", "006", "007", "008", "009"}
|
||||
changed := []string{"000", "008", "001", "002", "003", "005", "006", "007", "009", "004", "new"}
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
|
||||
res := ApplyChanges(origin, chs)
|
||||
|
||||
assert.Equal(t, changed, res)
|
||||
}
|
||||
|
||||
func Test_SameLength(t *testing.T) {
|
||||
for i := 0; i < 10000; i++ {
|
||||
l := randNum(5, 200)
|
||||
origin := getRandArray(l)
|
||||
changed := make([]string, len(origin))
|
||||
copy(changed, origin)
|
||||
rand.Shuffle(len(changed),
|
||||
func(i, j int) { changed[i], changed[j] = changed[j], changed[i] })
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
res := ApplyChanges(origin, chs)
|
||||
|
||||
assert.Equal(t, res, changed)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DifferentLength(t *testing.T) {
|
||||
for i := 0; i < 10000; i++ {
|
||||
l := randNum(5, 200)
|
||||
origin := getRandArray(l)
|
||||
changed := make([]string, len(origin))
|
||||
copy(changed, origin)
|
||||
rand.Shuffle(len(changed),
|
||||
func(i, j int) { changed[i], changed[j] = changed[j], changed[i] })
|
||||
|
||||
delCnt := randNum(0, 10)
|
||||
for i := 0; i < delCnt; i++ {
|
||||
l := len(changed) - 1
|
||||
if l <= 0 {
|
||||
continue
|
||||
}
|
||||
delIdx := randNum(0, l)
|
||||
changed = Remove(changed, changed[delIdx])
|
||||
}
|
||||
|
||||
insCnt := randNum(0, 10)
|
||||
for i := 0; i < insCnt; i++ {
|
||||
l := len(changed) - 1
|
||||
if l <= 0 {
|
||||
continue
|
||||
}
|
||||
insIdx := randNum(0, l)
|
||||
changed = Insert(changed, insIdx, []string{bson.NewObjectId().Hex()}...)
|
||||
}
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
res := ApplyChanges(origin, chs)
|
||||
|
||||
assert.Equal(t, res, changed)
|
||||
}
|
||||
}
|
||||
|
||||
func randNum(min, max int) int{
|
||||
if max <= min {
|
||||
return max
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Intn(max - min) + min
|
||||
}
|
||||
|
||||
func getRandArray(len int) []string {
|
||||
res := make([]string, len)
|
||||
for i := 0; i < len; i++ {
|
||||
res[i] = bson.NewObjectId().Hex()
|
||||
}
|
||||
return res
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue