|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import * as d3 from 'd3'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export interface AppState { |
|
|
isAnalyzing: boolean; |
|
|
isGlobalLoading: boolean; |
|
|
isSaving: boolean; |
|
|
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>; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class AppStateManager { |
|
|
private state: AppState; |
|
|
private deps: ButtonStateDependencies; |
|
|
|
|
|
constructor(deps: ButtonStateDependencies) { |
|
|
this.state = { |
|
|
isAnalyzing: false, |
|
|
isGlobalLoading: false, |
|
|
isSaving: false, |
|
|
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 }); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private updateButtonStatesFromAppState(): void { |
|
|
const hasText = (this.deps.textField.property('value') || '').trim().length > 0; |
|
|
const isBusy = this.state.isAnalyzing || this.state.isGlobalLoading || this.state.isSaving; |
|
|
|
|
|
|
|
|
this.deps.submitBtn.classed('inactive', !hasText || isBusy); |
|
|
|
|
|
|
|
|
this.deps.saveBtn.classed('inactive', |
|
|
!this.state.hasValidData || |
|
|
isBusy || |
|
|
this.state.dataSource === 'server' || |
|
|
this.state.isSavedToServer |
|
|
); |
|
|
|
|
|
|
|
|
this.deps.saveLocalBtn.classed('inactive', |
|
|
!this.state.hasValidData || |
|
|
isBusy || |
|
|
this.state.dataSource === 'local' || |
|
|
this.state.isSavedToLocal |
|
|
); |
|
|
|
|
|
|
|
|
if (!this.deps.textMetrics.empty()) { |
|
|
this.deps.textMetrics.classed('is-hidden', !this.state.hasValidData); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateState(updates: Partial<AppState>): void { |
|
|
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'); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateButtonStates(): void { |
|
|
this.updateButtonStatesFromAppState(); |
|
|
} |
|
|
} |
|
|
|
|
|
|