2023-01-04 04:09:24 -08:00
|
|
|
import { useState, useImperativeHandle, forwardRef } from 'react';
|
2023-01-04 15:54:25 -08:00
|
|
|
import { Flex, Group, ScrollArea } from '@mantine/core';
|
2023-01-04 04:09:24 -08:00
|
|
|
import clone from 'lodash/clone';
|
|
|
|
|
import get from 'lodash/get';
|
2023-01-04 15:54:25 -08:00
|
|
|
import setWith from 'lodash/setWith';
|
2023-01-04 04:09:24 -08:00
|
|
|
import { nanoid } from 'nanoid';
|
|
|
|
|
import { NDSongQueryFields } from '/@/renderer/api/navidrome.types';
|
2023-01-04 15:54:25 -08:00
|
|
|
import { Button, DropdownMenu, NumberInput, QueryBuilder } from '/@/renderer/components';
|
|
|
|
|
import {
|
|
|
|
|
convertNDQueryToQueryGroup,
|
|
|
|
|
convertQueryGroupToNDQuery,
|
|
|
|
|
} from '/@/renderer/features/playlists/utils';
|
|
|
|
|
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
|
|
|
|
import { RiMore2Fill } from 'react-icons/ri';
|
2023-01-04 04:09:24 -08:00
|
|
|
|
|
|
|
|
type AddArgs = {
|
|
|
|
|
groupIndex: number[];
|
|
|
|
|
level: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type DeleteArgs = {
|
|
|
|
|
groupIndex: number[];
|
|
|
|
|
level: number;
|
|
|
|
|
uniqueId: string;
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
interface PlaylistQueryBuilderProps {
|
|
|
|
|
onSave: (parsedFilter: any) => void;
|
|
|
|
|
onSaveAs: (parsedFilter: any) => void;
|
|
|
|
|
query: any;
|
|
|
|
|
}
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
export const PlaylistQueryBuilder = forwardRef(
|
|
|
|
|
({ query, onSave, onSaveAs }: PlaylistQueryBuilderProps, ref) => {
|
|
|
|
|
const [filters, setFilters] = useState<any>(
|
|
|
|
|
convertNDQueryToQueryGroup(query) || {
|
2023-01-04 04:09:24 -08:00
|
|
|
all: [],
|
2023-01-04 15:54:25 -08:00
|
|
|
},
|
|
|
|
|
);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
useImperativeHandle(ref, () => ({
|
|
|
|
|
reset() {
|
|
|
|
|
setFilters({
|
|
|
|
|
all: [],
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
}));
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const setFilterHandler = (newFilters: QueryBuilderGroup) => {
|
|
|
|
|
setFilters(newFilters);
|
|
|
|
|
// onSave(newFilters);
|
2023-01-04 04:09:24 -08:00
|
|
|
};
|
|
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const handleSave = () => {
|
|
|
|
|
onSave(convertQueryGroupToNDQuery(filters));
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const handleSaveAs = () => {
|
|
|
|
|
onSaveAs(convertQueryGroupToNDQuery(filters));
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const handleAddRuleGroup = (args: AddArgs) => {
|
|
|
|
|
const { level, groupIndex } = args;
|
|
|
|
|
const filtersCopy = clone(filters);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const getPath = (level: number) => {
|
|
|
|
|
if (level === 0) return 'group';
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const str = [];
|
|
|
|
|
for (const index of groupIndex) {
|
|
|
|
|
str.push(`group[${index}]`);
|
2023-01-04 04:09:24 -08:00
|
|
|
}
|
|
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
return `${str.join('.')}.group`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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);
|
2023-01-04 04:09:24 -08:00
|
|
|
};
|
|
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
|
|
|
|
const { uniqueId, level, groupIndex } = args;
|
|
|
|
|
const filtersCopy = clone(filters);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const getPath = (level: number) => {
|
|
|
|
|
if (level === 0) return 'group';
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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`);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
return `${str.join('.')}`;
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const path = getPath(level);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const updatedFilters = setWith(
|
|
|
|
|
filtersCopy,
|
|
|
|
|
path,
|
|
|
|
|
[
|
|
|
|
|
...get(filtersCopy, path).filter(
|
|
|
|
|
(group: QueryBuilderGroup) => group.uniqueId !== uniqueId,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
clone,
|
|
|
|
|
);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
setFilterHandler(updatedFilters);
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const getRulePath = (level: number, groupIndex: number[]) => {
|
|
|
|
|
if (level === 0) return 'rules';
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const str = [];
|
|
|
|
|
for (const index of groupIndex) {
|
|
|
|
|
str.push(`group[${index}]`);
|
|
|
|
|
}
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
return `${str.join('.')}.rules`;
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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: 'title',
|
|
|
|
|
operator: 'contains',
|
|
|
|
|
uniqueId: nanoid(),
|
|
|
|
|
value: null,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
clone,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setFilterHandler(updatedFilters);
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const handleDeleteRule = (args: DeleteArgs) => {
|
|
|
|
|
const { uniqueId, level, groupIndex } = args;
|
|
|
|
|
const filtersCopy = clone(filters);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const path = getRulePath(level, groupIndex);
|
|
|
|
|
const updatedFilters = setWith(
|
|
|
|
|
filtersCopy,
|
|
|
|
|
path,
|
|
|
|
|
get(filtersCopy, path).filter((rule: QueryBuilderRule) => rule.uniqueId !== uniqueId),
|
|
|
|
|
clone,
|
|
|
|
|
);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
setFilterHandler(updatedFilters);
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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);
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const handleChangeType = (args: any) => {
|
|
|
|
|
const { level, groupIndex, value } = args;
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
const filtersCopy = clone(filters);
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
if (level === 0) {
|
|
|
|
|
return setFilterHandler({ ...filtersCopy, type: value });
|
2023-01-04 04:09:24 -08:00
|
|
|
}
|
|
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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);
|
2023-01-04 04:09:24 -08:00
|
|
|
};
|
|
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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);
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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);
|
|
|
|
|
};
|
2023-01-04 04:09:24 -08:00
|
|
|
|
2023-01-04 15:54:25 -08:00
|
|
|
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>
|
2023-01-04 04:09:24 -08:00
|
|
|
);
|
2023-01-04 15:54:25 -08:00
|
|
|
},
|
|
|
|
|
);
|