- Replace placeholder with OpenCV.js WASM perspective correction: pick highest-confidence rectangle, compute homography, fold weighted scale corrections from secondary datums, single warpPerspective - All units now mm throughout (no cm conversion) - Simplified datum creation: two buttons (+ Rectangle / + Line) with preset chips, auto-numbered labels (Line 1, Rectangle 2, etc.) - Dimensions default to 0, user must input manually; Next button disabled until all datums have valid dimensions with tooltip hint - Fix image preview (keep object URL alive), fix canvas disappearing on breakpoint switch (single instance + ResizeObserver re-fit) - Mobile responsive: bottom sheet for datum panel, full-width canvas - Spinner on result screen during processing - Stricter ESLint config, updated Prettier to 4-space/no-semicolons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
129 lines
4.4 KiB
Vue
129 lines
4.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from "vue"
|
|
import { useMediaQuery } from "@vueuse/core"
|
|
import { useAppStore } from "@/stores/app"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip"
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetTrigger,
|
|
} from "@/components/ui/sheet"
|
|
import DatumCanvas from "@/components/DatumCanvas.vue"
|
|
import DatumPanel from "@/components/DatumPanel.vue"
|
|
|
|
const store = useAppStore()
|
|
const sheetOpen = ref(false)
|
|
const isMobile = useMediaQuery("(max-width: 767px)")
|
|
|
|
const canvasHeight = computed(() =>
|
|
isMobile.value ? "h-[calc(100vh-14rem)]" : "h-[calc(100vh-12rem)]",
|
|
)
|
|
|
|
const incompleteDatums = computed(() =>
|
|
store.datums.filter((d) => {
|
|
if (d.type === "rectangle") return d.widthMm <= 0 || d.heightMm <= 0
|
|
return d.lengthMm <= 0
|
|
}),
|
|
)
|
|
|
|
const nextTooltip = computed(() => {
|
|
if (store.datums.length === 0) return "Add at least one datum"
|
|
if (incompleteDatums.value.length === 0) return ""
|
|
const names = incompleteDatums.value.map((d) => d.label)
|
|
return `Missing dimensions: ${names.join(", ")}`
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between gap-2">
|
|
<div class="min-w-0">
|
|
<h2 class="text-xl font-semibold">Place Datums</h2>
|
|
<p class="hidden text-sm text-muted-foreground sm:block">
|
|
Add reference shapes on the image and enter their real-world
|
|
dimensions.
|
|
</p>
|
|
</div>
|
|
<div class="flex shrink-0 gap-2">
|
|
<Button variant="outline" size="sm" @click="store.goToStep(2)">
|
|
Back
|
|
</Button>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger as-child>
|
|
<span class="inline-flex">
|
|
<Button
|
|
size="sm"
|
|
:disabled="!store.canProceedToStep4"
|
|
@click="store.goToStep(4)"
|
|
>
|
|
Next
|
|
</Button>
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent v-if="nextTooltip" side="bottom">
|
|
{{ nextTooltip }}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Single layout: canvas always present, sidebar conditionally placed -->
|
|
<div
|
|
class="grid gap-4"
|
|
:class="isMobile ? 'grid-cols-1' : 'grid-cols-[1fr_360px]'"
|
|
:style="{ height: isMobile ? undefined : 'calc(100vh - 12rem)' }"
|
|
>
|
|
<div :class="canvasHeight">
|
|
<DatumCanvas />
|
|
</div>
|
|
|
|
<!-- Desktop: inline sidebar -->
|
|
<DatumPanel v-if="!isMobile" />
|
|
</div>
|
|
|
|
<!-- Mobile: bottom sheet for datums -->
|
|
<Sheet v-if="isMobile" v-model:open="sheetOpen">
|
|
<SheetTrigger as-child>
|
|
<Button variant="outline" class="w-full">
|
|
Datums ({{ store.datums.length }})
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="ml-2"
|
|
>
|
|
<path d="m18 15-6-6-6 6" />
|
|
</svg>
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent
|
|
side="bottom"
|
|
class="h-[75vh] overflow-hidden rounded-t-xl"
|
|
>
|
|
<SheetHeader>
|
|
<SheetTitle>Datums</SheetTitle>
|
|
</SheetHeader>
|
|
<div class="h-[calc(75vh-4rem)] overflow-y-auto">
|
|
<DatumPanel />
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
</div>
|
|
</template>
|