diff --git a/pkg/models/querybuilder_tag.go b/pkg/models/querybuilder_tag.go index e6036b751..16a8bcb82 100644 --- a/pkg/models/querybuilder_tag.go +++ b/pkg/models/querybuilder_tag.go @@ -1,6 +1,7 @@ package models import ( + "errors" "database/sql" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" @@ -52,6 +53,29 @@ func (qb *TagQueryBuilder) Update(updatedTag Tag, tx *sqlx.Tx) (*Tag, error) { } func (qb *TagQueryBuilder) Destroy(id string, tx *sqlx.Tx) error { + // delete tag from scenes and markers first + _, err := tx.Exec("DELETE FROM scenes_tags WHERE tag_id = ?", id) + if err != nil { + return err + } + + _, err = tx.Exec("DELETE FROM scene_markers_tags WHERE tag_id = ?", id) + if err != nil { + return err + } + + // cannot unset primary_tag_id in scene_markers because it is not nullable + countQuery := "SELECT COUNT(*) as count FROM scene_markers where primary_tag_id = ?" + args := []interface{}{id} + primaryMarkers, err := runCountQuery(countQuery, args) + if err != nil { + return err + } + + if primaryMarkers > 0 { + return errors.New("Cannot delete tag used as a primary tag in scene markers") + } + return executeDeleteQuery("tags", id, tx) } diff --git a/ui/v2/src/components/Tags/TagList.tsx b/ui/v2/src/components/Tags/TagList.tsx index c61f9be2e..aae49b025 100644 --- a/ui/v2/src/components/Tags/TagList.tsx +++ b/ui/v2/src/components/Tags/TagList.tsx @@ -1,4 +1,4 @@ -import { Button, Classes, Dialog, EditableText, FormGroup, HTMLTable, InputGroup, Spinner, Tag } from "@blueprintjs/core"; +import { Alert, Button, Classes, Dialog, EditableText, FormGroup, HTMLTable, InputGroup, Spinner, Tag } from "@blueprintjs/core"; import _ from "lodash"; import React, { FunctionComponent, useEffect, useState } from "react"; import { QueryHookResult } from "react-apollo-hooks"; @@ -22,11 +22,15 @@ export const TagList: FunctionComponent = (props: IProps) => { // Editing / New state const [editingTag, setEditingTag] = useState | undefined>(undefined); + const [deletingTag, setDeletingTag] = useState | undefined>(undefined); const [name, setName] = useState(""); const { data, error, loading } = StashService.useAllTags(); const updateTag = StashService.useTagUpdate(getTagInput() as GQL.TagUpdateInput); const createTag = StashService.useTagCreate(getTagInput() as GQL.TagCreateInput); + const deleteTag = StashService.useTagDestroy(getDeleteTagInput() as GQL.TagDestroyInput); + + const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); useEffect(() => { setIsLoading(loading); @@ -42,12 +46,22 @@ export const TagList: FunctionComponent = (props: IProps) => { } }, [editingTag]); + useEffect(() => { + setIsDeleteAlertOpen(!!deletingTag); + }, [deletingTag]); + function getTagInput() { const tagInput: Partial = { name }; if (!!editingTag) { (tagInput as Partial).id = editingTag.id; } return tagInput; } + function getDeleteTagInput() { + const tagInput: Partial = {}; + if (!!deletingTag) { tagInput.id = deletingTag.id; } + return tagInput; + } + async function onEdit() { try { if (!!editingTag && !!editingTag.id) { @@ -63,11 +77,41 @@ export const TagList: FunctionComponent = (props: IProps) => { } } + async function onDelete() { + try { + await deleteTag(); + ToastUtils.success("Deleted tag"); + setDeletingTag(undefined); + } catch (e) { + ErrorUtils.handle(e); + } + } + + function renderDeleteAlert() { + return ( + setDeletingTag(undefined)} + onConfirm={() => onDelete()} + > +

+ Are you sure you want to delete {deletingTag && deletingTag.name}? +

+
+ ); + } + if (!data || !data.allTags || isLoading) { return ; } if (!!error) { return <>{error.message}; } const tagElements = tags.map((tag) => { return ( + <> + {renderDeleteAlert()}
setEditingTag(tag)}>{tag.name}
@@ -76,8 +120,10 @@ export const TagList: FunctionComponent = (props: IProps) => { Markers: {tag.scene_marker_count} Total: {(tag.scene_count || 0) + (tag.scene_marker_count || 0)} +
+ ); }); return ( diff --git a/ui/v2/src/core/StashService.ts b/ui/v2/src/core/StashService.ts index 6f6066fb9..7943a96e3 100644 --- a/ui/v2/src/core/StashService.ts +++ b/ui/v2/src/core/StashService.ts @@ -157,6 +157,9 @@ export class StashService { public static useTagUpdate(input: GQL.TagUpdateInput) { return GQL.useTagUpdate({ variables: input, refetchQueries: ["AllTags"] }); } + public static useTagDestroy(input: GQL.TagDestroyInput) { + return GQL.useTagDestroy({ variables: input, refetchQueries: ["AllTags"] }); + } public static useConfigureGeneral(input: GQL.ConfigGeneralInput) { return GQL.useConfigureGeneral({ variables: { input }, refetchQueries: ["Configuration"] });