duplicate phash

This commit is contained in:
Gykes 2025-11-28 21:49:24 -08:00
parent c6ae43c1d6
commit f4733f4e8d
4 changed files with 144 additions and 0 deletions

View file

@ -0,0 +1,118 @@
import React, { useMemo, useState } from "react";
import { useIntl } from "react-intl";
import { BooleanCriterion } from "src/models/list-filter/criteria/criterion";
import { ListFilterModel } from "src/models/list-filter/filter";
import { Option, SidebarListFilter } from "./SidebarListFilter";
import { DuplicatedCriterionOption } from "src/models/list-filter/criteria/phash";
interface ISidebarDuplicateFilterProps {
title?: React.ReactNode;
filter: ListFilterModel;
setFilter: (f: ListFilterModel) => void;
sectionID?: string;
}
export const SidebarDuplicateFilter: React.FC<ISidebarDuplicateFilterProps> = ({
title,
filter,
setFilter,
sectionID,
}) => {
const intl = useIntl();
const [expandedType, setExpandedType] = useState<string | null>(null);
const trueLabel = intl.formatMessage({ id: "true" });
const falseLabel = intl.formatMessage({ id: "false" });
const phashLabel = intl.formatMessage({ id: "media_info.phash" });
const criteria = filter.criteriaFor(
DuplicatedCriterionOption.type
) as BooleanCriterion[];
const criterion = criteria.length > 0 ? criteria[0] : null;
// The main duplicate type option
const phashOption = useMemo(
() => ({
id: "phash",
label: phashLabel,
}),
[phashLabel]
);
// Determine if pHash is selected (has a true/false value)
const phashSelected = criterion !== null;
// Selected shows "pHash: True" or "pHash: False" when a value is set
const selected: Option[] = useMemo(() => {
if (!criterion) return [];
const valueLabel = criterion.value === "true" ? trueLabel : falseLabel;
return [
{
id: "phash",
label: `${phashLabel}: ${valueLabel}`,
},
];
}, [criterion, phashLabel, trueLabel, falseLabel]);
// Available options - show pHash if not selected
const options: Option[] = useMemo(() => {
if (phashSelected) return [];
return [phashOption];
}, [phashSelected, phashOption]);
function onSelect(item: Option) {
if (item.id === "phash") {
// Expand to show True/False options
setExpandedType("phash");
}
}
function onUnselect() {
setFilter(filter.removeCriterion(DuplicatedCriterionOption.type));
setExpandedType(null);
}
function onSelectValue(value: "true" | "false") {
const newCriterion = criterion
? criterion.clone()
: DuplicatedCriterionOption.makeCriterion();
newCriterion.value = value;
setFilter(
filter.replaceCriteria(DuplicatedCriterionOption.type, [newCriterion])
);
setExpandedType(null);
}
// Sub-options shown when pHash is clicked
const subOptions =
expandedType === "phash" ? (
<div className="duplicate-sub-options">
<div
className="duplicate-sub-option"
onClick={() => onSelectValue("true")}
>
{trueLabel}
</div>
<div
className="duplicate-sub-option"
onClick={() => onSelectValue("false")}
>
{falseLabel}
</div>
</div>
) : null;
return (
<SidebarListFilter
title={title}
candidates={options}
onSelect={onSelect}
onUnselect={onUnselect}
selected={selected}
singleValue
postCandidates={subOptions}
sectionID={sectionID}
/>
);
};

View file

@ -726,6 +726,24 @@ input[type="range"].zoom-slider {
min-height: 2em;
}
.duplicate-sub-options {
margin-left: 2rem;
padding-left: 0.5rem;
.duplicate-sub-option {
align-items: center;
cursor: pointer;
display: flex;
height: 2em;
opacity: 0.8;
padding-left: 0.5rem;
&:hover {
background-color: rgba(138, 155, 168, 0.15);
}
}
}
.tilted {
transform: rotate(45deg);
}

View file

@ -60,6 +60,7 @@ import {
DurationCriterionOption,
PerformerAgeCriterionOption,
} from "src/models/list-filter/scenes";
import { SidebarDuplicateFilter } from "../List/Filters/DuplicateFilter";
import { SidebarAgeFilter } from "../List/Filters/SidebarAgeFilter";
import { SidebarDurationFilter } from "../List/Filters/SidebarDurationFilter";
import {
@ -351,6 +352,12 @@ const SidebarContent: React.FC<{
setFilter={setFilter}
sectionID="organized"
/>
<SidebarDuplicateFilter
title={<FormattedMessage id="duplicated" />}
filter={filter}
setFilter={setFilter}
sectionID="duplicated"
/>
<SidebarAgeFilter
title={<FormattedMessage id="performer_age" />}
option={PerformerAgeCriterionOption}

View file

@ -1059,6 +1059,7 @@
"select_youngest": "Select the youngest file in the duplicate group",
"title": "Duplicate Scenes"
},
"duplicated": "Duplicated",
"duplicated_phash": "Duplicated (pHash)",
"duration": "Duration",
"effect_filters": {