Merge pull request #299 from jonvdveen/feat/resolution-condition
feat: Resolution condition for workflow IF/SWITCH nodes (megapixels, short/long edge)
This commit is contained in:
@@ -27,6 +27,7 @@ const conditionTypeLabels: Record<ConditionType, string> = {
|
||||
asset_type: "Asset Type",
|
||||
iso_range: "ISO Range",
|
||||
focal_length: "Focal Length",
|
||||
resolution: "Resolution",
|
||||
rating: "Rating",
|
||||
is_favorited: "Favorited",
|
||||
file_size: "File Size",
|
||||
@@ -197,6 +198,25 @@ function ConditionFields({ condition, onChange }: { condition: ICondition; onCha
|
||||
<Input className="h-7 text-xs w-16" type="number" min={1} max={5} placeholder="Max" value={condition.max ?? ""} onChange={(e) => onChange({ ...condition, max: parseInt(e.target.value) || undefined })} />
|
||||
</div>
|
||||
);
|
||||
case "resolution": {
|
||||
const unit = (condition.metric || "megapixels") === "megapixels" ? "MP" : "px";
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Select value={condition.metric || "megapixels"} onValueChange={(v) => onChange({ ...condition, metric: v })}>
|
||||
<SelectTrigger className="h-7 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="megapixels">Megapixels</SelectItem>
|
||||
<SelectItem value="short_edge">Short Edge (px)</SelectItem>
|
||||
<SelectItem value="long_edge">Long Edge (px)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input className="h-7 text-xs" type="number" min={0} step="any" placeholder={`Min ${unit}`} value={condition.min ?? ""} onChange={(e) => onChange({ ...condition, min: e.target.value === "" ? undefined : parseFloat(e.target.value) })} />
|
||||
<Input className="h-7 text-xs" type="number" min={0} step="any" placeholder={`Max ${unit}`} value={condition.max ?? ""} onChange={(e) => onChange({ ...condition, max: e.target.value === "" ? undefined : parseFloat(e.target.value) })} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "is_favorited":
|
||||
return (
|
||||
<Select value={condition.value === false ? "false" : "true"} onValueChange={(v) => onChange({ ...condition, value: v === "true" })}>
|
||||
|
||||
@@ -16,6 +16,7 @@ const conditionTypeLabels: Record<ConditionType, string> = {
|
||||
asset_type: "Asset Type",
|
||||
iso_range: "ISO Range",
|
||||
focal_length: "Focal Length",
|
||||
resolution: "Resolution",
|
||||
rating: "Rating",
|
||||
is_favorited: "Favorited",
|
||||
file_size: "File Size",
|
||||
@@ -100,6 +101,16 @@ export function formatConditionSummary(c: ICondition): string {
|
||||
if (c.max != null) return `${label}: ≤${c.max}`;
|
||||
return label;
|
||||
}
|
||||
case "resolution": {
|
||||
const metricNames: Record<string, string> = { megapixels: "MP", short_edge: "short edge", long_edge: "long edge" };
|
||||
const metric = c.metric || "megapixels";
|
||||
const unit = metric === "megapixels" ? "MP" : "px";
|
||||
const name = metric === "megapixels" ? "" : `${metricNames[metric]} `;
|
||||
if (c.min != null && c.max != null) return `${label}: ${name}${c.min}–${c.max}${unit}`;
|
||||
if (c.min != null) return `${label}: ${name}≥${c.min}${unit}`;
|
||||
if (c.max != null) return `${label}: ${name}≤${c.max}${unit}`;
|
||||
return label;
|
||||
}
|
||||
case "is_favorited":
|
||||
return c.value === false ? "Not Favorited" : "Favorited";
|
||||
case "file_size": {
|
||||
|
||||
@@ -92,6 +92,21 @@ function buildSingleCondition(c: ICondition): SQL | undefined {
|
||||
return parts.length > 0 ? and(...parts)! : undefined;
|
||||
}
|
||||
|
||||
case "resolution": {
|
||||
// Short/long edge are orientation-agnostic; megapixels = width × height / 1e6.
|
||||
const metric = c.metric || "megapixels";
|
||||
const value =
|
||||
metric === "short_edge"
|
||||
? sql`LEAST(${exif.exifImageWidth}, ${exif.exifImageHeight})`
|
||||
: metric === "long_edge"
|
||||
? sql`GREATEST(${exif.exifImageWidth}, ${exif.exifImageHeight})`
|
||||
: sql`(${exif.exifImageWidth}::bigint * ${exif.exifImageHeight}) / 1000000.0`;
|
||||
const parts: SQL[] = [];
|
||||
if (c.min !== undefined && c.min !== null) parts.push(sql`${value} >= ${c.min}`);
|
||||
if (c.max !== undefined && c.max !== null) parts.push(sql`${value} <= ${c.max}`);
|
||||
return parts.length > 0 ? and(...parts)! : undefined;
|
||||
}
|
||||
|
||||
case "file_size": {
|
||||
// min/max in megabytes (fractional allowed)
|
||||
const parts: SQL[] = [];
|
||||
|
||||
Vendored
+1
-1
@@ -54,7 +54,7 @@ export type ConditionType =
|
||||
| "date_range" | "date_relative" | "day_of_week"
|
||||
| "camera_make" | "camera_model" | "lens"
|
||||
| "asset_type" | "iso_range" | "focal_length"
|
||||
| "rating" | "is_favorited"
|
||||
| "rating" | "is_favorited" | "resolution"
|
||||
| "file_size" | "filename" | "file_extension"
|
||||
| "face_count" | "time_of_day"
|
||||
| "not_in_album" | "not_in_specific_album";
|
||||
|
||||
Reference in New Issue
Block a user