-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: implement independent padding controls #158 #318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
020469b
26f3db3
5488e88
4046c16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { Palette, Trash as Trash2, UploadSimple as Upload, X } from "@phosphor-icons/react"; | ||
| import { Link, LinkBreak, Palette, Trash as Trash2, UploadSimple as Upload, X } from "@phosphor-icons/react"; | ||
| import { AnimatePresence, LayoutGroup, motion } from "motion/react"; | ||
| import { useEffect, useMemo, useRef, useState } from "react"; | ||
| import { toast } from "sonner"; | ||
|
|
@@ -43,13 +43,17 @@ import type { | |
| CursorStyle, | ||
| EditorEffectSection, | ||
| FigureData, | ||
| Padding, | ||
| PlaybackSpeed, | ||
| WebcamOverlaySettings, | ||
| WebcamPositionPreset, | ||
| ZoomDepth, | ||
| ZoomMode, | ||
| ZoomTransitionEasing, | ||
| } from "./types"; | ||
| import { | ||
| isZeroPadding, | ||
| } from "./videoPlayback/layoutUtils"; | ||
| import { | ||
| DEFAULT_AUTO_CAPTION_SETTINGS, | ||
| DEFAULT_CROP_REGION, | ||
|
|
@@ -60,6 +64,7 @@ import { | |
| DEFAULT_CURSOR_SMOOTHING, | ||
| DEFAULT_CURSOR_STYLE, | ||
| DEFAULT_CURSOR_SWAY, | ||
| DEFAULT_PADDING, | ||
| DEFAULT_WEBCAM_CORNER_RADIUS, | ||
| DEFAULT_WEBCAM_MARGIN, | ||
| DEFAULT_WEBCAM_POSITION_PRESET, | ||
|
|
@@ -394,8 +399,8 @@ interface SettingsPanelProps { | |
| onWebcamChange?: (webcam: WebcamOverlaySettings) => void; | ||
| onUploadWebcam?: () => void; | ||
| onClearWebcam?: () => void; | ||
| padding?: number; | ||
| onPaddingChange?: (padding: number) => void; | ||
| padding?: Padding; | ||
| onPaddingChange?: (padding: Padding) => void; | ||
| frame?: string | null; | ||
| onFrameChange?: (frameId: string | null) => void; | ||
| cropRegion?: CropRegion; | ||
|
|
@@ -733,7 +738,7 @@ export function SettingsPanel({ | |
| onWebcamChange, | ||
| onUploadWebcam, | ||
| onClearWebcam, | ||
| padding = 50, | ||
| padding = DEFAULT_PADDING, | ||
| onPaddingChange, | ||
| frame = null, | ||
| onFrameChange, | ||
|
|
@@ -786,7 +791,7 @@ export function SettingsPanel({ | |
| ); | ||
| const removeBackgroundStateRef = useRef<{ | ||
| aspectRatio: AspectRatio; | ||
| padding: number; | ||
| padding: Padding; | ||
| } | null>(null); | ||
| const fileInputRef = useRef<HTMLInputElement>(null); | ||
| const builtInWallpaperPaths = useMemo( | ||
|
|
@@ -914,7 +919,7 @@ export function SettingsPanel({ | |
| const [gradient, setGradient] = useState<string>( | ||
| GRADIENTS.includes(selected) ? selected : GRADIENTS[0], | ||
| ); | ||
| const removeBackgroundEnabled = aspectRatio === "native" && padding === 0; | ||
| const removeBackgroundEnabled = aspectRatio === "native" && isZeroPadding(padding); | ||
|
|
||
| // Device frames from extension system | ||
| const [availableFrames, setAvailableFrames] = useState<FrameInstance[]>([]); | ||
|
|
@@ -1125,14 +1130,60 @@ export function SettingsPanel({ | |
| padding, | ||
| }; | ||
| onAspectRatioChange?.("native"); | ||
| onPaddingChange?.(0); | ||
| onPaddingChange?.({ top: 0, bottom: 0, left: 0, right: 0, linked: padding.linked }); | ||
| return; | ||
| } | ||
|
|
||
| if (removeBackgroundStateRef.current) { | ||
| onAspectRatioChange?.(removeBackgroundStateRef.current.aspectRatio); | ||
| onPaddingChange?.(removeBackgroundStateRef.current.padding); | ||
| const previousState = removeBackgroundStateRef.current; | ||
| if (previousState) { | ||
| onAspectRatioChange?.(previousState.aspectRatio); | ||
| onPaddingChange?.(previousState.padding); | ||
| removeBackgroundStateRef.current = null; | ||
| return; | ||
| } | ||
|
|
||
| // Fallback if the project loaded in a "background removed" state already | ||
| onAspectRatioChange?.(initialEditorPreferences.aspectRatio); | ||
| onPaddingChange?.({ ...DEFAULT_PADDING }); | ||
| }; | ||
|
|
||
| const togglePaddingLink = () => { | ||
| const isLinked = padding.linked !== false; | ||
| const nextLinked = !isLinked; | ||
| if (nextLinked) { | ||
| // Compute average for relinking to avoid sudden shifts | ||
| const avg = Math.round( | ||
| (padding.top + padding.bottom + padding.left + padding.right) / 4, | ||
| ); | ||
| onPaddingChange?.({ | ||
| top: avg, | ||
| bottom: avg, | ||
| left: avg, | ||
| right: avg, | ||
| linked: true, | ||
| }); | ||
| } else { | ||
| onPaddingChange?.({ | ||
| ...padding, | ||
| linked: false, | ||
| }); | ||
| } | ||
| }; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| const handlePaddingSideChange = (side: keyof Padding, value: number) => { | ||
| if (padding.linked !== false) { | ||
| onPaddingChange?.({ | ||
| top: value, | ||
| bottom: value, | ||
| left: value, | ||
| right: value, | ||
| linked: true, | ||
| }); | ||
| } else { | ||
| onPaddingChange?.({ | ||
| ...padding, | ||
| [side]: value, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -1288,7 +1339,7 @@ export function SettingsPanel({ | |
| const resetFrameSection = () => { | ||
| onShadowChange?.(initialEditorPreferences.shadowIntensity); | ||
| onBorderRadiusChange?.(initialEditorPreferences.borderRadius); | ||
| onPaddingChange?.(initialEditorPreferences.padding); | ||
| onPaddingChange?.(DEFAULT_PADDING); | ||
| onFrameChange?.(null); | ||
| onAspectRatioChange?.(initialEditorPreferences.aspectRatio); | ||
| removeBackgroundStateRef.current = null; | ||
|
|
@@ -1778,17 +1829,95 @@ export function SettingsPanel({ | |
| formatValue={(v) => `${v}px`} | ||
| parseInput={(text) => parseFloat(text.replace(/px$/, ""))} | ||
| /> | ||
| <SliderControl | ||
| label={tSettings("effects.padding")} | ||
| value={padding} | ||
| defaultValue={initialEditorPreferences.padding} | ||
| min={0} | ||
| max={100} | ||
| step={1} | ||
| onChange={(v) => onPaddingChange?.(v)} | ||
| formatValue={(v) => `${v}%`} | ||
| parseInput={(text) => parseFloat(text.replace(/%$/, ""))} | ||
| /> | ||
| <div className="flex flex-col gap-1.5 pt-0.5"> | ||
| <div className="flex items-center justify-between"> | ||
| <span className="text-[10px] text-muted-foreground"> | ||
| {tSettings("effects.padding")} | ||
| </span> | ||
| <button | ||
| type="button" | ||
| onClick={togglePaddingLink} | ||
| className={cn( | ||
| "p-1 rounded-md transition-colors", | ||
| padding.linked !== false | ||
| ? "text-[#2563EB] bg-[#2563EB]/10" | ||
| : "text-muted-foreground hover:bg-foreground/[0.05]", | ||
| )} | ||
| title={ | ||
| padding.linked !== false | ||
| ? tSettings("effects.paddingLinked", "Linked (Uniform)") | ||
| : tSettings("effects.paddingUnlinked", "Unlinked (Asymmetrical)") | ||
| } | ||
| > | ||
| {padding.linked !== false ? ( | ||
| <Link size={12} weight="bold" /> | ||
| ) : ( | ||
| <LinkBreak size={12} weight="bold" /> | ||
| )} | ||
| </button> | ||
| </div> | ||
|
|
||
| {padding.linked !== false ? ( | ||
| <SliderControl | ||
| label="" | ||
| value={padding.top} | ||
| defaultValue={DEFAULT_PADDING.top} | ||
| min={0} | ||
| max={100} | ||
| step={1} | ||
| onChange={(v) => handlePaddingSideChange("top", v)} | ||
| formatValue={(v) => `${v}%`} | ||
| parseInput={(text) => parseFloat(text.replace(/%$/, ""))} | ||
| /> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ) : ( | ||
| <div className="grid grid-cols-2 gap-x-3 gap-y-1.5"> | ||
| <SliderControl | ||
| label={tSettings("effects.paddingTop", "Top")} | ||
| value={padding.top} | ||
| defaultValue={DEFAULT_PADDING.top} | ||
| min={0} | ||
| max={100} | ||
| step={1} | ||
| onChange={(v) => handlePaddingSideChange("top", v)} | ||
| formatValue={(v) => `${v}%`} | ||
| parseInput={(text) => parseFloat(text.replace(/%$/, ""))} | ||
| /> | ||
| <SliderControl | ||
| label={tSettings("effects.paddingBottom", "Bottom")} | ||
| value={padding.bottom} | ||
| defaultValue={DEFAULT_PADDING.bottom} | ||
| min={0} | ||
| max={100} | ||
| step={1} | ||
| onChange={(v) => handlePaddingSideChange("bottom", v)} | ||
| formatValue={(v) => `${v}%`} | ||
| parseInput={(text) => parseFloat(text.replace(/%$/, ""))} | ||
| /> | ||
| <SliderControl | ||
| label={tSettings("effects.paddingLeft", "Left")} | ||
| value={padding.left} | ||
| defaultValue={DEFAULT_PADDING.left} | ||
| min={0} | ||
| max={100} | ||
| step={1} | ||
| onChange={(v) => handlePaddingSideChange("left", v)} | ||
| formatValue={(v) => `${v}%`} | ||
| parseInput={(text) => parseFloat(text.replace(/%$/, ""))} | ||
| /> | ||
| <SliderControl | ||
| label={tSettings("effects.paddingRight", "Right")} | ||
| value={padding.right} | ||
| defaultValue={DEFAULT_PADDING.right} | ||
| min={0} | ||
| max={100} | ||
| step={1} | ||
| onChange={(v) => handlePaddingSideChange("right", v)} | ||
| formatValue={(v) => `${v}%`} | ||
| parseInput={(text) => parseFloat(text.replace(/%$/, ""))} | ||
| /> | ||
| </div> | ||
| )} | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| </div> | ||
|
Comment on lines
+1832
to
+1920
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find all settings locale files and show the "effects.padding" entry + surrounding keys
fd -t f 'settings.json' | xargs -I {} sh -c 'echo "=== {} ==="; rg -nP -C2 "\"padding\"" {} || true'
# Also check what tSettings resolution looks like for effects.padding vs effects.padding.top
rg -nP -C2 '"padding"\s*:' --type=jsonRepository: webadderall/Recordly Length of output: 2399 🏁 Script executed: #!/bin/bash
# Check for the nested padding keys that the code references
rg '"effects\.padding\.(linked|unlinked|top|bottom|left|right)"' src/i18n/locales/ || echo "No nested padding keys found"
# Also show the full effects section structure from one locale to understand the hierarchy
echo "=== Full effects section in en/settings.json ==="
rg -A 20 '"effects":\s*{' src/i18n/locales/en/settings.json | head -30Repository: webadderall/Recordly Length of output: 250 🏁 Script executed: #!/bin/bash
# Show the full effects section from en locale
rg -A 50 '"effects":' src/i18n/locales/en/settings.json | head -60Repository: webadderall/Recordly Length of output: 1984 🏁 Script executed: #!/bin/bash
# Search for padding section in effects
rg -A 2 -B 2 '"padding"' src/i18n/locales/en/settings.json
# Also check the full JSON structure to understand nesting
echo "=== Checking if effects.padding is an object or string ==="
python3 <<'EOF'
import json
with open('src/i18n/locales/en/settings.json', 'r') as f:
data = json.load(f)
if 'effects' in data and 'padding' in data['effects']:
padding_val = data['effects']['padding']
print(f"Type of effects.padding: {type(padding_val).__name__}")
print(f"Value: {padding_val}")
if isinstance(padding_val, dict):
print(f"Keys: {list(padding_val.keys())}")
EOFRepository: webadderall/Recordly Length of output: 286 i18n key collision: Line 1835 calls Add the corresponding nested keys to each locale file, or use a disjoint namespace (e.g., - title={
- padding.linked !== false
- ? tSettings("effects.padding.linked", "Linked (Uniform)")
- : tSettings("effects.padding.unlinked", "Unlinked (Asymmetrical)")
- }
+ title={
+ padding.linked !== false
+ ? tSettings("padding.linked", "Linked (Uniform)")
+ : tSettings("padding.unlinked", "Unlinked (Asymmetrical)")
+ }Then add 🤖 Prompt for AI Agents |
||
| <div className="flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-1.5"> | ||
| <span className="text-[10px] text-muted-foreground"> | ||
| {tSettings("effects.removeBackground")} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.