File size: 3,516 Bytes
e5d8d3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9c9e16
e5d8d3a
e9c9e16
2f20f47
e9c9e16
e5d8d3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import * as d3 from 'd3';
import type { GLTR_Text_Box } from '../vis/GLTR_Text_Box';
import type { Histogram } from '../vis/Histogram';
import type { HistogramBinClickEvent } from '../vis/Histogram';
import type { FrontendAnalyzeResult } from '../api/GLTR_API';
import { calculateHighlights } from '../utils/highlightUtils';

export type HighlightControllerOptions = {
    stats_frac: Histogram;
    lmf: GLTR_Text_Box;
    currentData: { result: FrontendAnalyzeResult } | null;
};

export class HighlightController {
    private options: HighlightControllerOptions;

    constructor(options: HighlightControllerOptions) {
        this.options = options;
    }

    /**
     * 清除所有高亮(文本与直方图)
     */
    public clearHighlights(): void {
        this.options.stats_frac.clearSelection();
        this.options.lmf.clearHighlight();
    }

    /**
     * 处理直方图 bin 点击事件
     */
    public handleHistogramBinClick(ev: HistogramBinClickEvent): void {
        const { currentData } = this.options;
        if (!currentData) return;

        // 如果 binIndex 为 -1,表示用户取消选择,清除所有高亮
        if (ev.binIndex === -1) {
            this.clearHighlights();
            return;
        }

        const { x0, x1, binIndex, no_bins, source } = ev;
        const data = currentData.result;

        // 仅处理 token 直方图
        const { indices, style } = calculateHighlights('token', x0, x1, binIndex, no_bins, data);
        
        // 高亮这些 token
        this.options.lmf.setHighlightedIndices(indices, style);
    }

    /**
     * 更新当前数据(当数据变化时调用)
     */
    public updateCurrentData(currentData: { result: FrontendAnalyzeResult } | null): void {
        // 创建一个新对象来更新,保持 options 对象的引用不变
        (this.options as any).currentData = currentData;
    }
}

/**
 * 初始化高亮清除事件监听(点击空白处和 ESC 键)
 */
export const initHighlightClearListeners = (
    clearHighlights: () => void
): void => {
    // 点击页面空白处清除高亮(通用解决方案)
    // 监听整个文档的点击事件,但排除可交互元素
    d3.select('body').on('click.clearHighlight', (event: MouseEvent) => {
        const target = <HTMLElement>event.target;
        if (!target) return;
        
        // 排除可交互元素:token、按钮、输入框、直方图bin等
        const isInteractive = 
            target.closest('.token') ||           // token元素
            target.closest('button') ||           // 按钮
            target.closest('input') ||            // 输入框
            target.closest('textarea') ||         // 文本域
            target.closest('select') ||           // 下拉框
            target.closest('.bar') ||             // 直方图bar
            target.closest('.hover-area') ||      // 直方图悬停区域
            target.closest('a') ||                // 链接
            target.closest('[role="button"]') ||  // 有button角色的元素
            target.closest('[onclick]');          // 有onclick属性的元素
        
        // 如果点击的不是可交互元素,则清除高亮
        if (!isInteractive) {
            clearHighlights();
        }
    });

    // 按下 ESC 键清除高亮
    d3.select(window).on('keydown.clearHighlight', (event: KeyboardEvent) => {
        if (event.key === 'Escape') {
            clearHighlights();
        }
    });
};