4 Commits

Author SHA1 Message Date
VPS 4 Deploy Agent 0d0ad5e4a6 fix: sidebar height scroll, saveTransform bypassing flip config and api gto insert syntax parse
continuous-integration/webhook Deploy concluído (VPS4)
2026-06-01 00:09:05 +02:00
VPS 4 Deploy Agent 8a6923af54 chore(deploy): bump version
continuous-integration/webhook Ignorado: bump version
2026-05-31 23:59:36 +02:00
VPS 4 Deploy Agent b6745a196d feat(ui): simplify orientation step replacing degree rotation array by basic transforms
continuous-integration/webhook Deploy concluído (VPS4)
2026-05-31 23:56:14 +02:00
VPS 4 Deploy Agent 4b4a4208ce chore(deploy): bump version
continuous-integration/webhook Ignorado: bump version
2026-05-31 21:42:33 +02:00
5 changed files with 46 additions and 64 deletions
@@ -1 +1 @@
2.1.49
2.1.53
@@ -64,6 +64,8 @@ body {
================================================================ */
.sidebar {
width: var(--sidebar-width);
height: 100vh;
overflow-y: auto;
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
@@ -153,7 +153,7 @@ export default function ImageDetailsPage() {
const totalRotation = ((baseRotation + fineTuneAngle) % 360 + 360) % 360;
const saveTransform = async () => {
if (totalRotation === 0 && remark === (image.remark || '')) {
if (totalRotation === 0 && !baseFlipH && !baseFlipV && remark === (image.remark || '')) {
showToast('Nenhuma edição aplicada.', 'error');
return;
}
@@ -193,17 +193,17 @@ export default function ImageDetailsPage() {
const cssTransform = (deg, flipH, flipV) =>
`scaleX(${flipH ? -1 : 1}) scaleY(${flipV ? -1 : 1}) rotate(${deg}deg)`;
const choosePages = [
{ flipH: false, flipV: false, label: 'Rotações' },
{ flipH: true, flipV: false, label: 'Espelhado ⇆' },
{ flipH: false, flipV: true, label: 'Espelhado ⇅' },
const initialOptions = [
{ id: 'normal', deg: 0, flipH: false, flipV: false, label: '' },
{ id: 'fliph', deg: 0, flipH: true, flipV: false, label: 'Espelho ⇆' },
{ id: 'flipv', deg: 0, flipH: false, flipV: true, label: 'Espelho ⇅' },
{ id: 'rot90', deg: 90, flipH: false, flipV: false, label: 'Rotacionar +90°' },
];
const pickOrientation = (deg) => {
const pg = choosePages[choosePage];
setBaseRotation(deg);
setBaseFlipH(pg.flipH);
setBaseFlipV(pg.flipV);
const pickOrientation = (opt) => {
setBaseRotation(opt.deg);
setBaseFlipH(opt.flipH);
setBaseFlipV(opt.flipV);
setFineTuneAngle(0);
setStep('finetune');
};
@@ -282,9 +282,8 @@ export default function ImageDetailsPage() {
};
// Gerencia opções (4 no total). No mobile exibe APENAS UMA GIGANTE por vez.
const allRotations = [0, 90, 180, 270];
const useOneCard = isMobile;
const currentRotations = useOneCard ? [allRotations[orientationSubpage]] : allRotations;
const currentOptions = useOneCard ? [initialOptions[orientationSubpage]] : initialOptions;
return (
<div className="layout">
@@ -352,25 +351,6 @@ export default function ImageDetailsPage() {
Escolha a melhor orientação inicial.
</p>
{/* Controles Principais de Categoria (Rotações, Espelhos) */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16, marginBottom: 20 }}>
<button
type="button"
className="btn btn-secondary"
onClick={() => { setChoosePage((p) => Math.max(0, p - 1)); setOrientationSubpage(0); }}
disabled={choosePage === 0}
> Categoria</button>
<span style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--text-secondary)' }}>
{choosePages[choosePage].label} ({choosePage + 1}/{choosePages.length})
</span>
<button
type="button"
className="btn btn-secondary"
onClick={() => { setChoosePage((p) => Math.min(choosePages.length - 1, p + 1)); setOrientationSubpage(0); }}
disabled={choosePage === choosePages.length - 1}
>Categoria </button>
</div>
{/* Controles de Rotação (Mobile exibe 1 por vez) */}
{useOneCard && (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16, marginBottom: 16 }}>
@@ -387,36 +367,33 @@ export default function ImageDetailsPage() {
)}
<div style={{ display: 'grid', gridTemplateColumns: useOneCard ? '1fr' : 'repeat(4, 1fr)', gap: 16 }}>
{currentRotations.map((deg) => {
const pg = choosePages[choosePage];
return (
<div
key={deg}
style={{
border: '1px solid var(--border-color)', borderRadius: 10, background: 'var(--bg-color)',
padding: useOneCard ? 0 : 16, display: 'flex', flexDirection: 'column',
alignItems: 'center', transition: 'all .2s ease', overflow: 'hidden'
}}
>
<div style={{ width: '100%', aspectRatio: useOneCard ? 'auto' : '1 / 1', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: useOneCard ? 20 : 0, background: '#0b1220' }}>
<img
src={imageUrl}
alt={`${deg}°`}
loading="lazy"
style={{ width: useOneCard ? '100%' : '80%', maxHeight: useOneCard ? '50vh' : '100%', objectFit: 'contain', transform: cssTransform(deg, pg.flipH, pg.flipV), transformOrigin: 'center' }}
/>
</div>
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', width: '100%', background: 'var(--bg-surface)' }}>
<span style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--text-primary)' }}>
{deg}°{pg.flipH ? ' ⇆' : pg.flipV ? ' ⇅' : ''}
</span>
<button className="btn btn-primary" style={{ width: '100%' }} onClick={() => pickOrientation(deg)}>
Escolher esta
</button>
</div>
{currentOptions.map((opt) => (
<div
key={opt.id}
style={{
border: '1px solid var(--border-color)', borderRadius: 10, background: 'var(--bg-surface)',
padding: useOneCard ? 0 : 16, display: 'flex', flexDirection: 'column',
alignItems: 'center', transition: 'all .2s ease', overflow: 'hidden'
}}
>
<div style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: useOneCard ? 20 : 0, background: 'var(--bg-surface)' }}>
<img
src={imageUrl}
alt={opt.label}
loading="lazy"
style={{ width: '100%', maxHeight: useOneCard ? '50vh' : 280, objectFit: 'contain', transform: cssTransform(opt.deg, opt.flipH, opt.flipV), transformOrigin: 'center' }}
/>
</div>
);
})}
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', width: '100%', background: 'var(--bg-surface)' }}>
<span style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--text-primary)' }}>
{opt.label}
</span>
<button className="btn btn-primary" style={{ width: '100%' }} onClick={() => pickOrientation(opt)}>
Escolher esta
</button>
</div>
</div>
))}
</div>
</div>
)}
+5 -2
View File
@@ -169,16 +169,19 @@ router.post('/:id/images', async (req, res) => {
const { image_id } = req.body;
if (!image_id) return res.status(400).json({ error: 'image_id é obrigatório' });
const gId = parseInt(req.params.id, 10);
const imgId = parseInt(image_id, 10);
// Verificar se já está vinculada
const existing = await db.get(
'SELECT id FROM gto_images WHERE gto_id = $1 AND image_id = $2',
[req.params.id, image_id]
[gId, imgId]
);
if (existing) return res.status(409).json({ error: 'Imagem já vinculada a esta GTO' });
await db.run(
'INSERT INTO gto_images (gto_id, image_id) VALUES ($1, $2) RETURNING id',
[req.params.id, image_id]
[gId, imgId]
);
res.status(201).json({ success: true, message: 'Imagem vinculada à GTO' });
} catch (err) {
+1 -1
View File
@@ -1 +1 @@
2.1.49
2.1.51