| |
| |
| |
| |
|
|
| import * as d3 from 'd3'; |
|
|
| |
| |
| |
| export interface AppState { |
| isAnalyzing: boolean; |
| isGlobalLoading: boolean; |
| isSaving: boolean; |
| isSemanticSearching: boolean; |
| lastSearchedQuery: string | null; |
| hasValidData: boolean; |
| |
| dataSource: 'local' | 'server' | 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>; |
| |
| getSemanticSearchQuery?: () => string; |
| |
| 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 }; |
| } |
|
|
| |
| |
| |
| getIsAnalyzing(): boolean { |
| return this.state.isAnalyzing; |
| } |
|
|
| |
| |
| |
| setIsAnalyzing(analyzing: boolean): void { |
| this.updateState({ isAnalyzing: analyzing }); |
| } |
|
|
| |
| |
| |
| 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 }); |
| } |
|
|
| |
| |
| |
| 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; |
|
|
| |
| this.deps.submitBtn.classed('inactive', !hasText || isBusy || this.state.hasValidData); |
|
|
| |
| this.deps.saveBtn.classed('inactive', !dataReadyForSave); |
| this.deps.saveLocalBtn.classed('inactive', !dataReadyForSave); |
|
|
| |
| 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); |
| } |
| } |
|
|
| |
| if (!this.deps.textMetrics.empty()) { |
| this.deps.textMetrics.classed('is-hidden', !this.state.hasValidData); |
| } |
| } |
|
|
| |
| |
| |
| updateState(updates: Partial<AppState>): void { |
| |
| if (updates.hasValidData === false) { |
| updates = { ...updates, lastSearchedQuery: null }; |
| } |
| Object.assign(this.state, updates); |
| this.updateButtonStatesFromAppState(); |
|
|
| |
| if (updates.isGlobalLoading !== undefined) { |
| d3.selectAll(".loadersmall").style('display', updates.isGlobalLoading ? null : 'none'); |
| |
| if (!updates.isGlobalLoading) { |
| d3.select('#analyze_progress').text('').style('display', 'none'); |
| } |
| } |
| |
| 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(); |
| } |
| } |
|
|
|
|