Add default ops, handle releaseDate

This commit is contained in:
jeffvli
2022-11-03 14:45:00 -07:00
parent 5908554f38
commit f284b29052
3 changed files with 246 additions and 75 deletions
+3 -4
View File
@@ -26,13 +26,12 @@ const getList = async (
const { serverId } = req.params;
const { take, skip, serverUrlId, advancedFilters } = req.query;
const decodedAdvancedFilters = advancedFilters && decodeURI(advancedFilters);
const jsonAdvancedFilters =
decodedAdvancedFilters && JSON.parse(decodedAdvancedFilters);
const decodedAdvancedFilters =
advancedFilters && JSON.parse(decodeURI(advancedFilters));
const albums = await service.albums.findMany({
...req.query,
advancedFilters: jsonAdvancedFilters,
advancedFilters: decodedAdvancedFilters,
serverId,
skip: Number(skip),
take: Number(take),
+71 -12
View File
@@ -134,8 +134,11 @@ const advancedFilterGroup = (
for (const rule of group.rules) {
if (rule.field && rule.operator) {
const [table, field, relationField] = rule.field.split('.');
const condition = rule.operator === '!~' ? 'none' : 'some';
const condition =
rule.operator === '!~' || rule.operator === '!=' ? 'none' : 'some';
const op = operatorMap[rule.operator as keyof typeof operatorMap];
const value =
field !== 'releaseDate' ? rule.value : new Date(rule.value);
switch (table) {
case 'albums':
@@ -144,7 +147,7 @@ const advancedFilterGroup = (
[field]: {
[condition]: {
[relationField]: {
[op]: rule.value,
[op]: value,
},
userId: user.id,
},
@@ -152,13 +155,24 @@ const advancedFilterGroup = (
});
break;
}
if (field === 'genres') {
query[rootType].push({
[field]: {
[condition]: {
[relationField]: {
equals: value,
},
},
},
});
break;
}
query[rootType].push({
[field]: {
mode: insensitiveFields.includes(field)
? 'insensitive'
: undefined,
[op]: rule.value,
[op]: value,
},
});
break;
@@ -171,7 +185,7 @@ const advancedFilterGroup = (
[field]: {
some: {
[relationField]: {
[op]: rule.value,
[op]: value,
},
userId: user.id,
},
@@ -181,13 +195,29 @@ const advancedFilterGroup = (
});
break;
}
if (field === 'genres') {
query[rootType].push({
[table]: {
some: {
[field]: {
[condition]: {
[relationField]: {
equals: value,
},
},
},
},
},
});
break;
}
query[rootType].push({
[table]: {
[condition]: {
[field]: {
mode: 'insensitive',
[op]: rule.value,
[op]: value,
},
},
},
@@ -218,8 +248,10 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
for (const rule of filter.rules) {
if (rule.field && rule.operator) {
let [table, field, relationField] = rule.field.split('.');
const condition = rule.operator === '!~' ? 'none' : 'some';
const condition =
rule.operator === '!~' || rule.operator === '!=' ? 'none' : 'some';
const op = operatorMap[rule.operator as keyof typeof operatorMap];
const value = field !== 'releaseDate' ? rule.value : new Date(rule.value);
switch (table) {
case 'albums':
@@ -228,7 +260,7 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
[field]: {
[condition]: {
[relationField]: {
[op]: rule.value,
[op]: value,
},
userId: user.id,
},
@@ -236,13 +268,24 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
});
break;
}
if (field === 'genres') {
rootQuery[rootQueryType].push({
[field]: {
[condition]: {
[relationField]: {
equals: value,
},
},
},
});
break;
}
rootQuery[rootQueryType].push({
[field]: {
mode: insensitiveFields.includes(field)
? 'insensitive'
: undefined,
[op]: rule.value,
[op]: value,
},
});
break;
@@ -255,7 +298,7 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
[field]: {
some: {
[relationField]: {
[op]: rule.value,
[op]: value,
},
userId: user.id,
},
@@ -265,13 +308,29 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
});
break;
}
if (field === 'genres') {
rootQuery[rootQueryType].push({
[table]: {
some: {
[field]: {
[condition]: {
[relationField]: {
equals: value,
},
},
},
},
},
});
break;
}
rootQuery[rootQueryType].push({
[table]: {
[condition]: {
[field]: {
mode: 'insensitive',
[op]: rule.value,
[op]: value,
},
},
},
@@ -1,5 +1,7 @@
import { useMemo } from 'react';
import { Stack, Group } from '@mantine/core';
import dayjs from 'dayjs';
import { AnimatePresence, motion } from 'framer-motion';
import get from 'lodash/get';
import set from 'lodash/set';
import { nanoid } from 'nanoid/non-secure';
@@ -12,6 +14,7 @@ import {
Select,
TextInput,
} from '@/renderer/components';
import { useGenreList } from '@/renderer/features/genres';
export enum FilterGroupType {
AND = 'AND',
@@ -19,10 +22,10 @@ export enum FilterGroupType {
}
export type AdvancedFilterRule = {
field: string | null;
operator: string | null;
field?: string | null;
operator?: string | null;
uniqueId: string;
value: string | number | Date | undefined | null | any;
value?: string | number | Date | undefined | null | any;
};
export type AdvancedFilterGroup = {
@@ -58,8 +61,8 @@ const NUMBER_FILTER_OPTIONS_DATA = [
];
const ID_FILTER_OPTIONS_DATA = [
{ label: 'is', value: 'equals' },
{ label: 'is not', value: 'not' },
{ label: 'is', value: '=' },
{ label: 'is not', value: '!=' },
];
const FILTER_GROUP_OPTIONS_DATA = [
@@ -75,73 +78,96 @@ const FILTER_GROUP_OPTIONS_DATA = [
const FILTER_OPTIONS_DATA = [
{
default: '~',
label: 'Artist Title',
value: 'artists.name',
},
{
default: '=',
label: 'Artist Rating',
value: 'artists.ratings.value',
},
{
default: '=',
label: 'Artist Genre',
value: 'artists.genre',
value: 'artists.genres.id',
},
{
default: '~',
label: 'Album Artist Title',
value: 'albumArtists.name',
},
{
default: '=',
label: 'Album Artist Rating',
value: 'albumArtists.ratings.value',
},
{
default: '=',
label: 'Album Artist Genre',
value: 'albumArtists.genre',
value: 'albumArtists.genres.id',
},
{
default: '~',
label: 'Album Title',
value: 'albums.name',
},
{
label: 'Album Genre',
value: 'albums.genre',
},
{
default: '=',
label: 'Album Rating',
value: 'albums.ratings.value',
},
{
default: '=',
label: 'Album Genre',
value: 'albums.genres.id',
},
{
default: '=',
label: 'Album Year',
value: 'albums.releaseYear',
},
{
default: '<',
label: 'Album Release Date',
value: 'albums.releaseDate',
},
{
label: 'Album Plays',
default: '=',
disabled: true,
label: 'Album Play Count',
value: 'albums.playCount',
},
{
default: '<',
label: 'Album Date Added',
value: 'albums.dateAdded',
},
{
default: '~',
label: 'Track Title',
value: 'songs.name',
},
{
label: 'Track Plays',
value: 'songs.playCount',
},
{
default: '=',
label: 'Track Rating',
value: 'songs.ratings.value',
},
{
default: '=',
label: 'Track Genre',
value: 'songs.genres.id',
},
{
default: '=',
disabled: true,
label: 'Track Play Count',
value: 'songs.playCount',
},
];
const OPTIONS_MAP = {
'albumArtists.genre': {
'albumArtists.genres.id': {
type: 'id',
},
'albumArtists.name': {
@@ -156,7 +182,7 @@ const OPTIONS_MAP = {
'albums.favorite': {
type: 'boolean',
},
'albums.genre': {
'albums.genres.id': {
type: 'id',
},
'albums.name': {
@@ -174,7 +200,7 @@ const OPTIONS_MAP = {
'albums.releaseYear': {
type: 'number',
},
'artists.genre': {
'artists.genres.id': {
type: 'id',
},
'artists.name': {
@@ -183,6 +209,9 @@ const OPTIONS_MAP = {
'artists.ratings.value': {
type: 'number',
},
'songs.genres.id': {
type: 'id',
},
'songs.name': {
type: 'string',
},
@@ -218,7 +247,7 @@ export const formatAdvancedFiltersGroups = (groups: AdvancedFilterGroup[]) => {
};
// Prevent query key from constantly changing due to empty rules or groups
export const formatAdvancedFiltersQuery = (filter: AdvancedFilterGroup) => {
export const encodeAdvancedFiltersQuery = (filter: AdvancedFilterGroup) => {
const updatedFilter = {
...filter,
group: formatAdvancedFiltersGroups(filter.group),
@@ -227,7 +256,7 @@ export const formatAdvancedFiltersQuery = (filter: AdvancedFilterGroup) => {
.map((rule) => ({ ...rule, uniqueId: undefined })),
};
return updatedFilter;
return encodeURI(JSON.stringify(updatedFilter));
};
interface FilterOptionProps {
@@ -252,6 +281,48 @@ const FilterOption = ({
onChangeValue,
}: FilterOptionProps) => {
const { field, operator, uniqueId, value } = data;
const { data: genres } = useGenreList();
const genresData = useMemo(() => {
if (!genres?.data) return null;
const album = [];
const song = [];
const albumArtist = [];
const artist = [];
for (const genre of genres.data) {
if (genre.albumCount > 0) {
album.push({
label: `${genre.name} (${genre.albumCount})`,
value: genre.id,
});
}
if (genre.songCount > 0) {
song.push({
label: `${genre.name} (${genre.songCount})`,
value: genre.id,
});
}
if (genre.albumArtistCount > 0) {
albumArtist.push({
label: `${genre.name} (${genre.albumArtistCount})`,
value: genre.id,
});
}
if (genre.artistCount > 0) {
artist.push({
label: `${genre.name} (${genre.artistCount})`,
value: genre.id,
});
}
}
return { album, albumArtist, artist, song };
}, [genres]);
const handleDeleteRule = () => {
onDeleteRule({ groupIndex, level, uniqueId });
@@ -281,6 +352,17 @@ const FilterOption = ({
});
}
const isDate = e instanceof Date;
if (isDate) {
return onChangeValue({
groupIndex,
level,
uniqueId,
value: dayjs(e).format('YYYY-MM-DD'),
});
}
return onChangeValue({
groupIndex,
level,
@@ -337,10 +419,10 @@ const FilterOption = ({
};
const filterInputValueMap = {
'albumArtists.genre': (
'albumArtists.genres.id': (
<Select
searchable
data={[]}
data={genresData?.albumArtist || []}
maxWidth={175}
size="xs"
value={value}
@@ -380,10 +462,10 @@ const FilterOption = ({
onChange={handleChangeValue}
/>
),
'albums.genre': (
'albums.genres.id': (
<Select
searchable
data={[]}
data={genresData?.album || []}
maxWidth={175}
size="xs"
value={value}
@@ -443,10 +525,10 @@ const FilterOption = ({
onChange={handleChangeValue}
/>
),
'artists.genre': (
'artists.genres.id': (
<Select
searchable
data={[]}
data={genresData?.artist || []}
maxWidth={175}
size="xs"
value={value}
@@ -474,6 +556,17 @@ const FilterOption = ({
onChange={handleChangeValue}
/>
),
'songs.genres.id': (
<Select
searchable
data={genresData?.song || []}
maxWidth={175}
size="xs"
value={value}
width="20%"
onChange={handleChangeValue}
/>
),
'songs.name': (
<TextInput
maxWidth={175}
@@ -640,39 +733,55 @@ const FilterGroup = ({
</DropdownMenu.Dropdown>
</DropdownMenu>
</Group>
{data.rules.map((rule: AdvancedFilterRule) => (
<FilterOption
key={rule.uniqueId}
data={rule}
groupIndex={groupIndex || []}
level={level}
noRemove={data.rules.length === 1}
onChangeField={onChangeField}
onChangeOperator={onChangeOperator}
onChangeValue={onChangeValue}
onDeleteRule={onDeleteRule}
/>
))}
{data.group && (
<>
{data.group.map((group: AdvancedFilterGroup, index: number) => (
<FilterGroup
key={group.uniqueId}
data={group}
groupIndex={[...(groupIndex || []), index]}
level={level + 1}
uniqueId={group.uniqueId}
onAddRule={onAddRule}
onAddRuleGroup={onAddRuleGroup}
<AnimatePresence key="advanced-filter-option" initial={false}>
{data.rules.map((rule: AdvancedFilterRule) => (
<motion.div
key={rule.uniqueId}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -25 }}
initial={{ opacity: 0, x: -25 }}
transition={{ duration: 0.2, ease: 'easeInOut' }}
>
<FilterOption
data={rule}
groupIndex={groupIndex || []}
level={level}
noRemove={data.rules.length === 1}
onChangeField={onChangeField}
onChangeOperator={onChangeOperator}
onChangeType={onChangeType}
onChangeValue={onChangeValue}
onDeleteRule={onDeleteRule}
onDeleteRuleGroup={onDeleteRuleGroup}
/>
</motion.div>
))}
</AnimatePresence>
{data.group && (
<AnimatePresence key="advanced-filter-group" initial={false}>
{data.group.map((group: AdvancedFilterGroup, index: number) => (
<motion.div
key={group.uniqueId}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -25 }}
initial={{ opacity: 0, x: -25 }}
transition={{ duration: 0.2, ease: 'easeInOut' }}
>
<FilterGroup
data={group}
groupIndex={[...(groupIndex || []), index]}
level={level + 1}
uniqueId={group.uniqueId}
onAddRule={onAddRule}
onAddRuleGroup={onAddRuleGroup}
onChangeField={onChangeField}
onChangeOperator={onChangeOperator}
onChangeType={onChangeType}
onChangeValue={onChangeValue}
onDeleteRule={onDeleteRule}
onDeleteRuleGroup={onDeleteRuleGroup}
/>
</motion.div>
))}
</>
</AnimatePresence>
)}
</Stack>
);
@@ -701,10 +810,10 @@ export const AdvancedFilters = ({ filters, setFilters }: any) => {
group: [],
rules: [
{
field: undefined,
operator: undefined,
field: '',
operator: '',
uniqueId: nanoid(),
value: undefined,
value: '',
},
],
type: FilterGroupType.AND,
@@ -800,11 +909,15 @@ export const AdvancedFilters = ({ filters, setFilters }: any) => {
path,
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
if (rule.uniqueId !== uniqueId) return rule;
const defaultOperator = FILTER_OPTIONS_DATA.find(
(option) => option.value === value
)?.default;
return {
...rule,
field: value,
operator: null,
value: null,
operator: defaultOperator || '',
value: '',
};
})
);