Containing Group/Sub-Group relationships (#5105)

* Add UI support for setting containing groups
* Show containing groups in group details panel
* Move tag hierarchical filter code into separate type
* Add depth to scene_count and add sub_group_count
* Add sub-groups tab to groups page
* Add containing groups to edit groups dialog
* Show containing group description in sub-group view
* Show group scene number in group scenes view
* Add ability to drag move grid cards
* Add sub group order option
* Add reorder sub-groups interface
* Separate page size selector component
* Add interfaces to add and remove sub-groups to a group
* Separate MultiSet components
* Allow setting description while setting containing groups
This commit is contained in:
WithoutPants
2024-08-30 11:43:44 +10:00
committed by GitHub
parent 96fdd94a01
commit bcf0fda7ac
99 changed files with 5388 additions and 935 deletions

View File

@@ -6,6 +6,7 @@ import (
"strconv"
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/group"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin/hook"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
@@ -43,6 +44,16 @@ func groupFromGroupCreateInput(ctx context.Context, input GroupCreateInput) (*mo
return nil, fmt.Errorf("converting tag ids: %w", err)
}
newGroup.ContainingGroups, err = translator.groupIDDescriptions(input.ContainingGroups)
if err != nil {
return nil, fmt.Errorf("converting containing group ids: %w", err)
}
newGroup.SubGroups, err = translator.groupIDDescriptions(input.SubGroups)
if err != nil {
return nil, fmt.Errorf("converting containing group ids: %w", err)
}
if input.Urls != nil {
newGroup.URLs = models.NewRelatedStrings(input.Urls)
}
@@ -82,26 +93,10 @@ func (r *mutationResolver) GroupCreate(ctx context.Context, input GroupCreateInp
// Start the transaction and save the group
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Group
err = qb.Create(ctx, newGroup)
if err != nil {
if err = r.groupService.Create(ctx, newGroup, frontimageData, backimageData); err != nil {
return err
}
// update image table
if len(frontimageData) > 0 {
if err := qb.UpdateFrontImage(ctx, newGroup.ID, frontimageData); err != nil {
return err
}
}
if len(backimageData) > 0 {
if err := qb.UpdateBackImage(ctx, newGroup.ID, backimageData); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
@@ -141,6 +136,18 @@ func groupPartialFromGroupUpdateInput(translator changesetTranslator, input Grou
return
}
updatedGroup.ContainingGroups, err = translator.updateGroupIDDescriptions(input.ContainingGroups, "containing_groups")
if err != nil {
err = fmt.Errorf("converting containing group ids: %w", err)
return
}
updatedGroup.SubGroups, err = translator.updateGroupIDDescriptions(input.SubGroups, "sub_groups")
if err != nil {
err = fmt.Errorf("converting containing group ids: %w", err)
return
}
updatedGroup.URLs = translator.updateStrings(input.Urls, "urls")
return updatedGroup, nil
@@ -179,37 +186,31 @@ func (r *mutationResolver) GroupUpdate(ctx context.Context, input GroupUpdateInp
}
}
// Start the transaction and save the group
var group *models.Group
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Group
group, err = qb.UpdatePartial(ctx, groupID, updatedGroup)
frontImage := group.ImageInput{
Image: frontimageData,
Set: frontImageIncluded,
}
backImage := group.ImageInput{
Image: backimageData,
Set: backImageIncluded,
}
_, err = r.groupService.UpdatePartial(ctx, groupID, updatedGroup, frontImage, backImage)
if err != nil {
return err
}
// update image table
if frontImageIncluded {
if err := qb.UpdateFrontImage(ctx, group.ID, frontimageData); err != nil {
return err
}
}
if backImageIncluded {
if err := qb.UpdateBackImage(ctx, group.ID, backimageData); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
// for backwards compatibility - run both movie and group hooks
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.GroupUpdatePost, input, translator.getFields())
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.MovieUpdatePost, input, translator.getFields())
return r.getGroup(ctx, group.ID)
r.hookExecutor.ExecutePostHooks(ctx, groupID, hook.GroupUpdatePost, input, translator.getFields())
r.hookExecutor.ExecutePostHooks(ctx, groupID, hook.MovieUpdatePost, input, translator.getFields())
return r.getGroup(ctx, groupID)
}
func groupPartialFromBulkGroupUpdateInput(translator changesetTranslator, input BulkGroupUpdateInput) (ret models.GroupPartial, err error) {
@@ -230,6 +231,18 @@ func groupPartialFromBulkGroupUpdateInput(translator changesetTranslator, input
return
}
updatedGroup.ContainingGroups, err = translator.updateGroupIDDescriptionsBulk(input.ContainingGroups, "containing_groups")
if err != nil {
err = fmt.Errorf("converting containing group ids: %w", err)
return
}
updatedGroup.SubGroups, err = translator.updateGroupIDDescriptionsBulk(input.SubGroups, "sub_groups")
if err != nil {
err = fmt.Errorf("converting containing group ids: %w", err)
return
}
updatedGroup.URLs = translator.optionalURLsBulk(input.Urls, nil)
return updatedGroup, nil
@@ -254,10 +267,8 @@ func (r *mutationResolver) BulkGroupUpdate(ctx context.Context, input BulkGroupU
ret := []*models.Group{}
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Group
for _, groupID := range groupIDs {
group, err := qb.UpdatePartial(ctx, groupID, updatedGroup)
group, err := r.groupService.UpdatePartial(ctx, groupID, updatedGroup, group.ImageInput{}, group.ImageInput{})
if err != nil {
return err
}
@@ -333,3 +344,70 @@ func (r *mutationResolver) GroupsDestroy(ctx context.Context, groupIDs []string)
return true, nil
}
func (r *mutationResolver) AddGroupSubGroups(ctx context.Context, input GroupSubGroupAddInput) (bool, error) {
groupID, err := strconv.Atoi(input.ContainingGroupID)
if err != nil {
return false, fmt.Errorf("converting group id: %w", err)
}
subGroups, err := groupsDescriptionsFromGroupInput(input.SubGroups)
if err != nil {
return false, fmt.Errorf("converting sub group ids: %w", err)
}
if err := r.withTxn(ctx, func(ctx context.Context) error {
return r.groupService.AddSubGroups(ctx, groupID, subGroups, input.InsertIndex)
}); err != nil {
return false, err
}
return true, nil
}
func (r *mutationResolver) RemoveGroupSubGroups(ctx context.Context, input GroupSubGroupRemoveInput) (bool, error) {
groupID, err := strconv.Atoi(input.ContainingGroupID)
if err != nil {
return false, fmt.Errorf("converting group id: %w", err)
}
subGroupIDs, err := stringslice.StringSliceToIntSlice(input.SubGroupIds)
if err != nil {
return false, fmt.Errorf("converting sub group ids: %w", err)
}
if err := r.withTxn(ctx, func(ctx context.Context) error {
return r.groupService.RemoveSubGroups(ctx, groupID, subGroupIDs)
}); err != nil {
return false, err
}
return true, nil
}
func (r *mutationResolver) ReorderSubGroups(ctx context.Context, input ReorderSubGroupsInput) (bool, error) {
groupID, err := strconv.Atoi(input.GroupID)
if err != nil {
return false, fmt.Errorf("converting group id: %w", err)
}
subGroupIDs, err := stringslice.StringSliceToIntSlice(input.SubGroupIds)
if err != nil {
return false, fmt.Errorf("converting sub group ids: %w", err)
}
insertPointID, err := strconv.Atoi(input.InsertAtID)
if err != nil {
return false, fmt.Errorf("converting insert at id: %w", err)
}
insertAfter := utils.IsTrue(input.InsertAfter)
if err := r.withTxn(ctx, func(ctx context.Context) error {
return r.groupService.ReorderSubGroups(ctx, groupID, subGroupIDs, insertPointID, insertAfter)
}); err != nil {
return false, err
}
return true, nil
}