diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 96f90da63..31fab5283 100755 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -44,13 +44,27 @@ const intlFormats = { }, }; +function languageMessageString(language: string) { + return language.replace(/-/, ""); +} + export const App: React.FC = () => { const config = useConfiguration(); const { data: systemStatusData } = useSystemStatus(); - const language = config.data?.configuration?.interface?.language ?? "en-GB"; - const messageLanguage = language.replace(/-/, ""); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const messages = flattenMessages((locales as any)[messageLanguage]); + const defaultLocale = "en-GB"; + const language = + config.data?.configuration?.interface?.language ?? defaultLocale; + const defaultMessageLanguage = languageMessageString(defaultLocale); + const messageLanguage = languageMessageString(language); + + // use en-GB as default messages if any messages aren't found in the chosen language + const mergedMessages = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(locales as any)[defaultMessageLanguage], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(locales as any)[messageLanguage], + }; + const messages = flattenMessages(mergedMessages); const setupMatch = useRouteMatch(["/setup", "/migrate"]); diff --git a/ui/v2.5/src/components/Changelog/versions/v080.md b/ui/v2.5/src/components/Changelog/versions/v080.md index 5edeee159..91cbbc69b 100644 --- a/ui/v2.5/src/components/Changelog/versions/v080.md +++ b/ui/v2.5/src/components/Changelog/versions/v080.md @@ -7,6 +7,7 @@ * Added [DLNA server](/settings?tab=dlna). ([#1364](https://github.com/stashapp/stash/pull/1364)) ### 🎨 Improvements +* Filter modifiers and sort by options are now sorted alphabetically. ([#1406](https://github.com/stashapp/stash/pull/1406)) * Add `CreatedAt` and `UpdatedAt` (and `FileModTime` where applicable) to API objects. ([#1421](https://github.com/stashapp/stash/pull/1421)) * Add Studios Performer filter criterion. ([#1405](https://github.com/stashapp/stash/pull/1405)) * Add `subtractDays` post-process scraper action. ([#1399](https://github.com/stashapp/stash/pull/1399)) diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx index 05222612c..9964be182 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx @@ -22,7 +22,7 @@ export const GalleryAddPanel: React.FC = ({ gallery }) => { }; // if galleries is already present, then we modify it, otherwise add let galleryCriterion = filter.criteria.find((c) => { - return c.type === "galleries"; + return c.criterionOption.value === "galleries"; }) as GalleriesCriterion; if ( diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx index ec06b478d..385bb0c0b 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx @@ -24,7 +24,7 @@ export const GalleryImagesPanel: React.FC = ({ }; // if galleries is already present, then we modify it, otherwise add let galleryCriterion = filter.criteria.find((c) => { - return c.type === "galleries"; + return c.criterionOption.value === "galleries"; }) as GalleriesCriterion; if ( diff --git a/ui/v2.5/src/components/List/AddFilter.tsx b/ui/v2.5/src/components/List/AddFilter.tsx index 03a513d22..5d521c1ea 100644 --- a/ui/v2.5/src/components/List/AddFilter.tsx +++ b/ui/v2.5/src/components/List/AddFilter.tsx @@ -5,20 +5,24 @@ import Mousetrap from "mousetrap"; import { Icon, FilterSelect, DurationInput } from "src/components/Shared"; import { CriterionModifier } from "src/core/generated-graphql"; import { - Criterion, - CriterionType, DurationCriterion, CriterionValue, + Criterion, } from "src/models/list-filter/criteria/criterion"; import { NoneCriterion } from "src/models/list-filter/criteria/none"; -import { makeCriteria } from "src/models/list-filter/criteria/utils"; -import { ListFilterModel } from "src/models/list-filter/filter"; +import { makeCriteria } from "src/models/list-filter/criteria/factory"; +import { ListFilterOptions } from "src/models/list-filter/filter-options"; +import { useIntl } from "react-intl"; +import { CriterionType } from "src/models/list-filter/types"; interface IAddFilterProps { - onAddCriterion: (criterion: Criterion, oldId?: string) => void; + onAddCriterion: ( + criterion: Criterion, + oldId?: string + ) => void; onCancel: () => void; - filter: ListFilterModel; - editingCriterion?: Criterion; + filterOptions: ListFilterOptions; + editingCriterion?: Criterion; } export const AddFilter: React.FC = ( @@ -27,10 +31,14 @@ export const AddFilter: React.FC = ( const defaultValue = useRef(); const [isOpen, setIsOpen] = useState(false); - const [criterion, setCriterion] = useState(new NoneCriterion()); + const [criterion, setCriterion] = useState>( + new NoneCriterion() + ); const valueStage = useRef(criterion.value); + const intl = useIntl(); + // configure keyboard shortcuts useEffect(() => { Mousetrap.bind("f", () => setIsOpen(true)); @@ -114,7 +122,7 @@ export const AddFilter: React.FC = ( } const maybeRenderFilterPopoverContents = () => { - if (criterion.type === "none") { + if (criterion.criterionOption.value === "none") { return; } @@ -149,19 +157,19 @@ export const AddFilter: React.FC = ( if (Array.isArray(criterion.value)) { if ( - criterion.type !== "performers" && - criterion.type !== "studios" && - criterion.type !== "parent_studios" && - criterion.type !== "tags" && - criterion.type !== "sceneTags" && - criterion.type !== "performerTags" && - criterion.type !== "movies" + criterion.criterionOption.value !== "performers" && + criterion.criterionOption.value !== "studios" && + criterion.criterionOption.value !== "parent_studios" && + criterion.criterionOption.value !== "tags" && + criterion.criterionOption.value !== "sceneTags" && + criterion.criterionOption.value !== "performerTags" && + criterion.criterionOption.value !== "movies" ) return; return ( { const newCriterion = _.cloneDeep(criterion); @@ -223,18 +231,28 @@ export const AddFilter: React.FC = ( if (props.editingCriterion) { return; } + + const options = props.filterOptions.criterionOptions + .map((c) => { + return { + value: c.value, + text: intl.formatMessage({ id: c.messageID }), + }; + }) + .sort((a, b) => a.text.localeCompare(b.text)); + return ( Filter - {props.filter.criterionOptions.map((c) => ( + {options.map((c) => ( ))} @@ -263,7 +281,10 @@ export const AddFilter: React.FC = ( - diff --git a/ui/v2.5/src/components/List/ListFilter.tsx b/ui/v2.5/src/components/List/ListFilter.tsx index 4e59388cc..2ef6d07ec 100644 --- a/ui/v2.5/src/components/List/ListFilter.tsx +++ b/ui/v2.5/src/components/List/ListFilter.tsx @@ -16,10 +16,15 @@ import { } from "react-bootstrap"; import { Icon } from "src/components/Shared"; -import { Criterion } from "src/models/list-filter/criteria/criterion"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { useFocus } from "src/utils"; +import { ListFilterOptions } from "src/models/list-filter/filter-options"; +import { useIntl } from "react-intl"; +import { + Criterion, + CriterionValue, +} from "src/models/list-filter/criteria/criterion"; import { AddFilter } from "./AddFilter"; interface IListFilterOperation { @@ -38,6 +43,7 @@ interface IListFilterProps { onDelete?: () => void; otherOperations?: IListFilterOperation[]; filter: ListFilterModel; + filterOptions: ListFilterOptions; itemsSelected?: boolean; } @@ -58,9 +64,11 @@ export const ListFilter: React.FC = ( }, 500); const [editingCriterion, setEditingCriterion] = useState< - Criterion | undefined + Criterion | undefined >(undefined); + const intl = useIntl(); + useEffect(() => { Mousetrap.bind("/", (e) => { setQueryFocus(); @@ -69,17 +77,17 @@ export const ListFilter: React.FC = ( Mousetrap.bind("r", () => onReshuffleRandomSort()); Mousetrap.bind("v g", () => { - if (props.filter.displayModeOptions.includes(DisplayMode.Grid)) { + if (props.filterOptions.displayModeOptions.includes(DisplayMode.Grid)) { onChangeDisplayMode(DisplayMode.Grid); } }); Mousetrap.bind("v l", () => { - if (props.filter.displayModeOptions.includes(DisplayMode.List)) { + if (props.filterOptions.displayModeOptions.includes(DisplayMode.List)) { onChangeDisplayMode(DisplayMode.List); } }); Mousetrap.bind("v w", () => { - if (props.filter.displayModeOptions.includes(DisplayMode.Wall)) { + if (props.filterOptions.displayModeOptions.includes(DisplayMode.Wall)) { onChangeDisplayMode(DisplayMode.Wall); } }); @@ -160,9 +168,9 @@ export const ListFilter: React.FC = ( props.onFilterUpdate(newFilter); } - function onChangeSortBy(event: React.MouseEvent) { + function onChangeSortBy(eventKey: string | null) { const newFilter = _.cloneDeep(props.filter); - newFilter.sortBy = event.currentTarget.text; + newFilter.sortBy = eventKey ?? undefined; newFilter.currentPage = 1; props.onFilterUpdate(newFilter); } @@ -180,7 +188,10 @@ export const ListFilter: React.FC = ( props.onFilterUpdate(newFilter); } - function onAddCriterion(criterion: Criterion, oldId?: string) { + function onAddCriterion( + criterion: Criterion, + oldId?: string + ) { const newFilter = _.cloneDeep(props.filter); // Find if we are editing an existing criteria, then modify that. Or create a new one. @@ -208,7 +219,7 @@ export const ListFilter: React.FC = ( setEditingCriterion(undefined); } - function onRemoveCriterion(removedCriterion: Criterion) { + function onRemoveCriterion(removedCriterion: Criterion) { const newFilter = _.cloneDeep(props.filter); newFilter.criteria = newFilter.criteria.filter( (criterion) => criterion.getId() !== removedCriterion.getId() @@ -218,7 +229,7 @@ export const ListFilter: React.FC = ( } let removedCriterionId = ""; - function onRemoveCriterionTag(criterion?: Criterion) { + function onRemoveCriterionTag(criterion?: Criterion) { if (!criterion) { return; } @@ -227,7 +238,7 @@ export const ListFilter: React.FC = ( onRemoveCriterion(criterion); } - function onClickCriterionTag(criterion?: Criterion) { + function onClickCriterionTag(criterion?: Criterion) { if (!criterion || removedCriterionId !== "") { return; } @@ -235,15 +246,24 @@ export const ListFilter: React.FC = ( } function renderSortByOptions() { - return props.filter.sortByOptions.map((option) => ( - - {option} - - )); + return props.filterOptions.sortByOptions + .map((o) => { + return { + message: intl.formatMessage({ id: o.messageID }), + value: o.value, + }; + }) + .sort((a, b) => a.message.localeCompare(b.message)) + .map((option) => ( + + {option.message} + + )); } function renderDisplayModeOptions() { @@ -272,7 +292,7 @@ export const ListFilter: React.FC = ( } } - return props.filter.displayModeOptions.map((option) => ( + return props.filterOptions.displayModeOptions.map((option) => ( = ( key={criterion.getId()} onClick={() => onClickCriterionTag(criterion)} > - {criterion.getLabel()} + {criterion.getLabel(intl)}