mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Finalize base features for smart playlist editor
This commit is contained in:
parent
0c7a0cc88a
commit
df4f05b14c
6 changed files with 739 additions and 624 deletions
|
|
@ -8,7 +8,7 @@ import { useParams } from 'react-router';
|
|||
import styled from 'styled-components';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { PlaylistSongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
import { PlaylistSongListQuery, ServerType, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
|
|
@ -79,10 +79,14 @@ const HeaderItems = styled.div`
|
|||
`;
|
||||
|
||||
interface PlaylistDetailHeaderProps {
|
||||
handleToggleShowQueryBuilder: () => void;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const PlaylistDetailSongListHeader = ({ tableRef }: PlaylistDetailHeaderProps) => {
|
||||
export const PlaylistDetailSongListHeader = ({
|
||||
tableRef,
|
||||
handleToggleShowQueryBuilder,
|
||||
}: PlaylistDetailHeaderProps) => {
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
|
@ -365,18 +369,36 @@ export const PlaylistDetailSongListHeader = ({ tableRef }: PlaylistDetailHeaderP
|
|||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Item onClick={() => handlePlay(Play.NOW)}>Play</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onClick={() => handlePlay(Play.LAST)}>
|
||||
Add to queue (last)
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onClick={() => handlePlay(Play.NEXT)}>
|
||||
Add to queue (next)
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item
|
||||
disabled
|
||||
onClick={() => handlePlay(Play.NEXT)}
|
||||
onClick={() => handlePlay(Play.LAST)}
|
||||
>
|
||||
Add to queue (last)
|
||||
Edit playlist
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
disabled
|
||||
onClick={() => handlePlay(Play.LAST)}
|
||||
>
|
||||
Add to queue (next)
|
||||
Delete playlist
|
||||
</DropdownMenu.Item>
|
||||
{server?.type === ServerType.NAVIDROME && !detailQuery?.data?.rules && (
|
||||
<>
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item
|
||||
$danger
|
||||
onClick={handleToggleShowQueryBuilder}
|
||||
>
|
||||
Toggle smart playlist editor
|
||||
</DropdownMenu.Item>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,33 @@
|
|||
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||
import { Flex, Group, ScrollArea } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import { Group } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import clone from 'lodash/clone';
|
||||
import get from 'lodash/get';
|
||||
import setWith from 'lodash/setWith';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { NDSongQueryFields } from '/@/renderer/api/navidrome.types';
|
||||
import { Button, DropdownMenu, NumberInput, QueryBuilder } from '/@/renderer/components';
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
MotionFlex,
|
||||
NumberInput,
|
||||
QueryBuilder,
|
||||
ScrollArea,
|
||||
Select,
|
||||
} from '/@/renderer/components';
|
||||
import {
|
||||
convertNDQueryToQueryGroup,
|
||||
convertQueryGroupToNDQuery,
|
||||
} from '/@/renderer/features/playlists/utils';
|
||||
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
||||
import { RiMore2Fill } from 'react-icons/ri';
|
||||
import { RiMore2Fill, RiSaveLine } from 'react-icons/ri';
|
||||
import { SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
import {
|
||||
NDSongQueryBooleanOperators,
|
||||
NDSongQueryDateOperators,
|
||||
NDSongQueryFields,
|
||||
NDSongQueryNumberOperators,
|
||||
NDSongQueryStringOperators,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
|
||||
type AddArgs = {
|
||||
groupIndex: number[];
|
||||
|
|
@ -25,322 +41,410 @@ type DeleteArgs = {
|
|||
};
|
||||
|
||||
interface PlaylistQueryBuilderProps {
|
||||
onSave: (parsedFilter: any) => void;
|
||||
onSaveAs: (parsedFilter: any) => void;
|
||||
isSaving?: boolean;
|
||||
limit?: number;
|
||||
onSave: (
|
||||
parsedFilter: any,
|
||||
extraFilters: { limit?: number; sortBy?: string; sortOrder?: string },
|
||||
) => void;
|
||||
onSaveAs: (
|
||||
parsedFilter: any,
|
||||
extraFilters: { limit?: number; sortBy?: string; sortOrder?: string },
|
||||
) => void;
|
||||
query: any;
|
||||
sortBy: SongListSort;
|
||||
sortOrder: SortOrder;
|
||||
}
|
||||
|
||||
export const PlaylistQueryBuilder = forwardRef(
|
||||
({ query, onSave, onSaveAs }: PlaylistQueryBuilderProps, ref) => {
|
||||
const [filters, setFilters] = useState<any>(
|
||||
convertNDQueryToQueryGroup(query) || {
|
||||
all: [],
|
||||
},
|
||||
);
|
||||
const DEFAULT_QUERY = {
|
||||
group: [],
|
||||
rules: [
|
||||
{
|
||||
field: '',
|
||||
operator: '',
|
||||
uniqueId: nanoid(),
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
type: 'all' as 'all' | 'any',
|
||||
uniqueId: nanoid(),
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
reset() {
|
||||
setFilters({
|
||||
all: [],
|
||||
});
|
||||
},
|
||||
}));
|
||||
export const PlaylistQueryBuilder = ({
|
||||
sortOrder,
|
||||
sortBy,
|
||||
limit,
|
||||
isSaving,
|
||||
query,
|
||||
onSave,
|
||||
onSaveAs,
|
||||
}: PlaylistQueryBuilderProps) => {
|
||||
const [filters, setFilters] = useState<QueryBuilderGroup>(
|
||||
query ? convertNDQueryToQueryGroup(query) : DEFAULT_QUERY,
|
||||
);
|
||||
|
||||
const setFilterHandler = (newFilters: QueryBuilderGroup) => {
|
||||
setFilters(newFilters);
|
||||
// onSave(newFilters);
|
||||
};
|
||||
const extraFiltersForm = useForm({
|
||||
initialValues: {
|
||||
limit,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(convertQueryGroupToNDQuery(filters));
|
||||
};
|
||||
const handleResetFilters = () => {
|
||||
if (query) {
|
||||
setFilters(convertNDQueryToQueryGroup(query));
|
||||
} else {
|
||||
setFilters(DEFAULT_QUERY);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveAs = () => {
|
||||
onSaveAs(convertQueryGroupToNDQuery(filters));
|
||||
};
|
||||
const handleClearFilters = () => {
|
||||
setFilters(DEFAULT_QUERY);
|
||||
};
|
||||
|
||||
const handleAddRuleGroup = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
const setFilterHandler = (newFilters: QueryBuilderGroup) => {
|
||||
setFilters(newFilters);
|
||||
};
|
||||
|
||||
const getPath = (level: number) => {
|
||||
if (level === 0) return 'group';
|
||||
const handleSave = () => {
|
||||
onSave(convertQueryGroupToNDQuery(filters), extraFiltersForm.values);
|
||||
};
|
||||
|
||||
const str = [];
|
||||
for (const index of groupIndex) {
|
||||
str.push(`group[${index}]`);
|
||||
}
|
||||
const handleSaveAs = () => {
|
||||
onSaveAs(convertQueryGroupToNDQuery(filters), extraFiltersForm.values);
|
||||
};
|
||||
|
||||
return `${str.join('.')}.group`;
|
||||
};
|
||||
const handleAddRuleGroup = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getPath(level);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
[
|
||||
...get(filtersCopy, path),
|
||||
{
|
||||
group: [],
|
||||
rules: [
|
||||
{
|
||||
field: '',
|
||||
operator: '',
|
||||
uniqueId: nanoid(),
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
type: 'any',
|
||||
uniqueId: nanoid(),
|
||||
},
|
||||
],
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const getPath = (level: number) => {
|
||||
if (level === 0) return 'group';
|
||||
|
||||
const str = [];
|
||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||
if (i !== groupIndex.length - 1) {
|
||||
str.push(`group[${groupIndex[i]}]`);
|
||||
} else {
|
||||
str.push(`group`);
|
||||
}
|
||||
}
|
||||
|
||||
return `${str.join('.')}`;
|
||||
};
|
||||
|
||||
const path = getPath(level);
|
||||
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
[
|
||||
...get(filtersCopy, path).filter(
|
||||
(group: QueryBuilderGroup) => group.uniqueId !== uniqueId,
|
||||
),
|
||||
],
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const getRulePath = (level: number, groupIndex: number[]) => {
|
||||
if (level === 0) return 'rules';
|
||||
const getPath = (level: number) => {
|
||||
if (level === 0) return 'group';
|
||||
|
||||
const str = [];
|
||||
for (const index of groupIndex) {
|
||||
str.push(`group[${index}]`);
|
||||
}
|
||||
|
||||
return `${str.join('.')}.rules`;
|
||||
return `${str.join('.')}.group`;
|
||||
};
|
||||
|
||||
const handleAddRule = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
const path = getPath(level);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
[
|
||||
...get(filtersCopy, path),
|
||||
{
|
||||
group: [],
|
||||
rules: [
|
||||
{
|
||||
field: '',
|
||||
operator: '',
|
||||
uniqueId: nanoid(),
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
type: 'any',
|
||||
uniqueId: nanoid(),
|
||||
},
|
||||
],
|
||||
clone,
|
||||
);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
[
|
||||
...get(filtersCopy, path),
|
||||
{
|
||||
field: 'title',
|
||||
operator: 'contains',
|
||||
uniqueId: nanoid(),
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
clone,
|
||||
);
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const handleDeleteRule = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
const getPath = (level: number) => {
|
||||
if (level === 0) return 'group';
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).filter((rule: QueryBuilderRule) => rule.uniqueId !== uniqueId),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeField = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: QueryBuilderGroup) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
// const defaultOperator = FILTER_OPTIONS_DATA.find(
|
||||
// (option) => option.value === value,
|
||||
// )?.default;
|
||||
|
||||
return {
|
||||
...rule,
|
||||
field: value,
|
||||
operator: '',
|
||||
value: '',
|
||||
};
|
||||
}),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeType = (args: any) => {
|
||||
const { level, groupIndex, value } = args;
|
||||
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
if (level === 0) {
|
||||
return setFilterHandler({ ...filtersCopy, type: value });
|
||||
const str = [];
|
||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||
if (i !== groupIndex.length - 1) {
|
||||
str.push(`group[${groupIndex[i]}]`);
|
||||
} else {
|
||||
str.push(`group`);
|
||||
}
|
||||
}
|
||||
|
||||
const getTypePath = () => {
|
||||
const str = [];
|
||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||
str.push(`group[${groupIndex[i]}]`);
|
||||
}
|
||||
|
||||
return `${str.join('.')}`;
|
||||
};
|
||||
|
||||
const path = getTypePath();
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
{
|
||||
...get(filtersCopy, path),
|
||||
type: value,
|
||||
},
|
||||
clone,
|
||||
);
|
||||
|
||||
return setFilterHandler(updatedFilters);
|
||||
return `${str.join('.')}`;
|
||||
};
|
||||
|
||||
const handleChangeOperator = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
const path = getPath(level);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: QueryBuilderRule) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
return {
|
||||
...rule,
|
||||
operator: value,
|
||||
};
|
||||
}),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeValue = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
console.log('path', path);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: QueryBuilderRule) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
return {
|
||||
...rule,
|
||||
value,
|
||||
};
|
||||
}),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
h="100%"
|
||||
justify="space-between"
|
||||
>
|
||||
<ScrollArea h="100%">
|
||||
<QueryBuilder
|
||||
data={filters}
|
||||
filters={NDSongQueryFields}
|
||||
groupIndex={[]}
|
||||
level={0}
|
||||
uniqueId={filters.uniqueId}
|
||||
onAddRule={handleAddRule}
|
||||
onAddRuleGroup={handleAddRuleGroup}
|
||||
onChangeField={handleChangeField}
|
||||
onChangeOperator={handleChangeOperator}
|
||||
onChangeType={handleChangeType}
|
||||
onChangeValue={handleChangeValue}
|
||||
onDeleteRule={handleDeleteRule}
|
||||
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||
/>
|
||||
</ScrollArea>
|
||||
<Group
|
||||
align="flex-end"
|
||||
p="1rem 1rem 0"
|
||||
position="apart"
|
||||
>
|
||||
<NumberInput
|
||||
label="Limit to"
|
||||
width={75}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<DropdownMenu position="bottom-end">
|
||||
<DropdownMenu.Target>
|
||||
<Button
|
||||
p="0.5em"
|
||||
variant="default"
|
||||
>
|
||||
<RiMore2Fill size={15} />
|
||||
</Button>
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Item onClick={handleSaveAs}>Save as</DropdownMenu.Item>
|
||||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
</Group>
|
||||
</Flex>
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
[...get(filtersCopy, path).filter((group: QueryBuilderGroup) => group.uniqueId !== uniqueId)],
|
||||
clone,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const getRulePath = (level: number, groupIndex: number[]) => {
|
||||
if (level === 0) return 'rules';
|
||||
|
||||
const str = [];
|
||||
for (const index of groupIndex) {
|
||||
str.push(`group[${index}]`);
|
||||
}
|
||||
|
||||
return `${str.join('.')}.rules`;
|
||||
};
|
||||
|
||||
const handleAddRule = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
[
|
||||
...get(filtersCopy, path),
|
||||
{
|
||||
field: '',
|
||||
operator: '',
|
||||
uniqueId: nanoid(),
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleDeleteRule = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).filter((rule: QueryBuilderRule) => rule.uniqueId !== uniqueId),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeField = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: QueryBuilderGroup) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
return {
|
||||
...rule,
|
||||
field: value,
|
||||
operator: '',
|
||||
value: '',
|
||||
};
|
||||
}),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeType = (args: any) => {
|
||||
const { level, groupIndex, value } = args;
|
||||
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
if (level === 0) {
|
||||
return setFilterHandler({ ...filtersCopy, type: value });
|
||||
}
|
||||
|
||||
const getTypePath = () => {
|
||||
const str = [];
|
||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||
str.push(`group[${groupIndex[i]}]`);
|
||||
}
|
||||
|
||||
return `${str.join('.')}`;
|
||||
};
|
||||
|
||||
const path = getTypePath();
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
{
|
||||
...get(filtersCopy, path),
|
||||
type: value,
|
||||
},
|
||||
clone,
|
||||
);
|
||||
|
||||
return setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeOperator = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: QueryBuilderRule) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
return {
|
||||
...rule,
|
||||
operator: value,
|
||||
};
|
||||
}),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleChangeValue = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: QueryBuilderRule) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
return {
|
||||
...rule,
|
||||
value,
|
||||
};
|
||||
}),
|
||||
clone,
|
||||
);
|
||||
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const sortOptions = [{ label: 'Random', type: 'string', value: 'random' }, ...NDSongQueryFields];
|
||||
|
||||
return (
|
||||
<MotionFlex
|
||||
direction="column"
|
||||
h="calc(100% - 3rem)"
|
||||
justify="space-between"
|
||||
>
|
||||
<ScrollArea
|
||||
h="100%"
|
||||
p="1rem"
|
||||
>
|
||||
<QueryBuilder
|
||||
data={filters}
|
||||
filters={NDSongQueryFields}
|
||||
groupIndex={[]}
|
||||
level={0}
|
||||
operators={{
|
||||
boolean: NDSongQueryBooleanOperators,
|
||||
date: NDSongQueryDateOperators,
|
||||
number: NDSongQueryNumberOperators,
|
||||
string: NDSongQueryStringOperators,
|
||||
}}
|
||||
uniqueId={filters.uniqueId}
|
||||
onAddRule={handleAddRule}
|
||||
onAddRuleGroup={handleAddRuleGroup}
|
||||
onChangeField={handleChangeField}
|
||||
onChangeOperator={handleChangeOperator}
|
||||
onChangeType={handleChangeType}
|
||||
onChangeValue={handleChangeValue}
|
||||
onClearFilters={handleClearFilters}
|
||||
onDeleteRule={handleDeleteRule}
|
||||
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||
onResetFilters={handleResetFilters}
|
||||
/>
|
||||
</ScrollArea>
|
||||
<Group
|
||||
noWrap
|
||||
align="flex-end"
|
||||
p="1rem"
|
||||
position="apart"
|
||||
>
|
||||
<Group
|
||||
noWrap
|
||||
w="100%"
|
||||
>
|
||||
<Select
|
||||
searchable
|
||||
data={sortOptions}
|
||||
label="Sort"
|
||||
maxWidth="20%"
|
||||
width={125}
|
||||
{...extraFiltersForm.getInputProps('sortBy')}
|
||||
/>
|
||||
<Select
|
||||
data={[
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'desc',
|
||||
},
|
||||
]}
|
||||
label="Order"
|
||||
maxWidth="20%"
|
||||
width={125}
|
||||
{...extraFiltersForm.getInputProps('sortOrder')}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Limit"
|
||||
maxWidth="20%"
|
||||
width={75}
|
||||
{...extraFiltersForm.getInputProps('limit')}
|
||||
/>
|
||||
</Group>
|
||||
<Group noWrap>
|
||||
<Button
|
||||
loading={isSaving}
|
||||
variant="filled"
|
||||
onClick={handleSaveAs}
|
||||
>
|
||||
Save as
|
||||
</Button>
|
||||
<DropdownMenu position="bottom-end">
|
||||
<DropdownMenu.Target>
|
||||
<Button
|
||||
disabled={isSaving}
|
||||
p="0.5em"
|
||||
variant="default"
|
||||
>
|
||||
<RiMore2Fill size={15} />
|
||||
</Button>
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Item
|
||||
$danger
|
||||
rightSection={
|
||||
<RiSaveLine
|
||||
color="var(--danger-color)"
|
||||
size={15}
|
||||
/>
|
||||
}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save and replace
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
</Group>
|
||||
</MotionFlex>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue