mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
Align project captions, fill previews, and protect HUD only during recording
This commit is contained in:
@@ -404,8 +404,7 @@ export function registerRecordingHandlers(
|
||||
ipcMain.handle(
|
||||
"start-native-screen-recording",
|
||||
async (_, source: SelectedSource, options?: NativeMacRecordingOptions) => {
|
||||
// Capture starts before the renderer publishes its recording-state
|
||||
// transition, so protect the HUD at the actual capture boundary.
|
||||
// Refresh window protection; it applies only while recording is active.
|
||||
reassertHudOverlayCaptureProtection();
|
||||
const visibleWindowBounds = source.id?.startsWith("window:")
|
||||
? await bringSelectedWindowForward(source)
|
||||
|
||||
+2
-1
@@ -153,7 +153,8 @@ function applyHudOverlayCaptureProtectionToWindow(hud: BrowserWindow, enabled: b
|
||||
}
|
||||
|
||||
try {
|
||||
hud.setContentProtection(enabled);
|
||||
// Keep the idle HUD visible to screenshots and other capture applications.
|
||||
hud.setContentProtection(enabled && hudOverlayRecordingActive);
|
||||
} catch (error) {
|
||||
console.warn("Failed to apply HUD capture protection:", error);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,11 @@ import {
|
||||
DEFAULT_WALLPAPER_RELATIVE_PATH,
|
||||
isVideoWallpaperSource,
|
||||
} from "@/lib/wallpapers";
|
||||
import { type AspectRatio, formatAspectRatioForCSS } from "@/utils/aspectRatioUtils";
|
||||
import {
|
||||
type AspectRatio,
|
||||
getAspectRatioValue,
|
||||
formatAspectRatioForCSS,
|
||||
} from "@/utils/aspectRatioUtils";
|
||||
import { AnnotationOverlay } from "./AnnotationOverlay";
|
||||
import { type CaptionEditTarget, normalizeCaptionEditText } from "./captionEditing";
|
||||
import { buildActiveCaptionLayout } from "./captionLayout";
|
||||
@@ -218,6 +222,7 @@ function getEffectiveNativeAspectRatio(
|
||||
|
||||
interface VideoPlaybackProps {
|
||||
autoPlay?: boolean;
|
||||
fillPreview?: boolean;
|
||||
clipRegions: ClipRegion[];
|
||||
videoPath: string;
|
||||
onDurationChange: (duration: number) => void;
|
||||
@@ -305,6 +310,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
{
|
||||
videoPath,
|
||||
autoPlay = false,
|
||||
fillPreview = false,
|
||||
onDurationChange,
|
||||
onPreviewReadyChange,
|
||||
onTimeUpdate,
|
||||
@@ -2364,7 +2370,17 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
ref={previewFrameRef}
|
||||
className="relative overflow-hidden"
|
||||
style={{
|
||||
width: "100%",
|
||||
width: fillPreview
|
||||
? `max(100cqw, calc(100cqh * ${getAspectRatioValue(aspectRatio, nativeAspectRatio)}))`
|
||||
: "100%",
|
||||
...(fillPreview
|
||||
? {
|
||||
position: "absolute" as const,
|
||||
left: "50%",
|
||||
top: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
}
|
||||
: {}),
|
||||
aspectRatio: formatAspectRatioForCSS(aspectRatio, nativeAspectRatio),
|
||||
borderRadius: 0,
|
||||
clipPath: "none",
|
||||
|
||||
@@ -98,9 +98,9 @@ export function ProjectCard({
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<div className="flex items-start justify-between gap-3 pt-4">
|
||||
<AccountAvatar label={accountLabel} className="!size-[46px]" />
|
||||
<div data-project-caption className="min-w-0 flex-1">
|
||||
<div className="flex items-start justify-between gap-4 pt-5">
|
||||
<AccountAvatar label={accountLabel} className="!size-[48px]" />
|
||||
<div data-project-caption className="h-12 min-w-0 flex-1">
|
||||
{editing ? (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
@@ -111,7 +111,7 @@ export function ProjectCard({
|
||||
<input
|
||||
autoFocus
|
||||
aria-label="Project name"
|
||||
className="inline-project-name h-6 w-full text-[12px] font-medium"
|
||||
className="inline-project-name h-5 w-full text-[12px] font-medium"
|
||||
value={name}
|
||||
disabled={busy}
|
||||
maxLength={120}
|
||||
@@ -128,7 +128,7 @@ export function ProjectCard({
|
||||
) : (
|
||||
<p
|
||||
title={entry.name}
|
||||
className="truncate text-[12px] font-medium leading-[18px]"
|
||||
className="truncate text-[12px] font-medium leading-5"
|
||||
>
|
||||
{entry.name}
|
||||
</p>
|
||||
|
||||
@@ -81,6 +81,7 @@ export function ProjectHoverPreview({
|
||||
}, [onFinish]);
|
||||
return (
|
||||
<div
|
||||
style={{ containerType: "size" }}
|
||||
data-project-hover-preview
|
||||
className={`pointer-events-none absolute inset-0 overflow-hidden rounded-xl ${playing ? "opacity-100" : "opacity-0"}`}
|
||||
aria-hidden="true"
|
||||
@@ -88,6 +89,7 @@ export function ProjectHoverPreview({
|
||||
<VideoPlayback
|
||||
{...editor}
|
||||
autoPlay
|
||||
fillPreview
|
||||
ref={playback}
|
||||
videoPath={data.videoUrl}
|
||||
webcamVideoPath={data.webcamUrl}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function ProjectThumbnail({
|
||||
loading="lazy"
|
||||
draggable={false}
|
||||
onError={() => setFailedSource(sourceKey)}
|
||||
className="h-full w-full object-contain"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<ImageSquare weight="fill" className="size-8 text-muted-foreground/20" />
|
||||
|
||||
@@ -317,7 +317,19 @@ test("dashboard supports creation sort, independent folders, shared settings and
|
||||
card.querySelector("[data-project-caption]")!.getBoundingClientRect().height,
|
||||
]),
|
||||
);
|
||||
for (const [avatar, caption] of heights) expect(avatar).toBe(caption);
|
||||
for (const [avatar, caption] of heights) {
|
||||
expect(avatar).toBe(caption);
|
||||
expect(avatar).toBe(48);
|
||||
}
|
||||
const spacing = await home
|
||||
.locator("[data-project-caption]")
|
||||
.first()
|
||||
.evaluate((element) => {
|
||||
const caption = element.getBoundingClientRect();
|
||||
const avatar = element.previousElementSibling!.getBoundingClientRect();
|
||||
return { gap: caption.left - avatar.right, top: caption.top - avatar.top };
|
||||
});
|
||||
expect(spacing).toEqual({ gap: 16, top: 0 });
|
||||
const avatar = home.locator('[aria-label="Local profile"]').first();
|
||||
const circle = await avatar.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
@@ -451,6 +463,20 @@ test("project hover plays a muted five-second preview and stops on exit", async
|
||||
),
|
||||
)
|
||||
.toBe(true);
|
||||
const fill = await preview.evaluate((element) => {
|
||||
const card = element.getBoundingClientRect();
|
||||
const frame = element.firstElementChild!.getBoundingClientRect();
|
||||
return {
|
||||
card: card.toJSON(),
|
||||
frame: frame.toJSON(),
|
||||
covers:
|
||||
frame.left <= card.left + 1 &&
|
||||
frame.top <= card.top + 1 &&
|
||||
frame.right >= card.right - 1 &&
|
||||
frame.bottom >= card.bottom - 1,
|
||||
};
|
||||
});
|
||||
expect(fill, JSON.stringify(fill)).toMatchObject({ covers: true });
|
||||
await expect(preview).toHaveCount(0, { timeout: 8000 });
|
||||
await page.mouse.move(0, 0);
|
||||
await card.hover();
|
||||
|
||||
Reference in New Issue
Block a user