ไฮไลต์เสร็จแล้ว popup ซ้อนกันอีก 2 รูปแบบ (tooltip ทับแผงบน desktop + เมนู tool ค้างเปิดบน iOS) + งาน TEMP-DEBUG ของปัญหา pencil ลากไม่ติดบน iPad:
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 44s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 13s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 44s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 13s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
ส่วนที่ 1 — แก้ popup ซ้อนตอนสร้างไฮไลต์ (bug report: กด tool highlight → ไป highlight → เสร็จแล้วมี popup ของแก้ไขแสดงอีกอัน): (1) Tooltip hover (N1) ซ่อนเมื่อรายการที่ชี้ = selectedId — เดิมลากคลุมจบแล้วเคอร์เซอร์ยังอยู่บนไฮไลต์ใหม่พอดี auto-select เปิดแผง selection แล้ว tooltip ยังโผล่ซ้อนอีกกล่องบนตำแหน่งเดียวกัน; ชี้รายการอื่นที่ไม่ได้เลือก tooltip ยังทำงานตามเดิม (2) handleCreateAnnotation ตรง branch AUTO_SELECT_TYPES ปิดเมนู dropdown ทั้ง 5 + popup bookmark ก่อนแผงโผล่ — บน iOS การลาก (คลุมข้อความ/วาดเส้น) ไม่ยิง compat mousedown ให้ outside-click ปิดเมนูเอง (Android/desktop ปิดตั้งแต่เริ่มกดแล้ว) ไม่ปิดตรงนี้ = เมนูค้างเปิดซ้อนแผงบนจอสัมผัส - sync rules/pdf-viewer.md: กติกา popup ทีละชุดเพิ่มกรณี auto-select ปิดเมนู + เงื่อนไขซ่อน tooltip เมื่อ hovered = selectedId - verify: tsc ผ่าน + Playwright 6/6 — desktop: ไฮไลต์เสร็จแผงโผล่/ไม่มี tooltip ซ้อน/ชี้รายการอื่น tooltip กลับมา; เลียน iOS (dispatch touch PointerEvent ล้วนไม่มี compat mousedown): ลากคลุม commit จริง + แผงโผล่ + เมนูที่เคยเปิดค้างถูกปิด ส่วนที่ 2 — TEMP-DEBUG ปัญหา pencil ลากแล้วบางทีไม่ commit บน iPad (ทำคู่กันระหว่าง debug จริงเครื่อง ยังไม่ถึงเวลาเก็บกวาด): - components/DebugConsole.tsx (ใหม่): เปิด Eruda จาก CDN เมื่อ URL มี ?debug — อ่าน console บนจอ iPad จริงโดยไม่ต้องต่อ Safari Web Inspector (โหลดพลาดมีแบนเนอร์แจ้ง) + mount ใน app/annotate/[id]/page.tsx — ทั้งคู่ mark TEMP-DEBUG ไว้ลบพร้อมกัน - hooks/useCustomSelection.ts: log [debug] ตำแหน่งสำคัญของ gesture (pointerdown / move แรกของ pointerId นั้นหลัง down — กัน log 60-120Hz ท่วม console จนเปลี่ยนพฤติกรรมที่ดูอยู่เอง / แยก pointerup vs pointercancel / นิ้วที่สองถูกเมิน) + เก็บความเห็นใหม่ว่า guard นิ้วที่สองครอบคลุม touch ปลอมจาก palm rejection (มือพักบนจอระหว่างลากด้วย pen) - components/UploadFileForm.tsx: ปุ่มอัปโหลด ml-auto ชิดขวา (เดิมติด input ไปฝั่งซ้าย) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
9cc38835f0
commit
5c8e52bd62
6 changed files with 92 additions and 7 deletions
File diff suppressed because one or more lines are too long
|
|
@ -12,6 +12,7 @@ import {
|
||||||
toReplyDTO,
|
toReplyDTO,
|
||||||
} from "@/db/schema";
|
} from "@/db/schema";
|
||||||
import PDFViewer from "@/components/pdf/PDFViewer";
|
import PDFViewer from "@/components/pdf/PDFViewer";
|
||||||
|
import DebugConsole from "@/components/DebugConsole"; // TEMP-DEBUG (ลบพร้อมไฟล์)
|
||||||
|
|
||||||
const UUID_RE =
|
const UUID_RE =
|
||||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
|
@ -53,11 +54,15 @@ export default async function AnnotatePage({
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PDFViewer
|
<>
|
||||||
|
{/* TEMP-DEBUG: อ่าน log บนจอ iPad ผ่าน Eruda เมื่อเปิดหน้าพร้อม ?debug */}
|
||||||
|
<DebugConsole />
|
||||||
|
<PDFViewer
|
||||||
file={{ id: file.id, title: file.title, pdfUrl: file.pdfUrl }}
|
file={{ id: file.id, title: file.title, pdfUrl: file.pdfUrl }}
|
||||||
initialAnnotations={annotationRows.map(toAnnotationDTO)}
|
initialAnnotations={annotationRows.map(toAnnotationDTO)}
|
||||||
initialBookmarks={bookmarkRows.map(toBookmarkDTO)}
|
initialBookmarks={bookmarkRows.map(toBookmarkDTO)}
|
||||||
initialReplies={replyRows.map(toReplyDTO)}
|
initialReplies={replyRows.map(toReplyDTO)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
components/DebugConsole.tsx
Normal file
34
components/DebugConsole.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
/** TEMP-DEBUG: เปิด Eruda (devtools ลอยบนหน้าจอ — ปุ่ม gear มุมขวาล่าง) เมื่อเปิด
|
||||||
|
* หน้าพร้อม ?debug ไว้อ่าน console.warn ของ log [debug] บน iPad จริง (ไม่มี Mac
|
||||||
|
* ต่อ Safari Web Inspector) — โหลดสคริปต์จาก CDN เท่านั้น ไม่แตะ dependencies
|
||||||
|
* ของ project — เคลียร์ debug เสร็จ = ลบไฟล์นี้ + บรรทัด import/render ใน
|
||||||
|
* app/annotate/[id]/page.tsx */
|
||||||
|
export default function DebugConsole() {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!new URLSearchParams(window.location.search).has("debug")) return;
|
||||||
|
// React strict mode รัน effect สองรอบ — กัน append script ซ้ำ
|
||||||
|
if (document.getElementById("eruda-loader")) return;
|
||||||
|
const s = document.createElement("script");
|
||||||
|
s.id = "eruda-loader";
|
||||||
|
s.src = "https://cdn.jsdelivr.net/npm/eruda@3";
|
||||||
|
s.async = true;
|
||||||
|
s.onload = () => {
|
||||||
|
const w = window as unknown as { eruda?: { create: () => void } };
|
||||||
|
w.eruda?.create();
|
||||||
|
};
|
||||||
|
s.onerror = () => {
|
||||||
|
const b = document.createElement("div");
|
||||||
|
b.textContent =
|
||||||
|
"โหลด Eruda จาก CDN ไม่ได้ (iPad ต้องต่ออินเทอร์เน็ต) — แจ้งเพื่อเปลี่ยนเป็น bundle ใน project แทน";
|
||||||
|
b.style.cssText =
|
||||||
|
"position:fixed;top:0;left:0;right:0;z-index:99999;background:#dc2626;color:#fff;padding:8px;font-size:14px";
|
||||||
|
document.body.appendChild(b);
|
||||||
|
};
|
||||||
|
document.head.appendChild(s);
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ export default function UploadFileForm() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
className="shrink-0 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-white transition hover:opacity-90 disabled:opacity-50"
|
className="ml-auto shrink-0 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-white transition hover:opacity-90 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{pending ? "กำลังอัปโหลด…" : "อัปโหลด"}
|
{pending ? "กำลังอัปโหลด…" : "อัปโหลด"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1817,6 +1817,15 @@ export default function PDFViewer({
|
||||||
if (AUTO_SELECT_TYPES.has(type)) {
|
if (AUTO_SELECT_TYPES.has(type)) {
|
||||||
setSelectedId(saved.id);
|
setSelectedId(saved.id);
|
||||||
setPreviewData(null);
|
setPreviewData(null);
|
||||||
|
// แผง selection กำลังจะโผล่ = ปิดเมนู tool/popup bookmark ที่เปิดค้างก่อน — บน iOS
|
||||||
|
// การลาก (คลุมข้อความ/วาดเส้น) ไม่ยิง compat mousedown ให้ outside-click ปิดเมนู
|
||||||
|
// เอง (Android/desktop ปิดไปแล้วตั้งแต่เริ่มกด) ไม่ปิดตรงนี้ = เมนูซ้อนแผงบนจอสัมผัส
|
||||||
|
setStampMenuOpen(false);
|
||||||
|
setShapeMenuOpen(false);
|
||||||
|
setDrawMenuOpen(false);
|
||||||
|
setTextboxMenuOpen(false);
|
||||||
|
setHighlightMenuOpen(false);
|
||||||
|
setBookmarkComposer(null);
|
||||||
}
|
}
|
||||||
// F2: undo ของ create = ลบ id ปัจจุบัน; redo = สร้างใหม่ (id ใหม่) แล้วส่งต่อให้ undo รอบถัดไป
|
// F2: undo ของ create = ลบ id ปัจจุบัน; redo = สร้างใหม่ (id ใหม่) แล้วส่งต่อให้ undo รอบถัดไป
|
||||||
let currentId = saved.id;
|
let currentId = saved.id;
|
||||||
|
|
@ -3713,7 +3722,9 @@ export default function PDFViewer({
|
||||||
(z-5 เหนือ pins, pointer-events none ไม่บังการคลิกใด ๆ) */}
|
(z-5 เหนือ pins, pointer-events none ไม่บังการคลิกใด ๆ) */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const hovered = visibleAnnotations.find((a) => a.id === hoveredId);
|
const hovered = visibleAnnotations.find((a) => a.id === hoveredId);
|
||||||
if (!hovered || hovered.pageIndex !== i) return null;
|
// รายการที่ชี้ถูกเลือกอยู่ (แผง selection F1 เปิดแถวนั้นแล้ว) = ไม่โชว์
|
||||||
|
// tooltip ซ้อนอีกกล่อง — เจอตอนลากคลุมจบ auto-select โผล่ทันทีที่ตำแหน่งเคอร์เซอร์
|
||||||
|
if (!hovered || hovered.pageIndex !== i || hovered.id === selectedId) return null;
|
||||||
const box = annotationBBox(hovered, { width: w, height: h });
|
const box = annotationBBox(hovered, { width: w, height: h });
|
||||||
const note = annotationNote(hovered);
|
const note = annotationNote(hovered);
|
||||||
// เหลือพื้นที่ขวาของ bbox ไม่พอ (~264px) → วางทางซ้ายแทน
|
// เหลือพื้นที่ขวาของ bbox ไม่พอ (~264px) → วางทางซ้ายแทน
|
||||||
|
|
|
||||||
|
|
@ -524,8 +524,26 @@ export function useCustomSelection({
|
||||||
scrollRafRef.current = requestAnimationFrame(step);
|
scrollRafRef.current = requestAnimationFrame(step);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** TEMP-DEBUG: เก็บ pointerId ที่เคย log move แรกแล้ว — ทำลายทุก pointerdown
|
||||||
|
* ใหม่ (กัน hover ของ pencil กินสล็อต "move แรก" ก่อนปากกาแตะจอ) */
|
||||||
|
const seenMovePointerIds = new Set<number>();
|
||||||
|
|
||||||
const onPointerDown = (e: PointerEvent) => {
|
const onPointerDown = (e: PointerEvent) => {
|
||||||
if (dragRef.current) return; // นิ้วที่สองระหว่างลาก — ไม่สน
|
console.warn("[debug] pointerdown", {
|
||||||
|
pointerType: e.pointerType,
|
||||||
|
clientX: e.clientX,
|
||||||
|
clientY: e.clientY,
|
||||||
|
}); // TEMP-DEBUG
|
||||||
|
seenMovePointerIds.clear(); // TEMP-DEBUG
|
||||||
|
if (dragRef.current) {
|
||||||
|
// นิ้วที่สองระหว่างลาก — ไม่สน รวมถึง touch ปลอมจาก palm rejection (มือ
|
||||||
|
// ที่พักบนจอระหว่างลากด้วย pen) — ห้ามให้ pointer ตัวใหม่แทนที่ drag เดิม
|
||||||
|
console.warn("[debug] ignored second pointer", {
|
||||||
|
activeType: dragRef.current.pointerType,
|
||||||
|
newType: e.pointerType,
|
||||||
|
}); // TEMP-DEBUG
|
||||||
|
return;
|
||||||
|
}
|
||||||
// เริ่ม selection ได้เฉพาะที่เริ่มบน textLayer (แตะ pin/ปุ่ม/กล่องข้อความ = งานของมัน)
|
// เริ่ม selection ได้เฉพาะที่เริ่มบน textLayer (แตะ pin/ปุ่ม/กล่องข้อความ = งานของมัน)
|
||||||
if (
|
if (
|
||||||
!(e.target instanceof Element) ||
|
!(e.target instanceof Element) ||
|
||||||
|
|
@ -581,6 +599,17 @@ export function useCustomSelection({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPointerMove = (e: PointerEvent) => {
|
const onPointerMove = (e: PointerEvent) => {
|
||||||
|
// TEMP-DEBUG: log เฉพาะ move แรกของ pointer หลัง pointerdown ล่าสุด —
|
||||||
|
// pen/touch ยิง 60–120Hz ถ้า log ทุก event จะท่วม console และหน่วงเครื่อง
|
||||||
|
// จนเปลี่ยนพฤติกรรมที่กำลังวินิจฉัยอยู่เอง
|
||||||
|
if (!seenMovePointerIds.has(e.pointerId)) {
|
||||||
|
seenMovePointerIds.add(e.pointerId);
|
||||||
|
console.warn("[debug] pointermove (แรกของ pointer นี้หลัง down)", {
|
||||||
|
pointerType: e.pointerType,
|
||||||
|
pointerId: e.pointerId,
|
||||||
|
hasActiveDrag: !!dragRef.current,
|
||||||
|
}); // TEMP-DEBUG
|
||||||
|
}
|
||||||
const d = dragRef.current;
|
const d = dragRef.current;
|
||||||
if (!d || e.pointerId !== d.pointerId) return;
|
if (!d || e.pointerId !== d.pointerId) return;
|
||||||
d.curPt = { x: e.clientX, y: e.clientY };
|
d.curPt = { x: e.clientX, y: e.clientY };
|
||||||
|
|
@ -605,6 +634,12 @@ export function useCustomSelection({
|
||||||
/** เคลียร์ state ทุกทาง — commit เฉพาะตอน pointerup ที่มี range ไม่ collapsed
|
/** เคลียร์ state ทุกทาง — commit เฉพาะตอน pointerup ที่มี range ไม่ collapsed
|
||||||
* (pointercancel จาก gesture ของระบบ = ทิ้ง selection ไม่ commit) */
|
* (pointercancel จาก gesture ของระบบ = ทิ้ง selection ไม่ commit) */
|
||||||
const finish = (e: PointerEvent, commit: boolean) => {
|
const finish = (e: PointerEvent, commit: boolean) => {
|
||||||
|
// TEMP-DEBUG: แยก pointerup กับ pointercancel — cancel = ระบบยึด gesture ไป
|
||||||
|
// (คู่สมมติฐานหลักของ "ลากไม่ติด" บน pencil นอกเหนือจาก move ไม่มาถึง)
|
||||||
|
console.warn(`[debug] ${commit ? "pointerup" : "pointercancel"}`, {
|
||||||
|
pointerType: e.pointerType,
|
||||||
|
pointerId: e.pointerId,
|
||||||
|
}); // TEMP-DEBUG
|
||||||
const d = dragRef.current;
|
const d = dragRef.current;
|
||||||
if (!d || e.pointerId !== d.pointerId) return;
|
if (!d || e.pointerId !== d.pointerId) return;
|
||||||
stopAutoScroll();
|
stopAutoScroll();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue