Compare commits

..
Author SHA1 Message Date
Salihu b814bd0f12 fix: checkbox filtering 2026-08-21 19:29:10 +01:00
Salihu b86abd3d40 Revert "fix: checkbox filtering"
This reverts commit 3b858746e3.
2026-08-21 19:23:48 +01:00
Salihu 3b858746e3 fix: checkbox filtering 2026-08-21 19:21:14 +01:00
Philipinho 66b424a3b8 fix: preserve hash in vimeo embed url 2026-08-20 22:25:45 +01:00
3 changed files with 35 additions and 89 deletions
@@ -9,7 +9,6 @@ import {
Text,
UnstyledButton,
Button,
MultiSelect,
} from "@mantine/core";
import { IconPlus, IconTrash } from "@tabler/icons-react";
import {
@@ -53,9 +52,6 @@ const NO_VALUE_OPERATORS: FilterOperator[] = ["isEmpty", "isNotEmpty"];
// stored value so a stale shape isn't sent to the engine.
function valueClass(op: FilterOperator, inputKind: string): string {
if (NO_VALUE_OPERATORS.includes(op)) return "none";
if (inputKind === "choices") {
return op === "any" || op === "none" ? "choicesMulti" : "choicesSingle";
}
if (inputKind === "person") {
return op === "any" || op === "none" ? "personMulti" : "personSingle";
}
@@ -74,10 +70,6 @@ function getOperatorsForType(type: string): FilterOperator[] {
DEFAULT_FILTER_OPERATORS) as FilterOperator[];
}
function isMultiChoice(op: FilterCondition["op"]): boolean {
return op === "any" || op === "none";
}
function FilterValueInput({
condition,
property,
@@ -129,32 +121,6 @@ function FilterValueInput({
const typeOptions = property.typeOptions as SelectTypeOptions | undefined;
const choices = typeOptions?.choices ?? [];
const choiceOptions = choices.map((c) => ({ value: c.id, label: c.name }));
if (isMultiChoice(condition.op)) {
const { value } = condition;
const selected = (
Array.isArray(value) ? value : value ? [value] : []
).filter((id) => choices.some((c) => c.id === id));
return (
<MultiSelect
size="xs"
data={choiceOptions}
comboboxProps={{ withinPortal: false }}
value={selected}
onChange={(values) => onChange(values)}
w={160}
styles={{
pillsList: {
maxHeight: 70,
overflowY: "auto",
},
}}
maxDropdownHeight={220}
/>
);
}
return (
<Select
size="xs"
@@ -233,18 +199,11 @@ export function ViewFilterConfigPopover({
label: p.name,
}));
const [unSaved, setUnSaved] = useState(false)
const [draft, setDraft] = useState<FilterCondition | null>(null);
const [draftConditions, setDraftConditions] =
useState<FilterCondition[]>(conditions);
useEffect(() => {
if (opened) {
setDraftConditions(conditions);
setDraft(null);
setUnSaved(false)
}
}, [opened, conditions]);
if (!opened) setDraft(null);
}, [opened]);
const handleStartDraft = useCallback(() => {
const firstProperty = properties[0];
@@ -257,21 +216,14 @@ export function ViewFilterConfigPopover({
}, [properties]);
const handleSaveDraft = useCallback(() => {
const nextConditions = draft
? [...draftConditions, draft]
: draftConditions;
onChange(nextConditions);
if (!draft) return;
onChange([...conditions, draft]);
setDraft(null);
setUnSaved(false)
}, [draft, draftConditions, onChange]);
}, [draft, conditions, onChange]);
const handleCancelDraft = useCallback(() => {
setDraftConditions(conditions)
setDraft(null);
setUnSaved(false)
}, [conditions]);
}, []);
const handleDraftPropertyChange = useCallback(
(propertyId: string | null) => {
@@ -320,19 +272,17 @@ export function ViewFilterConfigPopover({
const handleRemove = useCallback(
(index: number) => {
setUnSaved(true);
setDraftConditions((current) => current.filter((_, i) => i !== index));
onChange(conditions.filter((_, i) => i !== index));
},
[],
[conditions, onChange],
);
const handlePropertyChange = useCallback(
(index: number, propertyId: string | null) => {
if (!propertyId) return;
const newProperty = properties.find((p) => p.id === propertyId);
setUnSaved(true)
setDraftConditions((current) =>
current.map((f, i) => {
onChange(
conditions.map((f, i) => {
if (i !== index) return f;
if (newProperty) {
const validOperators = getOperatorsForType(newProperty.type);
@@ -352,16 +302,15 @@ export function ViewFilterConfigPopover({
}),
);
},
[properties],
[conditions, properties, onChange],
);
const handleOperatorChange = useCallback(
(index: number, operator: string | null) => {
if (!operator) return;
const op = operator as FilterOperator;
setUnSaved(true)
setDraftConditions((current) =>
current.map((f, i) => {
onChange(
conditions.map((f, i) => {
if (i !== index) return f;
const kind = inputKindForProperty(
properties.find((p) => p.id === f.propertyId),
@@ -371,17 +320,16 @@ export function ViewFilterConfigPopover({
}),
);
},
[properties],
[conditions, properties, onChange],
);
const handleValueChange = useCallback(
(index: number, value: unknown) => {
setUnSaved(true)
setDraftConditions((current) =>
current.map((f, i) => (i === index ? { ...f, value } : f)),
onChange(
conditions.map((f, i) => (i === index ? { ...f, value } : f)),
);
},
[],
[conditions, onChange],
);
return (
@@ -414,13 +362,13 @@ export function ViewFilterConfigPopover({
{t("Filter by")}
</Text>
{draftConditions.length === 0 && !draft && (
{conditions.length === 0 && !draft && (
<Text size="xs" c="dimmed">
{t("No filters applied")}
</Text>
)}
{draftConditions.map((condition, index) => {
{conditions.map((condition, index) => {
const needsValue = !NO_VALUE_OPERATORS.includes(condition.op);
const property = properties.find(
(p) => p.id === condition.propertyId,
@@ -523,6 +471,14 @@ export function ViewFilterConfigPopover({
/>
)}
</Group>
<Group justify="flex-end" gap="xs">
<Button variant="default" size="xs" onClick={handleCancelDraft}>
{t("Cancel")}
</Button>
<Button size="xs" onClick={handleSaveDraft}>
{t("Save")}
</Button>
</Group>
</Stack>
);
})()}
@@ -536,20 +492,6 @@ export function ViewFilterConfigPopover({
{t("Add filter")}
</UnstyledButton>
)}
<Group justify="flex-end" gap="xs">
<Button
variant="default"
size="xs"
onClick={handleCancelDraft}
disabled={!draft && !unSaved}
>
{t("Cancel")}
</Button>
<Button size="xs" onClick={handleSaveDraft} disabled={!draft && !unSaved}>
{t("Save")}
</Button>
</Group>
</Stack>
</Popover.Dropdown>
</Popover>
@@ -73,9 +73,13 @@ export const embedProviders: IEmbedProvider[] = [
id: "vimeo",
name: "Vimeo",
regex:
/^(https:)?\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)/,
getEmbedUrl: (match) => {
return `https://player.vimeo.com/video/${match[4]}`;
/^(https:)?\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:\/([\da-zA-Z]+))?/,
getEmbedUrl: (match, url: string) => {
// preserve ?h= hash for unlisted videos
const hash =
match[5] ?? new URL(url, "https://vimeo.com").searchParams.get("h");
const base = `https://player.vimeo.com/video/${match[4]}`;
return hash ? `${base}?h=${hash}` : base;
},
},
{