mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(web): hide resolved comments from preview overlays (#1762)
This commit is contained in:
parent
bceca66bb4
commit
b2d2635360
2 changed files with 159 additions and 6 deletions
|
|
@ -3685,6 +3685,7 @@ function HtmlViewer({
|
|||
);
|
||||
const [activeCommentTarget, setActiveCommentTarget] = useState<PreviewCommentSnapshot | null>(null);
|
||||
const [hoveredCommentTarget, setHoveredCommentTarget] = useState<PreviewCommentSnapshot | null>(null);
|
||||
const [activePreviewCommentId, setActivePreviewCommentId] = useState<string | null>(null);
|
||||
const [liveCommentTargets, setLiveCommentTargets] = useState<Map<string, PreviewCommentSnapshot>>(() => new Map());
|
||||
const liveCommentTargetsRef = useRef(liveCommentTargets);
|
||||
const [commentDraft, setCommentDraft] = useState('');
|
||||
|
|
@ -4178,6 +4179,7 @@ function HtmlViewer({
|
|||
if (!selectionMode) {
|
||||
setActiveCommentTarget((current) => (current ? null : current));
|
||||
setHoveredCommentTarget((current) => (current ? null : current));
|
||||
setActivePreviewCommentId((current) => (current ? null : current));
|
||||
setLiveCommentTargets((current) => (current.size > 0 ? new Map() : current));
|
||||
setQueuedBoardNotes((current) => (current.length > 0 ? [] : current));
|
||||
setStrokePoints((current) => (current.length > 0 ? [] : current));
|
||||
|
|
@ -4245,11 +4247,16 @@ function HtmlViewer({
|
|||
if (data.type === 'od:comment-target') {
|
||||
const snapshot = snapshotFromData(data);
|
||||
if (!snapshot.elementId) return;
|
||||
const existing = previewComments.find((comment) => comment.elementId === snapshot.elementId);
|
||||
const existing = previewComments.find((comment) =>
|
||||
comment.filePath === file.name &&
|
||||
comment.status === 'open' &&
|
||||
comment.elementId === snapshot.elementId,
|
||||
);
|
||||
setActiveCommentTarget(snapshot);
|
||||
setHoveredCommentTarget(snapshot);
|
||||
setLiveCommentTargets((current) => new Map(current).set(snapshot.elementId, snapshot));
|
||||
if (boardMode) {
|
||||
setActivePreviewCommentId(existing?.id ?? null);
|
||||
setCommentDraft(existing?.note ?? '');
|
||||
setQueuedBoardNotes([]);
|
||||
}
|
||||
|
|
@ -4285,6 +4292,7 @@ function HtmlViewer({
|
|||
}
|
||||
setActiveCommentTarget(nextTarget);
|
||||
setHoveredCommentTarget(nextTarget);
|
||||
setActivePreviewCommentId(null);
|
||||
setQueuedBoardNotes([]);
|
||||
setCommentDraft('');
|
||||
setStrokePoints([]);
|
||||
|
|
@ -5034,6 +5042,7 @@ function HtmlViewer({
|
|||
function clearBoardComposer() {
|
||||
setActiveCommentTarget(null);
|
||||
setHoveredCommentTarget(null);
|
||||
setActivePreviewCommentId(null);
|
||||
setCommentDraft('');
|
||||
setQueuedBoardNotes([]);
|
||||
setStrokePoints([]);
|
||||
|
|
@ -5083,9 +5092,15 @@ function HtmlViewer({
|
|||
const canShare = source !== null;
|
||||
const exportTitle = file.name.replace(/\.html?$/i, '') || file.name;
|
||||
const canPptx = canShare && Boolean(onExportAsPptx) && !streaming;
|
||||
const visibleSideComments = previewComments.filter(
|
||||
(comment) => comment.filePath === file.name && comment.status === 'open',
|
||||
const visibleSideComments = useMemo(
|
||||
() => previewComments.filter((comment) => comment.filePath === file.name && comment.status === 'open'),
|
||||
[file.name, previewComments],
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!boardMode || !activePreviewCommentId) return;
|
||||
const stillOpen = visibleSideComments.some((comment) => comment.id === activePreviewCommentId);
|
||||
if (!stillOpen) clearBoardComposer();
|
||||
}, [activePreviewCommentId, boardMode, visibleSideComments]);
|
||||
const activeDeployment = deployResult || deployment;
|
||||
const activeDeployedUrl = activeDeployment?.url?.trim() || '';
|
||||
const activeDeploymentDelayed = activeDeployment?.status === 'link-delayed';
|
||||
|
|
@ -5798,7 +5813,7 @@ function HtmlViewer({
|
|||
</div>
|
||||
{(boardMode || drawClickSelectionMode) ? (
|
||||
<CommentPreviewOverlays
|
||||
comments={boardMode ? previewComments : []}
|
||||
comments={boardMode ? visibleSideComments : []}
|
||||
liveTargets={liveCommentTargets}
|
||||
hoveredTarget={hoveredCommentTarget}
|
||||
activeTarget={activeCommentTarget}
|
||||
|
|
@ -5808,6 +5823,7 @@ function HtmlViewer({
|
|||
onOpenComment={(comment, snapshot) => {
|
||||
setActiveCommentTarget(snapshot);
|
||||
setHoveredCommentTarget(snapshot);
|
||||
setActivePreviewCommentId(comment.id);
|
||||
setCommentDraft(comment.note);
|
||||
setQueuedBoardNotes([]);
|
||||
}}
|
||||
|
|
@ -5834,7 +5850,7 @@ function HtmlViewer({
|
|||
{boardMode && activeCommentTarget ? (
|
||||
<BoardComposerPopover
|
||||
target={activeCommentTarget}
|
||||
existing={previewComments.find((comment) => comment.elementId === activeCommentTarget.elementId) ?? null}
|
||||
existing={visibleSideComments.find((comment) => comment.elementId === activeCommentTarget.elementId) ?? null}
|
||||
draft={commentDraft}
|
||||
notes={queuedBoardNotes}
|
||||
onDraft={setCommentDraft}
|
||||
|
|
@ -5889,6 +5905,7 @@ function HtmlViewer({
|
|||
};
|
||||
setActiveCommentTarget(snapshot);
|
||||
setHoveredCommentTarget(snapshot);
|
||||
setActivePreviewCommentId(comment.id);
|
||||
setCommentDraft(comment.note);
|
||||
setQueuedBoardNotes([]);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import {
|
|||
updateInspectOverride,
|
||||
} from '../../src/components/FileViewer';
|
||||
import type { InspectOverrideMap } from '../../src/components/FileViewer';
|
||||
import type { LiveArtifact, LiveArtifactWorkspaceEntry, ProjectFile } from '../../src/types';
|
||||
import type { LiveArtifact, LiveArtifactWorkspaceEntry, PreviewComment, ProjectFile } from '../../src/types';
|
||||
import { I18nProvider } from '../../src/i18n';
|
||||
import type { Dict } from '../../src/i18n/types';
|
||||
|
||||
|
|
@ -1141,6 +1141,142 @@ describe('FileViewer tweaks toolbar', () => {
|
|||
expect(screen.queryByText('Queues while working')).toBeNull();
|
||||
});
|
||||
|
||||
it('hides non-open saved comments from preview markers when the side panel is empty', () => {
|
||||
const resolvedComment: PreviewComment = {
|
||||
id: 'comment-applying',
|
||||
projectId: 'project-1',
|
||||
conversationId: 'conversation-1',
|
||||
filePath: 'preview.html',
|
||||
elementId: 'pin-applying',
|
||||
selector: '[data-od-pin="pin-applying"]',
|
||||
label: 'pin-applying',
|
||||
text: '',
|
||||
htmlHint: '',
|
||||
position: { x: 24, y: 32, width: 18, height: 18 },
|
||||
note: 'Already sent to Claude',
|
||||
status: 'applying',
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
render(
|
||||
<FileViewer
|
||||
projectId="project-1"
|
||||
projectKind="prototype"
|
||||
file={htmlPreviewFile()}
|
||||
liveHtml='<html><body><main data-od-id="hero">Hero</main></body></html>'
|
||||
previewComments={[resolvedComment]}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('board-mode-toggle'));
|
||||
|
||||
expect(screen.getByTestId('comment-side-panel')).toBeTruthy();
|
||||
expect(screen.queryByTestId('comment-saved-marker-pin-applying')).toBeNull();
|
||||
expect(screen.queryByText('Already sent to Claude')).toBeNull();
|
||||
});
|
||||
|
||||
it('does not preload non-open element comments into the picker composer', async () => {
|
||||
const applyingElementComment: PreviewComment = {
|
||||
id: 'comment-element-applying',
|
||||
projectId: 'project-1',
|
||||
conversationId: 'conversation-1',
|
||||
filePath: 'preview.html',
|
||||
elementId: 'hero',
|
||||
selector: '[data-od-id="hero"]',
|
||||
label: 'Hero',
|
||||
text: 'Hero',
|
||||
htmlHint: '<main data-od-id="hero">Hero</main>',
|
||||
position: { x: 8, y: 12, width: 120, height: 48 },
|
||||
note: 'Do not resurrect this note',
|
||||
status: 'applying',
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
render(
|
||||
<FileViewer
|
||||
projectId="project-1"
|
||||
projectKind="prototype"
|
||||
file={htmlPreviewFile()}
|
||||
liveHtml='<html><body><main data-od-id="hero">Hero</main></body></html>'
|
||||
previewComments={[applyingElementComment]}
|
||||
/>,
|
||||
);
|
||||
|
||||
const frame = screen.getByTestId('artifact-preview-frame') as HTMLIFrameElement;
|
||||
fireEvent.click(screen.getByTestId('board-mode-toggle'));
|
||||
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
source: frame.contentWindow,
|
||||
data: {
|
||||
type: 'od:comment-target',
|
||||
elementId: 'hero',
|
||||
selector: '[data-od-id="hero"]',
|
||||
label: 'Hero',
|
||||
text: 'Hero',
|
||||
position: { x: 8, y: 12, width: 120, height: 48 },
|
||||
htmlHint: '<main data-od-id="hero">Hero</main>',
|
||||
},
|
||||
}));
|
||||
|
||||
const input = await screen.findByTestId('comment-popover-input') as HTMLTextAreaElement;
|
||||
expect(input.value).toBe('');
|
||||
expect(screen.queryByText('Remove')).toBeNull();
|
||||
expect(screen.queryByText('Do not resurrect this note')).toBeNull();
|
||||
});
|
||||
|
||||
it('closes an open saved-comment composer when that comment leaves the open state', async () => {
|
||||
const openComment: PreviewComment = {
|
||||
id: 'comment-status-transition',
|
||||
projectId: 'project-1',
|
||||
conversationId: 'conversation-1',
|
||||
filePath: 'preview.html',
|
||||
elementId: 'pin-transition',
|
||||
selector: '[data-od-pin="pin-transition"]',
|
||||
label: 'pin-transition',
|
||||
text: '',
|
||||
htmlHint: '',
|
||||
position: { x: 40, y: 52, width: 18, height: 18 },
|
||||
note: 'Do not recreate this stale comment',
|
||||
status: 'open',
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<FileViewer
|
||||
projectId="project-1"
|
||||
projectKind="prototype"
|
||||
file={htmlPreviewFile()}
|
||||
liveHtml='<html><body><main data-od-id="hero">Hero</main></body></html>'
|
||||
previewComments={[openComment]}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('board-mode-toggle'));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open comment for pin-transition' }));
|
||||
|
||||
expect((await screen.findByTestId('comment-popover-input') as HTMLTextAreaElement).value)
|
||||
.toBe('Do not recreate this stale comment');
|
||||
|
||||
rerender(
|
||||
<FileViewer
|
||||
projectId="project-1"
|
||||
projectKind="prototype"
|
||||
file={htmlPreviewFile()}
|
||||
liveHtml='<html><body><main data-od-id="hero">Hero</main></body></html>'
|
||||
previewComments={[{ ...openComment, status: 'applying' }]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('comment-popover-input')).toBeNull();
|
||||
});
|
||||
expect(screen.queryByTestId('comment-saved-marker-pin-transition')).toBeNull();
|
||||
expect(screen.queryByText('Do not recreate this stale comment')).toBeNull();
|
||||
});
|
||||
|
||||
it('collapses the comment side panel into a narrow reopen rail', () => {
|
||||
const onCollapseChange = vi.fn();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue