UNPKG

chrome-devtools-frontend

Version:
163 lines (142 loc) • 4.39 kB
// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import { type ChangeRecord, type Comment, type CommentAnchorSignature, CommentThread, type CommentThreadOptions, type CommentThreadStatus, type DOMNodeAnchorSignature, type EditorAnchorSignature, Events as CommentThreadEvents, type TimelineAnchorSignature, } from './CommentThread.js'; export { type ChangeRecord, type Comment, type CommentAnchorSignature, CommentThread, type CommentThreadOptions, type CommentThreadStatus, type DOMNodeAnchorSignature, type EditorAnchorSignature, type TimelineAnchorSignature, }; export const enum Events { COMMENT_THREADS_CHANGED = 'CommentThreadsChanged', COMMENT_MODE_CHANGED = 'CommentModeChanged', AGENT_ATTACHED_CHANGED = 'AgentAttachedChanged', } export interface EventTypes { [Events.COMMENT_THREADS_CHANGED]: CommentThread[]; [Events.COMMENT_MODE_CHANGED]: boolean; [Events.AGENT_ATTACHED_CHANGED]: boolean; } /** * Headless model managing comment thread data, CRUD operations, and comment mode. */ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> { readonly #commentThreads = new Map<string, CommentThread>(); #commentMode = false; #agentAttached = false; constructor() { super(); CommentThread.resetIndex(); } #onThreadChanged(): void { this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads()); } setAgentAttached(value: boolean): void { if (this.#agentAttached === value) { return; } this.#agentAttached = value; if (!value) { this.setCommentMode(false); } this.dispatchEventToListeners(Events.AGENT_ATTACHED_CHANGED, value); } isAgentAttached(): boolean { return this.#agentAttached; } setCommentMode(active: boolean): void { if (active && !this.#agentAttached) { return; } if (this.#commentMode === active) { return; } this.#commentMode = active; this.dispatchEventToListeners(Events.COMMENT_MODE_CHANGED, active); } isCommentMode(): boolean { return this.#commentMode; } createCommentThread( anchor: CommentAnchorSignature, text?: string, author: 'DEVELOPER'|'AGENT' = 'DEVELOPER', changes?: ChangeRecord[], ): CommentThread { const comments: Comment[] = text ? [{ author, text, timestamp: Date.now(), }] : []; const thread = new CommentThread({ anchor, comments, changes, }); thread.addEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this); this.#commentThreads.set(thread.id, thread); this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads()); return thread; } getCommentThread(id: string): CommentThread|undefined { return this.#commentThreads.get(id); } getCommentThreads(): CommentThread[] { return Array.from(this.#commentThreads.values()); } takeComments(): CommentThread[] { const threads: CommentThread[] = []; for (const thread of this.#commentThreads.values()) { if (thread.status === 'ACTIVE' && !thread.transmitted) { thread.transmitted = true; threads.push(thread); } } return threads; } resolveCommentThread(threadId: string, replyText?: string): boolean { const thread = this.#commentThreads.get(threadId); if (!thread) { return false; } thread.resolve(replyText); return true; } removeCommentThread(id: string): void { const thread = this.#commentThreads.get(id); if (!thread) { return; } thread.removeEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this); this.#commentThreads.delete(id); this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads()); } clear(): void { this.setCommentMode(false); for (const thread of this.#commentThreads.values()) { thread.removeEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this); } this.#commentThreads.clear(); CommentThread.resetIndex(); this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, []); } }