Each thumbnail now shows a yellow warning triangle in its action stack when at least one approved device's orientation does not match the photo's crop orientation. Tap opens the edit flow with that device set as the crop context, so the existing in-crop-tool indicator can guide the re-crop. Photos without a stored cropOrientation fall back to inferring it from the saved cropParams aspect, so older uploads aren't left blind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,20 @@
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="library__thumb-actions">
|
||||
<button
|
||||
v-if="mismatchedDevice(image)"
|
||||
class="library__action-btn library__action-btn--warn"
|
||||
type="button"
|
||||
:aria-label="`Crop orientation does not match ${mismatchedDevice(image)!.name}; tap to re-crop`"
|
||||
:title="`Cropped ${photoCropOrientation(image)}, but ${mismatchedDevice(image)!.name} is set to ${mismatchedDevice(image)!.orientation}.`"
|
||||
@click="startEdit(image, mismatchedDevice(image)!.id)"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
|
||||
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
||||
<line x1="12" y1="9" x2="12" y2="13"/>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="library__action-btn"
|
||||
type="button"
|
||||
@@ -280,11 +294,11 @@ function openShare(id: number) {
|
||||
|
||||
const editingId = ref<number | null>(null)
|
||||
|
||||
async function startEdit(image: Image) {
|
||||
async function startEdit(image: Image, deviceId?: number) {
|
||||
if (editingId.value) return
|
||||
editingId.value = image.id
|
||||
try {
|
||||
await uploadStore.initEdit(image)
|
||||
await uploadStore.initEdit(image, deviceId)
|
||||
router.push('/upload')
|
||||
} catch {
|
||||
toast.show('Could not load photo for editing', 'error')
|
||||
@@ -293,6 +307,30 @@ async function startEdit(image: Image) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Orientation mismatch ──────────────────────────────────────────────────────
|
||||
|
||||
// Photo's effective crop orientation. Prefers the explicit cropOrientation
|
||||
// field; falls back to inferring from cropParams aspect for legacy uploads
|
||||
// predating the field. Returns null when neither is available.
|
||||
function photoCropOrientation(image: Image): 'landscape' | 'portrait' | null {
|
||||
if (image.cropOrientation) return image.cropOrientation
|
||||
const p = image.cropParams
|
||||
if (!p?.natW || !p?.natH) return null
|
||||
return p.natW >= p.natH ? 'landscape' : 'portrait'
|
||||
}
|
||||
|
||||
// First approved device whose orientation doesn't match the photo's crop
|
||||
// orientation. Drives the warning triangle and the click target for re-crop.
|
||||
function mismatchedDevice(image: Image): Device | null {
|
||||
const photoOri = photoCropOrientation(image)
|
||||
if (!photoOri) return null
|
||||
for (const id of image.approvedDeviceIds) {
|
||||
const d = devicesStore.devices.find(dev => dev.id === id)
|
||||
if (d && d.orientation !== photoOri) return d
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// ── Lock ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function toggleLock(imageId: number, device: Device) {
|
||||
@@ -512,6 +550,11 @@ async function doDelete() {
|
||||
&:disabled { opacity: 0.5; cursor: default; }
|
||||
&:hover:not(:disabled) { background: rgba(0, 0, 0, 0.75); }
|
||||
&--danger:hover:not(:disabled) { background: rgba(180, 0, 0, 0.8); }
|
||||
|
||||
&--warn {
|
||||
background: var(--color-warning, #f59e0b);
|
||||
&:hover:not(:disabled) { background: color-mix(in srgb, var(--color-warning, #f59e0b) 85%, black); }
|
||||
}
|
||||
}
|
||||
|
||||
&__approvals {
|
||||
|
||||
Reference in New Issue
Block a user