Files
image-studio/view/layers/MaskControls.tsx
syntaxbullet 5915c62a9a refactor: Update bottom controls for improved styling and functionality
- Adjusted button styles across various components for consistency and better UX.
- Enhanced layout of action controls to utilize whitespace more effectively.
- Updated slider styles for a more modern appearance and improved usability.
- Refined input fields and labels for better accessibility and readability.
- Introduced new app surface styles for a cohesive design across the application.
- Added tests for canvas cursor behavior to ensure correct cursor display during operations.
2026-07-11 14:55:32 +02:00

85 lines
4.0 KiB
TypeScript

import { useEffect, useState } from "react";
import type { Asset } from "@core/asset";
import type { AppStore } from "@editor/store";
import { analyzeMask, runMaskOperation, type MaskAnalysis, type MaskRasterOperation } from "@operations/masks/rasterActions";
export function MaskStatus({ asset }: { asset: Asset }) {
const [analysis, setAnalysis] = useState<MaskAnalysis>();
useEffect(() => {
let cancelled = false;
void analyzeMask(asset)
.then((nextAnalysis) => {
if (!cancelled) setAnalysis(nextAnalysis);
})
.catch(() => {
if (!cancelled) setAnalysis(undefined);
});
return () => {
cancelled = true;
};
}, [asset.source, asset.intrinsicSize.w, asset.intrinsicSize.h]);
if (!analysis) return <span className="rounded bg-white/[0.04] px-2 py-0.5 text-[0.68rem] text-sky-100/40">Reading</span>;
return (
<span className="inline-flex min-w-0 items-center gap-1.5 rounded-md bg-white/[0.04] px-1.5 py-1 text-[0.68rem] text-sky-100/60">
<img src={analysis.thumbnail} alt="" className="h-7 w-10 rounded-md bg-black/30 object-cover ring-1 ring-white/10" />
<span className="whitespace-nowrap">Reveal {formatPercent(analysis.coverage)}</span>
<span className="whitespace-nowrap text-sky-100/45">Inpaint {formatPercent(analysis.hiddenCoverage)}</span>
</span>
);
}
export function MaskOperationButtons({ maskLayerId, maskAsset, dispatch }: { maskLayerId: string; maskAsset: Asset; dispatch: AppStore["dispatch"] }) {
return (
<>
<MaskOperationButton label="Invert" title="Invert mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "invert" }} dispatch={dispatch} />
<MaskOperationButton label="White" title="Fill mask white" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "fill", fill: "white" }} dispatch={dispatch} />
<MaskOperationButton label="Black" title="Fill mask black" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "fill", fill: "black" }} dispatch={dispatch} />
<MaskOperationButton label="Clear" title="Clear mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "fill", fill: "clear" }} dispatch={dispatch} />
<MaskOperationButton label="Feather" title="Feather mask edge" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "feather", radius: 3 }} dispatch={dispatch} />
<MaskOperationButton label="Expand" title="Expand mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "expand", radius: 3 }} dispatch={dispatch} />
<MaskOperationButton label="Contract" title="Contract mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "contract", radius: 3 }} dispatch={dispatch} />
<MaskOperationButton label="Blur" title="Blur mask edge" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "blur", radius: 2 }} dispatch={dispatch} />
<MaskOperationButton label="Clean" title="Despeckle mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "despeckle", strength: 8 }} dispatch={dispatch} />
</>
);
}
function MaskOperationButton({
label,
title,
maskLayerId,
maskAsset,
operation,
dispatch,
}: {
label: string;
title: string;
maskLayerId: string;
maskAsset: Asset;
operation: MaskRasterOperation;
dispatch: AppStore["dispatch"];
}) {
const [busy, setBusy] = useState(false);
return (
<button
type="button"
className={maskActionButtonClass()}
disabled={busy}
title={title}
onClick={() => {
setBusy(true);
void runMaskOperation(maskLayerId, maskAsset, operation, dispatch)
.finally(() => setBusy(false));
}}
>
{busy ? "..." : label}
</button>
);
}
function maskActionButtonClass() { return "rounded-md bg-white/[0.05] px-2 py-1 text-[0.68rem] font-semibold text-white/55 transition hover:bg-white/[0.09] hover:text-white disabled:pointer-events-none disabled:opacity-35"; }
function formatPercent(value: number) { return `${Math.round(value * 100)}%`; }