feat(ui): add frame aspect ratio, orientation, and alignment controls#20
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughSummary by CodeRabbitリリースノート
Walkthroughフレーム設定機能をサポートするため、キャンバス描画ロジックをアスペクト比・向き・縦位置に対応させるよう拡張。フォーム・セグメントコントロール用CSS基盤を追加し、stateとUIコンポーネントを新規実装。 ChangesFrame Settings and Canvas Orientation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR resolves issue #8 by removing the hard-coded output frame ratio and adding UI controls so users can choose the frame aspect ratio, orientation, and vertical alignment—improving rendering for portrait photos.
Changes:
- Added frame settings state (aspect ratio preset/custom, orientation, alignment) and integrated them into the canvas layout calculation.
- Updated the canvas drawing logic to compute final canvas dimensions based on a chosen target ratio and to reposition the image/text accordingly.
- Extended sidebar UI and styles to support
<select>inputs and segmented controls for the new settings.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| frontend/src/App.tsx | Adds frame settings UI/state and updates drawCanvas to support configurable aspect ratios, orientation flipping, and vertical alignment. |
| frontend/src/App.css | Styles the new aspect ratio <select> and segmented controls used by the frame settings UI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let targetRatio = 4300 / 3618; | ||
| if (aspectRatioPreset === "custom") { | ||
| if (customRatioW > 0 && customRatioH > 0) { | ||
| targetRatio = customRatioW / customRatioH; | ||
| } else { | ||
| targetRatio = img.width / img.height; | ||
| } | ||
| } else { | ||
| const [w, h] = aspectRatioPreset.split(':').map(Number); | ||
| if (w && h) targetRatio = w / h; | ||
| } | ||
|
|
||
| // Apply orientation flip | ||
| if (orientation === "portrait" && targetRatio > 1) { | ||
| targetRatio = 1 / targetRatio; | ||
| } else if (orientation === "landscape" && targetRatio < 1) { | ||
| targetRatio = 1 / targetRatio; | ||
| } | ||
|
|
||
| const minCanvasWidth = img.width + (minFramePadding * 2); | ||
| const minCanvasHeight = img.height + minFramePadding + minBottomSpace; | ||
|
|
||
| // 全体の仕上がりを 4300 : 3618 の比率に強制する | ||
| const targetRatio = 4300 / 3618; | ||
| // まず幅を基準に高さを計算 | ||
| let finalCanvasWidth = minCanvasWidth; | ||
| let finalCanvasHeight = Math.floor(finalCanvasWidth / targetRatio); | ||
|
|
||
| if (finalCanvasHeight < minCanvasHeight) { | ||
| // 高さが足りない場合は、最小の高さを基準にして幅を拡張 | ||
| finalCanvasHeight = minCanvasHeight; | ||
| finalCanvasWidth = Math.floor(finalCanvasHeight * targetRatio); | ||
| } |
| <label>Orientation</label> | ||
| <div className={`segmented-control ${aspectRatioPreset === '1:1' ? 'disabled' : ''}`}> | ||
| <button | ||
| className={`segment ${orientation === 'landscape' ? 'active' : ''}`} | ||
| onClick={() => setOrientation('landscape')} | ||
| disabled={aspectRatioPreset === '1:1'} | ||
| > | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2" ry="2"></rect></svg> | ||
| Landscape | ||
| </button> | ||
| <button | ||
| className={`segment ${orientation === 'portrait' ? 'active' : ''}`} | ||
| onClick={() => setOrientation('portrait')} | ||
| disabled={aspectRatioPreset === '1:1'} | ||
| > | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="3" width="14" height="18" rx="2" ry="2"></rect></svg> | ||
| Portrait | ||
| </button> | ||
| </div> | ||
| </div> | ||
| <div className="input-group"> | ||
| <label>Vertical Alignment</label> | ||
| <div className="segmented-control"> | ||
| <button | ||
| className={`segment ${alignment === 'top' ? 'active' : ''}`} | ||
| onClick={() => setAlignment('top')} | ||
| > | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="4" x2="20" y2="4"></line><rect x="8" y="8" width="8" height="8" rx="1" ry="1"></rect></svg> | ||
| Top | ||
| </button> | ||
| <button | ||
| className={`segment ${alignment === 'center' ? 'active' : ''}`} | ||
| onClick={() => setAlignment('center')} | ||
| > | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="12" x2="20" y2="12"></line><rect x="8" y="8" width="8" height="8" rx="1" ry="1"></rect></svg> | ||
| Center | ||
| </button> | ||
| </div> |
Resolved #8