remove animations and various smart playlist adjustments

This commit is contained in:
jeffvli
2025-11-29 19:55:22 -08:00
parent 526ba338a8
commit ad198ea047
3 changed files with 72 additions and 56 deletions
+21 -36
View File
@@ -1,4 +1,3 @@
import { AnimatePresence, motion } from 'motion/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option'; import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
@@ -187,41 +186,27 @@ export const QueryBuilder = ({
</Group> </Group>
{level === 0 && saveActions} {level === 0 && saveActions}
</Group> </Group>
<AnimatePresence initial={false}> {data?.rules?.map((rule: QueryBuilderRule) => (
{data?.rules?.map((rule: QueryBuilderRule) => ( <div key={rule.uniqueId}>
<motion.div <QueryBuilderOption
animate={{ opacity: 1, x: 0 }} data={rule}
exit={{ opacity: 0, x: -25 }} filters={filters}
initial={{ opacity: 0, x: -25 }} groupIndex={groupIndex || []}
key={rule.uniqueId} level={level}
transition={{ duration: 0.2, ease: 'easeInOut' }} noRemove={data?.rules?.length === 1}
> onChangeField={onChangeField}
<QueryBuilderOption onChangeOperator={onChangeOperator}
data={rule} onChangeValue={onChangeValue}
filters={filters} onDeleteRule={onDeleteRule}
groupIndex={groupIndex || []} operators={operators}
level={level} selectData={playlists}
noRemove={data?.rules?.length === 1} />
onChangeField={onChangeField} </div>
onChangeOperator={onChangeOperator} ))}
onChangeValue={onChangeValue}
onDeleteRule={onDeleteRule}
operators={operators}
selectData={playlists}
/>
</motion.div>
))}
</AnimatePresence>
{data?.group && ( {data?.group && (
<AnimatePresence initial={false}> <>
{data.group?.map((group: QueryBuilderGroup, index: number) => ( {data.group?.map((group: QueryBuilderGroup, index: number) => (
<motion.div <div key={group.uniqueId}>
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -25 }}
initial={{ opacity: 0, x: -25 }}
key={group.uniqueId}
transition={{ duration: 0.2, ease: 'easeInOut' }}
>
<QueryBuilder <QueryBuilder
data={group} data={group}
filters={filters} filters={filters}
@@ -241,9 +226,9 @@ export const QueryBuilder = ({
playlists={playlists} playlists={playlists}
uniqueId={group.uniqueId} uniqueId={group.uniqueId}
/> />
</motion.div> </div>
))} ))}
</AnimatePresence> </>
)} )}
</Stack> </Stack>
</Box> </Box>
@@ -34,7 +34,15 @@ interface QueryOptionProps {
selectData?: { label: string; value: string }[]; selectData?: { label: string; value: string }[];
} }
const QueryValueInput = ({ data, defaultValue, onChange, operator, type, ...props }: any) => { const QueryValueInput = ({
data,
defaultValue,
onChange,
operator,
type,
value: valueProp,
...props
}: any) => {
const [numberRange, setNumberRange] = useState<number[]>([0, 0]); const [numberRange, setNumberRange] = useState<number[]>([0, 0]);
// Parse date value helper - converts date string (YYYY-MM-DD) to Date for display // Parse date value helper - converts date string (YYYY-MM-DD) to Date for display
@@ -50,26 +58,45 @@ const QueryValueInput = ({ data, defaultValue, onChange, operator, type, ...prop
return null; return null;
}; };
const value = valueProp !== undefined ? valueProp : defaultValue;
// Store date range as strings for state management // Store date range as strings for state management
const [dateRange, setDateRange] = useState<[null | string, null | string]>(() => { const [dateRange, setDateRange] = useState<[null | string, null | string]>(() => {
if (defaultValue && Array.isArray(defaultValue)) { const currentValue = value !== undefined ? value : defaultValue;
if (currentValue && Array.isArray(currentValue)) {
return [ return [
typeof defaultValue[0] === 'string' ? defaultValue[0] : null, typeof currentValue[0] === 'string' ? currentValue[0] : null,
typeof defaultValue[1] === 'string' ? defaultValue[1] : null, typeof currentValue[1] === 'string' ? currentValue[1] : null,
]; ];
} }
return [null, null]; return [null, null];
}); });
// Sync dateRange state when defaultValue changes // Sync dateRange state when value changes
useEffect(() => { useEffect(() => {
if (operator === 'inTheRangeDate' && defaultValue && Array.isArray(defaultValue)) { const currentValue = value !== undefined ? value : defaultValue;
if (operator === 'inTheRangeDate' && currentValue && Array.isArray(currentValue)) {
setDateRange([ setDateRange([
typeof defaultValue[0] === 'string' ? defaultValue[0] : null, typeof currentValue[0] === 'string' ? currentValue[0] : null,
typeof defaultValue[1] === 'string' ? defaultValue[1] : null, typeof currentValue[1] === 'string' ? currentValue[1] : null,
]); ]);
} }
}, [defaultValue, operator]); }, [value, defaultValue, operator]);
// Sync numberRange state when value changes
useEffect(() => {
const currentValue = value !== undefined ? value : defaultValue;
if (operator === 'inTheRange' && currentValue && Array.isArray(currentValue)) {
setNumberRange([
typeof currentValue[0] === 'number'
? currentValue[0]
: Number(currentValue[0]) || 0,
typeof currentValue[1] === 'number'
? currentValue[1]
: Number(currentValue[1]) || 0,
]);
}
}, [value, defaultValue, operator]);
// Check if operator requires DatePicker // Check if operator requires DatePicker
const isDatePickerOperator = const isDatePickerOperator =
@@ -84,12 +111,13 @@ const QueryValueInput = ({ data, defaultValue, onChange, operator, type, ...prop
{ label: 'false', value: 'false' }, { label: 'false', value: 'false' },
]} ]}
onChange={onChange} onChange={onChange}
value={value}
{...props} {...props}
/> />
); );
case 'date': case 'date':
if (isDatePickerOperator && operator !== 'inTheRangeDate') { if (isDatePickerOperator && operator !== 'inTheRangeDate') {
const dateValue = defaultValue ? parseDateValue(defaultValue) : null; const dateValue = value ? parseDateValue(value) : null;
return ( return (
<DateInput <DateInput
clearable clearable
@@ -107,7 +135,7 @@ const QueryValueInput = ({ data, defaultValue, onChange, operator, type, ...prop
/> />
); );
} }
return <TextInput onChange={onChange} size="sm" {...props} />; return <TextInput onChange={onChange} size="sm" value={value} {...props} />;
case 'dateRange': case 'dateRange':
if (operator === 'inTheRangeDate') { if (operator === 'inTheRangeDate') {
return ( return (
@@ -158,24 +186,24 @@ const QueryValueInput = ({ data, defaultValue, onChange, operator, type, ...prop
<> <>
<NumberInput <NumberInput
{...props} {...props}
defaultValue={props.defaultValue && Number(props.defaultValue?.[0])}
maxWidth={81} maxWidth={81}
onChange={(e) => { onChange={(e) => {
const newRange = [Number(e) || 0, numberRange[1]]; const newRange = [Number(e) || 0, numberRange[1]];
setNumberRange(newRange); setNumberRange(newRange);
onChange(newRange); onChange(newRange);
}} }}
value={numberRange[0] || undefined}
width="10%" width="10%"
/> />
<NumberInput <NumberInput
{...props} {...props}
defaultValue={props.defaultValue && Number(props.defaultValue?.[1])}
maxWidth={81} maxWidth={81}
onChange={(e) => { onChange={(e) => {
const newRange = [numberRange[0], Number(e) || 0]; const newRange = [numberRange[0], Number(e) || 0];
setNumberRange(newRange); setNumberRange(newRange);
onChange(newRange); onChange(newRange);
}} }}
value={numberRange[1] || undefined}
width="10%" width="10%"
/> />
</> </>
@@ -185,14 +213,14 @@ const QueryValueInput = ({ data, defaultValue, onChange, operator, type, ...prop
<NumberInput <NumberInput
onChange={onChange} onChange={onChange}
size="sm" size="sm"
value={value !== undefined && value !== null ? Number(value) : undefined}
{...props} {...props}
defaultValue={props.defaultValue && Number(props.defaultValue)}
/> />
); );
case 'playlist': case 'playlist':
return <Select data={data} onChange={onChange} {...props} />; return <Select data={data} onChange={onChange} value={value} {...props} />;
case 'string': case 'string':
return <TextInput onChange={onChange} size="sm" {...props} />; return <TextInput onChange={onChange} size="sm" value={value || ''} {...props} />;
default: default:
return <></>; return <></>;
@@ -301,7 +329,6 @@ export const QueryBuilderOption = ({
{field ? ( {field ? (
<QueryValueInput <QueryValueInput
data={selectData || []} data={selectData || []}
defaultValue={value}
maxWidth={170} maxWidth={170}
onChange={handleChangeValue} onChange={handleChangeValue}
operator={operator} operator={operator}
@@ -311,15 +338,16 @@ export const QueryBuilderOption = ({
? 'dateRange' ? 'dateRange'
: fieldType : fieldType
} }
value={value}
width="25%" width="25%"
/> />
) : ( ) : (
<TextInput <TextInput
defaultValue={value}
disabled disabled
maxWidth={170} maxWidth={170}
onChange={handleChangeValue} onChange={handleChangeValue}
size="sm" size="sm"
value={value || ''}
width="25%" width="25%"
/> />
)} )}
@@ -276,10 +276,13 @@ export const PlaylistQueryBuilder = forwardRef(
const handleDeleteRuleGroup = useCallback((args: DeleteArgs) => { const handleDeleteRuleGroup = useCallback((args: DeleteArgs) => {
const { groupIndex, level, uniqueId } = args; const { groupIndex, level, uniqueId } = args;
const path = level === 0 ? 'group' : getTypePath(groupIndex); const path = level === 0 ? 'group' : getGroupPath(level - 1, groupIndex.slice(0, -1));
setFilters((prev) => { setFilters((prev) => {
const currentGroups = get(prev, path) || []; const currentGroups = get(prev, path);
if (!Array.isArray(currentGroups)) {
return prev;
}
return setWith( return setWith(
clone(prev), clone(prev),
path, path,