feat(transform): passo de escolha entre 4 posições (0/90/180/270) antes do ajuste fino
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
Ao clicar em Orientar, primeiro mostra 4 rotações da imagem para escolher a orientação grossa; ao clicar numa, abre o ajuste fino (escolhida + original) com botão para voltar. Rotação salva = orientação escolhida + ajuste fino.
This commit is contained in:
@@ -14,6 +14,8 @@ function formatDate(dateString) {
|
|||||||
export default function TransformModal({ isOpen, onClose, image, onSaved }) {
|
export default function TransformModal({ isOpen, onClose, image, onSaved }) {
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const [activeView, setActiveView] = useState('transform'); // 'transform' | 'gto'
|
const [activeView, setActiveView] = useState('transform'); // 'transform' | 'gto'
|
||||||
|
const [step, setStep] = useState('choose'); // 'choose' (4 posições) | 'finetune'
|
||||||
|
const [baseRotation, setBaseRotation] = useState(0); // 0 | 90 | 180 | 270
|
||||||
const [showInfo, setShowInfo] = useState(false);
|
const [showInfo, setShowInfo] = useState(false);
|
||||||
const [fineTuneAngle, setFineTuneAngle] = useState(0);
|
const [fineTuneAngle, setFineTuneAngle] = useState(0);
|
||||||
const [remark, setRemark] = useState('');
|
const [remark, setRemark] = useState('');
|
||||||
@@ -29,6 +31,8 @@ export default function TransformModal({ isOpen, onClose, image, onSaved }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
setFineTuneAngle(0);
|
setFineTuneAngle(0);
|
||||||
|
setBaseRotation(0);
|
||||||
|
setStep('choose');
|
||||||
setRemark('');
|
setRemark('');
|
||||||
setShowInfo(false);
|
setShowInfo(false);
|
||||||
setActiveView('transform');
|
setActiveView('transform');
|
||||||
@@ -70,15 +74,17 @@ export default function TransformModal({ isOpen, onClose, image, onSaved }) {
|
|||||||
|
|
||||||
const resetAngle = () => setFineTuneAngle(0);
|
const resetAngle = () => setFineTuneAngle(0);
|
||||||
|
|
||||||
|
const totalRotation = ((baseRotation + fineTuneAngle) % 360 + 360) % 360;
|
||||||
|
|
||||||
const saveTransform = async () => {
|
const saveTransform = async () => {
|
||||||
if (fineTuneAngle === 0) {
|
if (totalRotation === 0) {
|
||||||
showToast('Nenhuma edição aplicada.', 'error');
|
showToast('Nenhuma edição aplicada.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await api.post(`/images/${image.id}/transform`, {
|
await api.post(`/images/${image.id}/transform`, {
|
||||||
rotation: fineTuneAngle,
|
rotation: totalRotation,
|
||||||
flipH: false,
|
flipH: false,
|
||||||
flipV: false,
|
flipV: false,
|
||||||
remark
|
remark
|
||||||
@@ -180,8 +186,44 @@ export default function TransformModal({ isOpen, onClose, image, onSaved }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* View: Transformação */}
|
{/* View: Transformação — PASSO 1: escolher entre 4 posições */}
|
||||||
{activeView === 'transform' && (
|
{activeView === 'transform' && step === 'choose' && (
|
||||||
|
<div className="transform-view-wrapper">
|
||||||
|
<p style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: '0.85rem', margin: '4px 0 16px' }}>
|
||||||
|
Escolha a melhor orientação. Depois você faz o ajuste fino.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
|
||||||
|
{[0, 90, 180, 270].map((deg) => (
|
||||||
|
<button
|
||||||
|
key={deg}
|
||||||
|
type="button"
|
||||||
|
onClick={() => { setBaseRotation(deg); setFineTuneAngle(0); setStep('finetune'); }}
|
||||||
|
style={{
|
||||||
|
border: '2px solid var(--glass-border)', borderRadius: 10, background: '#0b1220',
|
||||||
|
padding: 8, cursor: 'pointer', display: 'flex', flexDirection: 'column',
|
||||||
|
alignItems: 'center', gap: 6, transition: 'border-color .2s'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => (e.currentTarget.style.borderColor = 'var(--primary-color)')}
|
||||||
|
onMouseLeave={(e) => (e.currentTarget.style.borderColor = 'var(--glass-border)')}
|
||||||
|
title={`Usar ${deg}°`}
|
||||||
|
>
|
||||||
|
<div style={{ width: '100%', aspectRatio: '1 / 1', display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }}>
|
||||||
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
alt={`${deg}°`}
|
||||||
|
loading="lazy"
|
||||||
|
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', transform: `rotate(${deg}deg)` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--text-secondary)' }}>{deg}°</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* View: Transformação — PASSO 2: ajuste fino (escolhida + original) */}
|
||||||
|
{activeView === 'transform' && step === 'finetune' && (
|
||||||
<div className="transform-view-wrapper">
|
<div className="transform-view-wrapper">
|
||||||
<div className="new-transform-layout">
|
<div className="new-transform-layout">
|
||||||
{/* Original */}
|
{/* Original */}
|
||||||
@@ -192,20 +234,27 @@ export default function TransformModal({ isOpen, onClose, image, onSaved }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Preview (com rotação aplicada) */}
|
{/* Preview (orientação escolhida + ajuste fino) */}
|
||||||
<div className="transform-panel panel-preview">
|
<div className="transform-panel panel-preview">
|
||||||
<h3 className="panel-title">Preview</h3>
|
<h3 className="panel-title">Preview ({totalRotation}°)</h3>
|
||||||
<div className="img-container">
|
<div className="img-container">
|
||||||
<img
|
<img
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
style={{ transform: `rotate(${fineTuneAngle}deg)` }}
|
style={{ transform: `rotate(${baseRotation + fineTuneAngle}deg)` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Controles de rotação */}
|
{/* Controles de rotação */}
|
||||||
<div className="transform-sidebar">
|
<div className="transform-sidebar">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary btn-small"
|
||||||
|
onClick={() => setStep('choose')}
|
||||||
|
style={{ width: '100%', marginBottom: 12 }}
|
||||||
|
title="Voltar para escolher a orientação"
|
||||||
|
>← Posições</button>
|
||||||
<h3 className="sidebar-title" style={{ fontSize: '0.65rem', textTransform: 'uppercase', letterSpacing: 1, color: 'var(--text-muted)', marginBottom: 12 }}>Ajuste</h3>
|
<h3 className="sidebar-title" style={{ fontSize: '0.65rem', textTransform: 'uppercase', letterSpacing: 1, color: 'var(--text-muted)', marginBottom: 12 }}>Ajuste</h3>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', width: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', width: '100%' }}>
|
||||||
{[
|
{[
|
||||||
|
|||||||
Reference in New Issue
Block a user