File size: 6,506 Bytes
605cef2
 
 
 
 
 
 
 
 
 
 
 
 
 
12c5b18
 
605cef2
fc062b2
 
 
 
9702df4
605cef2
 
 
 
 
 
 
 
 
 
6284eeb
12c5b18
 
 
e994b4e
 
605cef2
 
 
 
 
 
 
 
 
 
 
 
 
 
12c5b18
 
fc062b2
 
 
9702df4
 
605cef2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12c5b18
 
 
 
 
 
 
 
605cef2
6284eeb
605cef2
 
4030987
605cef2
 
12c5b18
 
 
 
 
 
 
 
 
 
e994b4e
12c5b18
e994b4e
 
 
 
 
4030987
e994b4e
 
 
 
12c5b18
6284eeb
 
 
 
 
605cef2
 
 
 
 
 
12c5b18
 
 
 
605cef2
 
 
 
 
 
 
 
 
 
 
12c5b18
 
 
ce2c2f8
 
 
12c5b18
605cef2
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
 * 应用状态管理模块
 * 负责集中管理应用状态和状态驱动的按钮状态更新
 */

import * as d3 from 'd3';

/**
 * 应用状态对象
 */
export interface AppState {
    isAnalyzing: boolean;
    isGlobalLoading: boolean;
    isSaving: boolean;  // 是否正在保存
    isSemanticSearching: boolean;  // 语义搜索进行中
    lastSearchedQuery: string | null;  // 上次成功搜索的 query,用于在输入未变化时保持 Search 按钮灰色
    hasValidData: boolean;  // 是否有有效数据
    // 以下字段仅用于按钮状态控制
    dataSource: 'local' | 'server' | null;  // 数据来源:null表示新分析的内容
    isSavedToLocal: boolean;  // 是否已保存到本地
    isSavedToServer: boolean;  // 是否已保存到服务器
    currentFileName: string | null;  // 当前文件名(本地或服务器)
}

/**
 * 按钮状态管理依赖
 */
export interface ButtonStateDependencies {
    submitBtn: d3.Selection<HTMLElement, unknown, HTMLElement, unknown>;
    saveBtn: d3.Selection<HTMLElement, unknown, HTMLElement, unknown>;
    saveLocalBtn: d3.Selection<HTMLElement, unknown, HTMLElement, unknown>;
    textField: d3.Selection<HTMLElement, unknown, HTMLElement, unknown>;
    textMetrics: d3.Selection<HTMLElement, unknown, HTMLElement, unknown>;
    semanticSearchBtn?: d3.Selection<HTMLElement, unknown, HTMLElement, unknown>;
    /** 获取当前语义搜索输入框的 query,用于判断是否与上次搜索一致 */
    getSemanticSearchQuery?: () => string;
    /** 翻译函数,用于 Search/Stop 按钮文案 */
    tr?: (key: string) => string;
}

/**
 * 应用状态管理器
 */
export class AppStateManager {
    private state: AppState;
    private deps: ButtonStateDependencies;

    constructor(deps: ButtonStateDependencies) {
        this.state = {
            isAnalyzing: false,
            isGlobalLoading: false,
            isSaving: false,
            isSemanticSearching: false,
            lastSearchedQuery: null,
            hasValidData: false,
            dataSource: null,
            isSavedToLocal: false,
            isSavedToServer: false,
            currentFileName: null
        };
        this.deps = deps;
    }

    /**
     * 获取当前状态
     */
    getState(): Readonly<AppState> {
        return { ...this.state };
    }

    /**
     * 获取 isAnalyzing 状态
     */
    getIsAnalyzing(): boolean {
        return this.state.isAnalyzing;
    }

    /**
     * 设置 isAnalyzing 状态
     */
    setIsAnalyzing(analyzing: boolean): void {
        this.updateState({ isAnalyzing: analyzing });
    }

    /**
     * 设置全局 loading 状态
     */
    setGlobalLoading(loading: boolean): void {
        this.updateState({ isGlobalLoading: loading });
    }

    setSemanticSearching(searching: boolean): void {
        this.updateState({ isSemanticSearching: searching });
    }

    setLastSearchedQuery(query: string | null): void {
        this.updateState({ lastSearchedQuery: query });
    }

    /**
     * 根据应用状态计算并更新所有按钮状态和UI元素状态
     */
    private updateButtonStatesFromAppState(): void {
        const hasText = (this.deps.textField.property('value') || '').length > 0;
        const isBusy = this.state.isAnalyzing || this.state.isGlobalLoading || this.state.isSaving;

        // 数据完整性:有有效数据即可保存
        const dataReadyForSave = this.state.hasValidData;

        // Analyze按钮:有文本 && 不忙 && 无有效数据(复用 hasValidData,与 TextMetrics 一致:有数据时灰色)
        this.deps.submitBtn.classed('inactive', !hasText || isBusy || this.state.hasValidData);

        // Upload / Save:仅在没有数据可保存时禁用
        this.deps.saveBtn.classed('inactive', !dataReadyForSave);
        this.deps.saveLocalBtn.classed('inactive', !dataReadyForSave);

        // 语义搜索按钮:Search/Stop 共用。进行中显示 Stop 且可点;否则显示 Search,按条件禁用
        if (this.deps.semanticSearchBtn && !this.deps.semanticSearchBtn.empty()) {
            const tr = this.deps.tr ?? ((k: string) => k);
            this.deps.semanticSearchBtn.text(this.state.isSemanticSearching ? tr('Stop') : tr('Search'));
            if (this.state.isSemanticSearching) {
                this.deps.semanticSearchBtn.classed('inactive', false);
            } else {
                const currentQuery = this.deps.getSemanticSearchQuery?.() ?? '';
                const queryUnchanged = this.state.lastSearchedQuery !== null && currentQuery === this.state.lastSearchedQuery;
                const canRunSemantic = (hasText || this.state.hasValidData) && currentQuery.length > 0;
                this.deps.semanticSearchBtn.classed('inactive', !canRunSemantic || queryUnchanged);
            }
        }

        // TextMetrics显示:有有效数据(hasValidData = true 时,stats 一定不为 null)
        if (!this.deps.textMetrics.empty()) {
            this.deps.textMetrics.classed('is-hidden', !this.state.hasValidData);
        }
    }

    /**
     * 更新应用状态并触发按钮状态更新
     */
    updateState(updates: Partial<AppState>): void {
        // 数据变更时清空 lastSearchedQuery(参考 TextMetrics:数据变化则状态重置)
        if (updates.hasValidData === false) {
            updates = { ...updates, lastSearchedQuery: null };
        }
        Object.assign(this.state, updates);
        this.updateButtonStatesFromAppState();

        // 同时更新全局loading的UI(如果需要)
        if (updates.isGlobalLoading !== undefined) {
            d3.selectAll(".loadersmall").style('display', updates.isGlobalLoading ? null : 'none');
            // 隐藏进度显示
            if (!updates.isGlobalLoading) {
                d3.select('#analyze_progress').text('').style('display', 'none');
            }
        }
        // 语义搜索 loading:独立 loader
        if (updates.isSemanticSearching !== undefined) {
            d3.select('#semantic_search_loader').style('visibility', updates.isSemanticSearching ? 'visible' : 'hidden');
            if (!updates.isSemanticSearching) {
                d3.select('#semantic_progress').text('').style('display', 'none');
            }
        }
    }

    /**
     * 手动触发按钮状态更新(用于外部调用)
     */
    updateButtonStates(): void {
        this.updateButtonStatesFromAppState();
    }
}