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
3 changed files with 74 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
"errors"
"database/sql" "database/sql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database" "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 { 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) 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 _ from "lodash";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useEffect, useState } from "react";
import { QueryHookResult } from "react-apollo-hooks"; import { QueryHookResult } from "react-apollo-hooks";
@@ -22,11 +22,15 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
// Editing / New state // Editing / New state
const [editingTag, setEditingTag] = useState<Partial<GQL.TagDataFragment> | undefined>(undefined); const [editingTag, setEditingTag] = useState<Partial<GQL.TagDataFragment> | undefined>(undefined);
const [deletingTag, setDeletingTag] = useState<Partial<GQL.TagDataFragment> | undefined>(undefined);
const [name, setName] = useState<string>(""); const [name, setName] = useState<string>("");
const { data, error, loading } = StashService.useAllTags(); const { data, error, loading } = StashService.useAllTags();
const updateTag = StashService.useTagUpdate(getTagInput() as GQL.TagUpdateInput); const updateTag = StashService.useTagUpdate(getTagInput() as GQL.TagUpdateInput);
const createTag = StashService.useTagCreate(getTagInput() as GQL.TagCreateInput); const createTag = StashService.useTagCreate(getTagInput() as GQL.TagCreateInput);
const deleteTag = StashService.useTagDestroy(getDeleteTagInput() as GQL.TagDestroyInput);
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
setIsLoading(loading); setIsLoading(loading);
@@ -42,12 +46,22 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
} }
}, [editingTag]); }, [editingTag]);
useEffect(() => {
setIsDeleteAlertOpen(!!deletingTag);
}, [deletingTag]);
function getTagInput() { function getTagInput() {
const tagInput: Partial<GQL.TagCreateInput | GQL.TagUpdateInput> = { name }; const tagInput: Partial<GQL.TagCreateInput | GQL.TagUpdateInput> = { name };
if (!!editingTag) { (tagInput as Partial<GQL.TagUpdateInput>).id = editingTag.id; } if (!!editingTag) { (tagInput as Partial<GQL.TagUpdateInput>).id = editingTag.id; }
return tagInput; return tagInput;
} }
function getDeleteTagInput() {
const tagInput: Partial<GQL.TagDestroyInput> = {};
if (!!deletingTag) { tagInput.id = deletingTag.id; }
return tagInput;
}
async function onEdit() { async function onEdit() {
try { try {
if (!!editingTag && !!editingTag.id) { 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 (!data || !data.allTags || isLoading) { return <Spinner size={Spinner.SIZE_LARGE} />; }
if (!!error) { return <>{error.message}</>; } if (!!error) { return <>{error.message}</>; }
const tagElements = tags.map((tag) => { const tagElements = tags.map((tag) => {
return ( return (
<>
{renderDeleteAlert()}
<div key={tag.id} className="tag-list-row"> <div key={tag.id} className="tag-list-row">
<span onClick={() => setEditingTag(tag)}>{tag.name}</span> <span onClick={() => setEditingTag(tag)}>{tag.name}</span>
<div style={{float: "right"}}> <div style={{float: "right"}}>
@@ -76,8 +120,10 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
Markers: {tag.scene_marker_count} Markers: {tag.scene_marker_count}
</Link> </Link>
<span>Total: {(tag.scene_count || 0) + (tag.scene_marker_count || 0)}</span> <span>Total: {(tag.scene_count || 0) + (tag.scene_marker_count || 0)}</span>
<Button intent="danger" icon="trash" onClick={() => setDeletingTag(tag)}></Button>
</div> </div>
</div> </div>
</>
); );
}); });
return ( return (

View File

@@ -157,6 +157,9 @@ export class StashService {
public static useTagUpdate(input: GQL.TagUpdateInput) { public static useTagUpdate(input: GQL.TagUpdateInput) {
return GQL.useTagUpdate({ variables: input, refetchQueries: ["AllTags"] }); 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) { public static useConfigureGeneral(input: GQL.ConfigGeneralInput) {
return GQL.useConfigureGeneral({ variables: { input }, refetchQueries: ["Configuration"] }); return GQL.useConfigureGeneral({ variables: { input }, refetchQueries: ["Configuration"] });