Merge pull request #97 from WithoutPants/delete_tag

Add delete tag button #96
This commit is contained in:
Leopere 2019-10-18 02:34:56 -04:00 committed by GitHub
commit 0d439ef42b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 1 deletions

View File

@ -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)
}

View File

@ -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<IProps> = (props: IProps) => {
// Editing / New state
const [editingTag, setEditingTag] = useState<Partial<GQL.TagDataFragment> | undefined>(undefined);
const [deletingTag, setDeletingTag] = useState<Partial<GQL.TagDataFragment> | undefined>(undefined);
const [name, setName] = useState<string>("");
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<boolean>(false);
useEffect(() => {
setIsLoading(loading);
@ -42,12 +46,22 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
}
}, [editingTag]);
useEffect(() => {
setIsDeleteAlertOpen(!!deletingTag);
}, [deletingTag]);
function getTagInput() {
const tagInput: Partial<GQL.TagCreateInput | GQL.TagUpdateInput> = { name };
if (!!editingTag) { (tagInput as Partial<GQL.TagUpdateInput>).id = editingTag.id; }
return tagInput;
}
function getDeleteTagInput() {
const tagInput: Partial<GQL.TagDestroyInput> = {};
if (!!deletingTag) { tagInput.id = deletingTag.id; }
return tagInput;
}
async function onEdit() {
try {
if (!!editingTag && !!editingTag.id) {
@ -63,11 +77,41 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
}
}
async function onDelete() {
try {
await deleteTag();
ToastUtils.success("Deleted tag");
setDeletingTag(undefined);
} catch (e) {
ErrorUtils.handle(e);
}
}
function renderDeleteAlert() {
return (
<Alert
cancelButtonText="Cancel"
confirmButtonText="Delete"
icon="trash"
intent="danger"
isOpen={isDeleteAlertOpen}
onCancel={() => setDeletingTag(undefined)}
onConfirm={() => onDelete()}
>
<p>
Are you sure you want to delete {deletingTag && deletingTag.name}?
</p>
</Alert>
);
}
if (!data || !data.allTags || isLoading) { return <Spinner size={Spinner.SIZE_LARGE} />; }
if (!!error) { return <>{error.message}</>; }
const tagElements = tags.map((tag) => {
return (
<>
{renderDeleteAlert()}
<div key={tag.id} className="tag-list-row">
<span onClick={() => setEditingTag(tag)}>{tag.name}</span>
<div style={{float: "right"}}>
@ -76,8 +120,10 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
Markers: {tag.scene_marker_count}
</Link>
<span>Total: {(tag.scene_count || 0) + (tag.scene_marker_count || 0)}</span>
<Button intent="danger" icon="trash" onClick={() => setDeletingTag(tag)}></Button>
</div>
</div>
</>
);
});
return (

View File

@ -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"] });