Fix saved filter UI bugs (#4394)

* Fix missing intl strings
* Fix saved filter list z-index
* Fix saved filter list double scrollbar
* Display error inside filter list
* Filter out nonexistent saved filter rows in FrontPageConfig
This commit is contained in:
DingDongSoLong4 2023-12-26 07:03:55 +02:00 committed by GitHub
parent 0cdea209bb
commit 9bd36408ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 46 deletions

View file

@ -3,11 +3,7 @@ import { FormattedMessage, IntlShape, useIntl } from "react-intl";
import { useFindSavedFilters } from "src/core/StashService"; import { useFindSavedFilters } from "src/core/StashService";
import { LoadingIndicator } from "../Shared/LoadingIndicator"; import { LoadingIndicator } from "../Shared/LoadingIndicator";
import { Button, Form, Modal } from "react-bootstrap"; import { Button, Form, Modal } from "react-bootstrap";
import { import * as GQL from "src/core/generated-graphql";
FilterMode,
FindSavedFiltersQuery,
SavedFilter,
} from "src/core/generated-graphql";
import { ConfigurationContext } from "src/hooks/Config"; import { ConfigurationContext } from "src/hooks/Config";
import { import {
IUIConfig, IUIConfig,
@ -21,24 +17,25 @@ import {
interface IAddSavedFilterModalProps { interface IAddSavedFilterModalProps {
onClose: (content?: FrontPageContent) => void; onClose: (content?: FrontPageContent) => void;
existingSavedFilterIDs: string[]; existingSavedFilterIDs: string[];
candidates: FindSavedFiltersQuery; candidates: GQL.FindSavedFiltersQuery;
} }
const FilterModeToMessageID = { const FilterModeToMessageID = {
[FilterMode.Galleries]: "galleries", [GQL.FilterMode.Galleries]: "galleries",
[FilterMode.Images]: "images", [GQL.FilterMode.Images]: "images",
[FilterMode.Movies]: "movies", [GQL.FilterMode.Movies]: "movies",
[FilterMode.Performers]: "performers", [GQL.FilterMode.Performers]: "performers",
[FilterMode.SceneMarkers]: "markers", [GQL.FilterMode.SceneMarkers]: "markers",
[FilterMode.Scenes]: "scenes", [GQL.FilterMode.Scenes]: "scenes",
[FilterMode.Studios]: "studios", [GQL.FilterMode.Studios]: "studios",
[FilterMode.Tags]: "tags", [GQL.FilterMode.Tags]: "tags",
}; };
function filterTitle(intl: IntlShape, f: Pick<SavedFilter, "mode" | "name">) { type SavedFilter = Pick<GQL.SavedFilter, "id" | "mode" | "name">;
return `${intl.formatMessage({ id: FilterModeToMessageID[f.mode] })}: ${
f.name function filterTitle(intl: IntlShape, f: SavedFilter) {
}`; const typeMessage = intl.formatMessage({ id: FilterModeToMessageID[f.mode] });
return `${typeMessage}: ${f.name}`;
} }
const AddContentModal: React.FC<IAddSavedFilterModalProps> = ({ const AddContentModal: React.FC<IAddSavedFilterModalProps> = ({
@ -98,7 +95,7 @@ const AddContentModal: React.FC<IAddSavedFilterModalProps> = ({
.filter((f) => { .filter((f) => {
// markers not currently supported // markers not currently supported
return ( return (
f.mode !== FilterMode.SceneMarkers && f.mode !== GQL.FilterMode.SceneMarkers &&
!existingSavedFilterIDs.includes(f.id) !existingSavedFilterIDs.includes(f.id)
); );
}) })
@ -232,7 +229,7 @@ const AddContentModal: React.FC<IAddSavedFilterModalProps> = ({
interface IFilterRowProps { interface IFilterRowProps {
content: FrontPageContent; content: FrontPageContent;
allSavedFilters: Pick<SavedFilter, "id" | "mode" | "name">[]; allSavedFilters: SavedFilter[];
onDelete: () => void; onDelete: () => void;
} }
@ -242,10 +239,9 @@ const ContentRow: React.FC<IFilterRowProps> = (props: IFilterRowProps) => {
function title() { function title() {
switch (props.content.__typename) { switch (props.content.__typename) {
case "SavedFilter": case "SavedFilter":
const savedFilterId = String(props.content.savedFilterId);
const savedFilter = props.allSavedFilters.find( const savedFilter = props.allSavedFilters.find(
(f) => (f) => f.id === savedFilterId
f.id ===
(props.content as ISavedFilterRow).savedFilterId?.toString()
); );
if (!savedFilter) return ""; if (!savedFilter) return "";
return filterTitle(intl, savedFilter); return filterTitle(intl, savedFilter);
@ -302,7 +298,18 @@ export const FrontPageConfig: React.FC<IFrontPageConfigProps> = ({
const frontPageContent = getFrontPageContent(ui); const frontPageContent = getFrontPageContent(ui);
if (frontPageContent) { if (frontPageContent) {
setCurrentContent(frontPageContent); setCurrentContent(
// filter out rows where the saved filter no longer exists
frontPageContent.filter((r) => {
if (r.__typename === "SavedFilter") {
const savedFilterId = String(r.savedFilterId);
return allFilters.findSavedFilters.some(
(f) => f.id === savedFilterId
);
}
return true;
})
);
} }
}, [allFilters, ui]); }, [allFilters, ui]);

View file

@ -43,7 +43,7 @@ export const PhashFilter: React.FC<IPhashFilterProps> = ({
className="btn-secondary" className="btn-secondary"
onChange={valueChanged} onChange={valueChanged}
value={value ? value.value : ""} value={value ? value.value : ""}
placeholder={intl.formatMessage({ id: "phash" })} placeholder={intl.formatMessage({ id: "media_info.phash" })}
/> />
</Form.Group> </Form.Group>
{criterion.modifier !== CriterionModifier.IsNull && {criterion.modifier !== CriterionModifier.IsNull &&

View file

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useState } from "react";
import { import {
Button, Button,
ButtonGroup, ButtonGroup,
@ -39,7 +39,6 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
const intl = useIntl(); const intl = useIntl();
const { data, error, loading, refetch } = useFindSavedFilters(filter.mode); const { data, error, loading, refetch } = useFindSavedFilters(filter.mode);
const oldError = useRef(error);
const [filterName, setFilterName] = useState(""); const [filterName, setFilterName] = useState("");
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@ -56,14 +55,6 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
const savedFilters = data?.findSavedFilters ?? []; const savedFilters = data?.findSavedFilters ?? [];
useEffect(() => {
if (error && error !== oldError.current) {
Toast.error(error);
}
oldError.current = error;
}, [error, Toast, oldError]);
async function onSaveFilter(name: string, id?: string) { async function onSaveFilter(name: string, id?: string) {
const filterCopy = filter.clone(); const filterCopy = filter.clone();
@ -285,6 +276,8 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
} }
function renderSavedFilters() { function renderSavedFilters() {
if (error) return <h6 className="text-center">{error.message}</h6>;
if (loading || saving) { if (loading || saving) {
return ( return (
<div className="loading"> <div className="loading">
@ -311,20 +304,22 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
function maybeRenderSetDefaultButton() { function maybeRenderSetDefaultButton() {
if (persistState === PersistanceLevel.ALL) { if (persistState === PersistanceLevel.ALL) {
return ( return (
<Button <div className="mt-1">
className="set-as-default-button" <Button
variant="secondary" className="set-as-default-button"
size="sm" variant="secondary"
onClick={() => onSetDefaultFilter()} size="sm"
> onClick={() => onSetDefaultFilter()}
{intl.formatMessage({ id: "actions.set_as_default" })} >
</Button> {intl.formatMessage({ id: "actions.set_as_default" })}
</Button>
</div>
); );
} }
} }
return ( return (
<div> <>
{maybeRenderDeleteAlert()} {maybeRenderDeleteAlert()}
{maybeRenderOverwriteAlert()} {maybeRenderOverwriteAlert()}
<InputGroup> <InputGroup>
@ -359,6 +354,6 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
</InputGroup> </InputGroup>
{renderSavedFilters()} {renderSavedFilters()}
{maybeRenderSetDefaultButton()} {maybeRenderSetDefaultButton()}
</div> </>
); );
}; };

View file

@ -65,8 +65,14 @@ input[type="range"].zoom-slider {
.saved-filter-list-menu { .saved-filter-list-menu {
width: 300px; width: 300px;
&.dropdown-menu.show {
display: flex;
flex-direction: column;
}
.set-as-default-button { .set-as-default-button {
float: right; float: right;
margin-right: 0.5rem;
} }
.LoadingIndicator { .LoadingIndicator {
@ -94,12 +100,17 @@ input[type="range"].zoom-slider {
align-items: center; align-items: center;
display: inline; display: inline;
overflow-x: hidden; overflow-x: hidden;
padding-left: 1.25rem;
padding-right: 0.25rem; padding-right: 0.25rem;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.btn-group { .btn-group {
margin-left: auto; margin-left: auto;
.btn {
border-radius: 0;
}
} }
.delete-button { .delete-button {

View file

@ -660,7 +660,6 @@ div.react-select__menu,
div.dropdown-menu { div.dropdown-menu {
background-color: $secondary; background-color: $secondary;
color: $text-color; color: $text-color;
z-index: 1600;
.react-select__option, .react-select__option,
.dropdown-item { .dropdown-item {

View file

@ -921,6 +921,7 @@
"unknown": "Unknown", "unknown": "Unknown",
"wall": "Wall" "wall": "Wall"
}, },
"distance": "Distance",
"donate": "Donate", "donate": "Donate",
"dupe_check": { "dupe_check": {
"description": "Levels below 'Exact' can take longer to calculate. False positives might also be returned on lower accuracy levels.", "description": "Levels below 'Exact' can take longer to calculate. False positives might also be returned on lower accuracy levels.",