refactored api uses and constants

This commit is contained in:
Ayzen
2025-09-30 15:08:25 +03:00
parent f6c89f5eda
commit 39bd56c19a
8 changed files with 349 additions and 171 deletions

View File

@ -1,5 +1,5 @@
{ {
"y_min": -80, "y_min": -80,
"y_max": 40, "y_max": 40,
"show_phase": false "show_phase": true
} }

View File

@ -4,6 +4,10 @@
*/ */
import { setButtonLoading } from './utils.js'; import { setButtonLoading } from './utils.js';
import { apiGet, apiPost } from './api-client.js';
import { API, TIMING, NOTIFICATION_TYPES } from './constants.js';
const { SUCCESS, ERROR } = NOTIFICATION_TYPES;
export class AcquisitionManager { export class AcquisitionManager {
constructor(notifications) { constructor(notifications) {
@ -44,8 +48,8 @@ export class AcquisitionManager {
// Initial status update // Initial status update
this.updateStatus(); this.updateStatus();
// Poll status every 2 seconds // Poll status periodically
this.statusInterval = setInterval(this.updateStatus, 2000); this.statusInterval = setInterval(this.updateStatus, TIMING.STATUS_POLL_INTERVAL);
this.isInitialized = true; this.isInitialized = true;
console.log('AcquisitionManager initialized'); console.log('AcquisitionManager initialized');
@ -55,22 +59,17 @@ export class AcquisitionManager {
try { try {
const originalState = setButtonLoading(this.elements.startBtn, true); const originalState = setButtonLoading(this.elements.startBtn, true);
const response = await fetch('/api/v1/acquisition/start', { const result = await apiPost(API.ACQUISITION.START);
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.success) { if (result.success) {
this.notifications.show({type: 'success', title: 'Acquisition Started', message: result.message}); this.notifications.show({ type: SUCCESS, title: 'Acquisition Started', message: result.message });
await this.updateStatus(); await this.updateStatus();
} else { } else {
this.notifications.show({type: 'error', title: 'Start Failed', message: result.error || 'Failed to start acquisition'}); this.notifications.show({ type: ERROR, title: 'Start Failed', message: result.error || 'Failed to start acquisition' });
} }
} catch (error) { } catch (error) {
console.error('Error starting acquisition:', error); console.error('Error starting acquisition:', error);
this.notifications.show({type: 'error', title: 'Start Failed', message: 'Failed to start acquisition'}); this.notifications.show({ type: ERROR, title: 'Start Failed', message: 'Failed to start acquisition' });
} finally { } finally {
setButtonLoading(this.elements.startBtn, false, originalState); setButtonLoading(this.elements.startBtn, false, originalState);
} }
@ -80,22 +79,17 @@ export class AcquisitionManager {
try { try {
const originalState = setButtonLoading(this.elements.stopBtn, true); const originalState = setButtonLoading(this.elements.stopBtn, true);
const response = await fetch('/api/v1/acquisition/stop', { const result = await apiPost(API.ACQUISITION.STOP);
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.success) { if (result.success) {
this.notifications.show({type: 'success', title: 'Acquisition Stopped', message: result.message}); this.notifications.show({ type: SUCCESS, title: 'Acquisition Stopped', message: result.message });
await this.updateStatus(); await this.updateStatus();
} else { } else {
this.notifications.show({type: 'error', title: 'Stop Failed', message: result.error || 'Failed to stop acquisition'}); this.notifications.show({ type: ERROR, title: 'Stop Failed', message: result.error || 'Failed to stop acquisition' });
} }
} catch (error) { } catch (error) {
console.error('Error stopping acquisition:', error); console.error('Error stopping acquisition:', error);
this.notifications.show({type: 'error', title: 'Stop Failed', message: 'Failed to stop acquisition'}); this.notifications.show({ type: ERROR, title: 'Stop Failed', message: 'Failed to stop acquisition' });
} finally { } finally {
setButtonLoading(this.elements.stopBtn, false, originalState); setButtonLoading(this.elements.stopBtn, false, originalState);
} }
@ -105,22 +99,17 @@ export class AcquisitionManager {
try { try {
const originalState = setButtonLoading(this.elements.singleSweepBtn, true); const originalState = setButtonLoading(this.elements.singleSweepBtn, true);
const response = await fetch('/api/v1/acquisition/single-sweep', { const result = await apiPost(API.ACQUISITION.SINGLE);
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.success) { if (result.success) {
this.notifications.show({type: 'success', title: 'Single Sweep', message: result.message}); this.notifications.show({ type: SUCCESS, title: 'Single Sweep', message: result.message });
await this.updateStatus(); await this.updateStatus();
} else { } else {
this.notifications.show({type: 'error', title: 'Single Sweep Failed', message: result.error || 'Failed to trigger single sweep'}); this.notifications.show({ type: ERROR, title: 'Single Sweep Failed', message: result.error || 'Failed to trigger single sweep' });
} }
} catch (error) { } catch (error) {
console.error('Error triggering single sweep:', error); console.error('Error triggering single sweep:', error);
this.notifications.show({type: 'error', title: 'Single Sweep Failed', message: 'Failed to trigger single sweep'}); this.notifications.show({ type: ERROR, title: 'Single Sweep Failed', message: 'Failed to trigger single sweep' });
} finally { } finally {
setButtonLoading(this.elements.singleSweepBtn, false, originalState); setButtonLoading(this.elements.singleSweepBtn, false, originalState);
} }
@ -128,8 +117,7 @@ export class AcquisitionManager {
async updateStatus() { async updateStatus() {
try { try {
const response = await fetch('/api/v1/acquisition/status'); const status = await apiGet(API.ACQUISITION.STATUS);
const status = await response.json();
this.currentStatus = status; this.currentStatus = status;
this.updateUI(status); this.updateUI(status);

View File

@ -0,0 +1,94 @@
/**
* API Client
* Centralized API request handling with consistent error handling
*/
/**
* Default fetch options
*/
const DEFAULT_OPTIONS = {
headers: {
'Content-Type': 'application/json'
}
};
/**
* Base API request wrapper
* @param {string} url - API endpoint URL
* @param {Object} options - Fetch options
* @returns {Promise<any>} Response data
*/
async function apiRequest(url, options = {}) {
const response = await fetch(url, {
...DEFAULT_OPTIONS,
...options,
headers: {
...DEFAULT_OPTIONS.headers,
...options.headers
}
});
if (!response.ok) {
const errorText = await response.text().catch(() => response.statusText);
const error = new Error(`HTTP ${response.status}: ${errorText}`);
error.status = response.status;
error.body = errorText;
throw error;
}
// Check if response has content
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
}
/**
* GET request
* @param {string} url - API endpoint URL
* @returns {Promise<any>} Response data
*/
export async function apiGet(url) {
return await apiRequest(url, { method: 'GET' });
}
/**
* POST request
* @param {string} url - API endpoint URL
* @param {Object} data - Request body data
* @returns {Promise<any>} Response data
*/
export async function apiPost(url, data = null) {
const options = { method: 'POST' };
if (data !== null) {
options.body = JSON.stringify(data);
}
return await apiRequest(url, options);
}
/**
* DELETE request
* @param {string} url - API endpoint URL
* @returns {Promise<any>} Response data
*/
export async function apiDelete(url) {
return await apiRequest(url, { method: 'DELETE' });
}
/**
* Build URL with query parameters
* @param {string} baseUrl - Base URL
* @param {Object} params - Query parameters
* @returns {string} URL with query string
*/
export function buildUrl(baseUrl, params = {}) {
const url = new URL(baseUrl, window.location.origin);
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined) {
url.searchParams.append(key, value);
}
});
return url.toString();
}

View File

@ -0,0 +1,140 @@
/**
* Application Constants
* Centralized configuration values
*/
// Timing constants (milliseconds)
export const TIMING = {
DEBOUNCE_DEFAULT: 300,
DEBOUNCE_SETTINGS: 300,
DEBOUNCE_RESIZE: 300,
DEBOUNCE_PRESET: 300,
DEBOUNCE_CALIBRATION: 400,
DEBOUNCE_REFERENCE: 500,
DEBOUNCE_DOWNLOAD: 600,
BUTTON_FEEDBACK: 1000,
STATUS_POLL_INTERVAL: 2000,
RECONNECT_BASE: 3000,
RECONNECT_MAX: 30000,
CONNECTION_TIMEOUT: 5000,
PING_INTERVAL: 30000,
NOTIFICATION_SUCCESS: 3000,
NOTIFICATION_ERROR: 7000,
NOTIFICATION_WARNING: 5000,
NOTIFICATION_INFO: 4000,
NOTIFICATION_ANIMATION: 300,
FULLSCREEN_RESIZE_DELAY: 200,
FULLSCREEN_EXIT_DELAY: 100,
CHART_ANIMATION_DELAY: 50,
ICON_INIT_DELAY: 10
};
// Size limits
export const LIMITS = {
MAX_NOTIFICATIONS: 5,
MAX_RECONNECT_ATTEMPTS: 10,
MAX_QUEUE_SIZE: 100,
MAX_DATA_POINTS: 1000,
MAX_CHARTS_SAVED: 10,
CHART_DATA_HISTORY: 100,
STORAGE_CLEANUP_DAYS: 7,
MESSAGE_RATE_WINDOW: 1000,
NOTIFICATION_DEDUPE_WINDOW: 2000,
NOTIFICATION_CLEANUP_AGE: 5000
};
// API endpoints
const API_BASE = '/api/v1';
export const API = {
BASE: API_BASE,
ACQUISITION: {
START: `${API_BASE}/acquisition/start`,
STOP: `${API_BASE}/acquisition/stop`,
SINGLE: `${API_BASE}/acquisition/single-sweep`,
STATUS: `${API_BASE}/acquisition/status`
},
SETTINGS: {
PRESETS: `${API_BASE}/settings/presets`,
PRESET_SET: `${API_BASE}/settings/preset/set`,
STATUS: `${API_BASE}/settings/status`,
CALIBRATIONS: `${API_BASE}/settings/calibrations`,
CALIBRATION_START: `${API_BASE}/settings/calibration/start`,
CALIBRATION_SAVE: `${API_BASE}/settings/calibration/save`,
CALIBRATION_SET: `${API_BASE}/settings/calibration/set`,
CALIBRATION_ADD_STANDARD: `${API_BASE}/settings/calibration/add-standard`,
CALIBRATION_STANDARDS_PLOTS: (name) => `${API_BASE}/settings/calibration/${encodeURIComponent(name)}/standards-plots`,
WORKING_CALIBRATION: `${API_BASE}/settings/working-calibration`,
WORKING_CALIBRATION_PLOTS: `${API_BASE}/settings/working-calibration/standards-plots`,
REFERENCES: `${API_BASE}/settings/references`,
REFERENCE_CREATE: `${API_BASE}/settings/reference/create`,
REFERENCE_SET: `${API_BASE}/settings/reference/set`,
REFERENCE_CURRENT: `${API_BASE}/settings/reference/current`,
REFERENCE_ITEM: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}`
}
};
// Storage keys
export const STORAGE_KEYS = {
PREFERENCES: 'preferences',
CHART_DATA: 'chart_data',
SESSION: 'session_data',
DEBUG: 'debug_info'
};
// WebSocket events
export const WS_EVENTS = {
CONNECTING: 'connecting',
CONNECTED: 'connected',
DISCONNECTED: 'disconnected',
DATA: 'data',
PROCESSOR_RESULT: 'processor_result',
PROCESSOR_HISTORY: 'processor_history',
ERROR: 'error'
};
// WebSocket message types
export const WS_MESSAGE_TYPES = {
PING: 'ping',
PONG: 'pong',
RECALCULATE: 'recalculate',
GET_HISTORY: 'get_history',
PROCESSOR_RESULT: 'processor_result',
PROCESSOR_HISTORY: 'processor_history',
ERROR: 'error'
};
// Notification types
export const NOTIFICATION_TYPES = {
SUCCESS: 'success',
ERROR: 'error',
WARNING: 'warning',
INFO: 'info'
};
// Chart update queue
export const CHART = {
UPDATE_INTERVAL: 100,
MAX_DATA_POINTS: 1000,
ANIMATION_ENABLED: true
};
// Calibration standards by mode
export const CALIBRATION_STANDARDS = {
S11: ['open', 'short', 'load'],
S21: ['through']
};

View File

@ -13,6 +13,10 @@ import {
downloadPlotlyImage, downloadPlotlyImage,
cleanupPlotly cleanupPlotly
} from './plotly-utils.js'; } from './plotly-utils.js';
import { apiGet, buildUrl } from './api-client.js';
import { API, TIMING, NOTIFICATION_TYPES } from './constants.js';
const { SUCCESS, ERROR, WARNING } = NOTIFICATION_TYPES;
export class SettingsManager { export class SettingsManager {
constructor(notifications, websocket, acquisition) { constructor(notifications, websocket, acquisition) {
@ -48,7 +52,7 @@ export class SettingsManager {
console.log('Settings Manager initialized'); console.log('Settings Manager initialized');
} catch (err) { } catch (err) {
console.error('Settings Manager init failed:', err); console.error('Settings Manager init failed:', err);
this.notify('error', 'Settings Error', 'Failed to initialize settings'); this.notify(ERROR, 'Settings Error', 'Failed to initialize settings');
} }
} }
@ -136,9 +140,7 @@ export class SettingsManager {
async loadStatus() { async loadStatus() {
try { try {
const r = await fetch('/api/v1/settings/status'); const status = await apiGet(API.SETTINGS.STATUS);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const status = await r.json();
this.updateStatusDisplay(status); this.updateStatusDisplay(status);
} catch (e) { } catch (e) {
console.error('Status load failed:', e); console.error('Status load failed:', e);
@ -167,19 +169,19 @@ export class SettingsManager {
try { try {
ButtonState.set(this.elements.viewPlotsBtn, { state: 'loading', icon: 'loader', text: 'Loading...' }); ButtonState.set(this.elements.viewPlotsBtn, { state: 'loading', icon: 'loader', text: 'Loading...' });
const url = `/api/v1/settings/calibration/${encodeURIComponent(name)}/standards-plots?preset_filename=${encodeURIComponent(preset.filename)}`; const url = buildUrl(API.SETTINGS.CALIBRATION_STANDARDS_PLOTS(name), {
const r = await fetch(url); preset_filename: preset.filename
if (!r.ok) throw new Error(`HTTP ${r.status}`); });
const plotsData = await r.json(); const plotsData = await apiGet(url);
this.showPlotsModal(plotsData); this.showPlotsModal(plotsData);
} catch (e) { } catch (e) {
console.error('Load plots failed:', e); console.error('Load plots failed:', e);
this.notify('error', 'Plots Error', 'Failed to load calibration plots'); this.notify(ERROR, 'Plots Error', 'Failed to load calibration plots');
} finally { } finally {
ButtonState.set(this.elements.viewPlotsBtn, { state: 'normal', icon: 'bar-chart-3', text: 'View Plots' }); ButtonState.set(this.elements.viewPlotsBtn, { state: 'normal', icon: 'bar-chart-3', text: 'View Plots' });
} }
}, 300); }, TIMING.DEBOUNCE_CALIBRATION);
} }
async handleViewCurrentPlots() { async handleViewCurrentPlots() {
@ -190,23 +192,19 @@ export class SettingsManager {
try { try {
ButtonState.set(this.elements.viewCurrentPlotsBtn, { state: 'loading', icon: 'loader', text: 'Loading...' }); ButtonState.set(this.elements.viewCurrentPlotsBtn, { state: 'loading', icon: 'loader', text: 'Loading...' });
const r = await fetch('/api/v1/settings/working-calibration/standards-plots'); const plotsData = await apiGet(API.SETTINGS.WORKING_CALIBRATION_PLOTS);
if (!r.ok) {
if (r.status === 404) {
this.notify('warning', 'No Data', 'No working calibration or standards available to plot');
return;
}
throw new Error(`HTTP ${r.status}`);
}
const plotsData = await r.json();
this.showPlotsModal(plotsData); this.showPlotsModal(plotsData);
} catch (e) { } catch (e) {
console.error('Load current plots failed:', e); if (e.status === 404) {
this.notify('error', 'Plots Error', 'Failed to load current calibration plots'); this.notify(WARNING, 'No Data', 'No working calibration or standards available to plot');
} else {
console.error('Load current plots failed:', e);
this.notify(ERROR, 'Plots Error', 'Failed to load current calibration plots');
}
} finally { } finally {
ButtonState.set(this.elements.viewCurrentPlotsBtn, { state: 'normal', icon: 'bar-chart-3', text: 'View Current Plots' }); ButtonState.set(this.elements.viewCurrentPlotsBtn, { state: 'normal', icon: 'bar-chart-3', text: 'View Current Plots' });
} }
}, 300); }, TIMING.DEBOUNCE_CALIBRATION);
} }
showPlotsModal(plotsData) { showPlotsModal(plotsData) {
@ -368,7 +366,7 @@ export class SettingsManager {
const downloadAllBtn = modal.querySelector('#downloadAllBtn'); const downloadAllBtn = modal.querySelector('#downloadAllBtn');
if (downloadAllBtn) { if (downloadAllBtn) {
downloadAllBtn.addEventListener('click', () => downloadAllBtn.addEventListener('click', () =>
this.debouncer.debounce('download-all', () => this.downloadAllCalibrationData(), 600) this.debouncer.debounce('download-all', () => this.downloadAllCalibrationData(), TIMING.DEBOUNCE_DOWNLOAD)
); );
} }
@ -407,10 +405,10 @@ export class SettingsManager {
const data = this.prepareCalibrationDownloadData(standardName); const data = this.prepareCalibrationDownloadData(standardName);
downloadJSON(data, `${base}_data.json`); downloadJSON(data, `${base}_data.json`);
this.notify('success', 'Download Complete', `Downloaded ${standardName.toUpperCase()} standard plot and data`); this.notify(SUCCESS, 'Download Complete', `Downloaded ${standardName.toUpperCase()} standard plot and data`);
} catch (e) { } catch (e) {
console.error('Download standard failed:', e); console.error('Download standard failed:', e);
this.notify('error', 'Download Failed', 'Failed to download calibration data'); this.notify(ERROR, 'Download Failed', 'Failed to download calibration data');
} }
} }
@ -436,10 +434,10 @@ export class SettingsManager {
downloadJSON(complete, `${base}.json`); downloadJSON(complete, `${base}.json`);
this.notify('success', 'Complete Download', `Downloaded complete calibration data for ${calibrationName}`); this.notify(SUCCESS, 'Complete Download', `Downloaded complete calibration data for ${calibrationName}`);
} catch (e) { } catch (e) {
console.error('Download all failed:', e); console.error('Download all failed:', e);
this.notify('error', 'Download Failed', 'Failed to download complete calibration data'); this.notify(ERROR, 'Download Failed', 'Failed to download complete calibration data');
} finally { } finally {
const btn = this.elements.downloadAllBtn; const btn = this.elements.downloadAllBtn;
if (btn) ButtonState.set(btn, { state: 'normal', icon: 'download-cloud', text: 'Download All' }); if (btn) ButtonState.set(btn, { state: 'normal', icon: 'download-cloud', text: 'Download All' });

View File

@ -4,6 +4,10 @@
*/ */
import { Debouncer, RequestGuard, ButtonState } from '../utils.js'; import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
import { apiGet, apiPost, buildUrl } from '../api-client.js';
import { API, TIMING, CALIBRATION_STANDARDS, NOTIFICATION_TYPES } from '../constants.js';
const { SUCCESS, ERROR, INFO } = NOTIFICATION_TYPES;
export class CalibrationManager { export class CalibrationManager {
constructor(notifications) { constructor(notifications) {
@ -57,9 +61,7 @@ export class CalibrationManager {
async loadWorkingCalibration() { async loadWorkingCalibration() {
try { try {
const r = await fetch('/api/v1/settings/working-calibration'); const working = await apiGet(API.SETTINGS.WORKING_CALIBRATION);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const working = await r.json();
this.updateWorking(working); this.updateWorking(working);
} catch (e) { } catch (e) {
console.error('Working calibration load failed:', e); console.error('Working calibration load failed:', e);
@ -69,9 +71,10 @@ export class CalibrationManager {
async loadCalibrations() { async loadCalibrations() {
if (!this.currentPreset) return; if (!this.currentPreset) return;
try { try {
const r = await fetch(`/api/v1/settings/calibrations?preset_filename=${encodeURIComponent(this.currentPreset.filename)}`); const url = buildUrl(API.SETTINGS.CALIBRATIONS, {
if (!r.ok) throw new Error(`HTTP ${r.status}`); preset_filename: this.currentPreset.filename
const calibrations = await r.json(); });
const calibrations = await apiGet(url);
this.populateDropdown(calibrations); this.populateDropdown(calibrations);
} catch (e) { } catch (e) {
console.error('Calibrations load failed:', e); console.error('Calibrations load failed:', e);
@ -184,10 +187,9 @@ export class CalibrationManager {
} }
getStandardsForMode() { getStandardsForMode() {
if (!this.currentPreset) return []; if (!this.currentPreset || !this.currentPreset.mode) return [];
if (this.currentPreset.mode === 's11') return ['open', 'short', 'load']; const modeKey = this.currentPreset.mode.toUpperCase();
if (this.currentPreset.mode === 's21') return ['through']; return CALIBRATION_STANDARDS[modeKey] || [];
return [];
} }
handleCalibrationChange() { handleCalibrationChange() {
@ -204,24 +206,20 @@ export class CalibrationManager {
try { try {
ButtonState.set(this.elements.startCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Starting...' }); ButtonState.set(this.elements.startCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Starting...' });
const r = await fetch('/api/v1/settings/calibration/start', { const result = await apiPost(API.SETTINGS.CALIBRATION_START, {
method: 'POST', preset_filename: this.currentPreset.filename
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ preset_filename: this.currentPreset.filename })
}); });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const result = await r.json();
this.notify('info', 'Calibration Started', `Started calibration for ${result.preset}`); this.notify(INFO, 'Calibration Started', `Started calibration for ${result.preset}`);
await this.loadWorkingCalibration(); await this.loadWorkingCalibration();
} catch (e) { } catch (e) {
console.error('Start calibration failed:', e); console.error('Start calibration failed:', e);
this.notify('error', 'Calibration Error', 'Failed to start calibration'); this.notify(ERROR, 'Calibration Error', 'Failed to start calibration');
} finally { } finally {
ButtonState.set(this.elements.startCalibrationBtn, { state: 'normal', icon: 'play', text: 'Start Calibration' }); ButtonState.set(this.elements.startCalibrationBtn, { state: 'normal', icon: 'play', text: 'Start Calibration' });
} }
}), 400 }), TIMING.DEBOUNCE_CALIBRATION
); );
} }
@ -237,27 +235,20 @@ export class CalibrationManager {
const btn = document.querySelector(`[data-standard="${standard}"]`); const btn = document.querySelector(`[data-standard="${standard}"]`);
ButtonState.set(btn, { state: 'loading', icon: 'upload', text: 'Capturing...' }); ButtonState.set(btn, { state: 'loading', icon: 'upload', text: 'Capturing...' });
this.notify('info', 'Capturing Standard', `Capturing ${standard.toUpperCase()} standard...`); this.notify(INFO, 'Capturing Standard', `Capturing ${standard.toUpperCase()} standard...`);
const r = await fetch('/api/v1/settings/calibration/add-standard', { const result = await apiPost(API.SETTINGS.CALIBRATION_ADD_STANDARD, { standard });
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ standard })
});
if (!r.ok) throw new Error(`HTTP ${r.status}`); this.notify(SUCCESS, 'Standard Captured', result.message);
const result = await r.json();
this.notify('success', 'Standard Captured', result.message);
this.resetCaptureState(); this.resetCaptureState();
await this.loadWorkingCalibration(); await this.loadWorkingCalibration();
} catch (e) { } catch (e) {
console.error('Capture standard failed:', e); console.error('Capture standard failed:', e);
this.notify('error', 'Calibration Error', 'Failed to capture calibration standard'); this.notify(ERROR, 'Calibration Error', 'Failed to capture calibration standard');
this.resetCaptureState(standard); this.resetCaptureState(standard);
} }
}), 500 }), TIMING.DEBOUNCE_CALIBRATION
); );
} }
@ -270,15 +261,9 @@ export class CalibrationManager {
try { try {
ButtonState.set(this.elements.saveCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Saving...' }); ButtonState.set(this.elements.saveCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Saving...' });
const r = await fetch('/api/v1/settings/calibration/save', { const result = await apiPost(API.SETTINGS.CALIBRATION_SAVE, { name });
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const result = await r.json();
this.notify('success', 'Calibration Saved', result.message); this.notify(SUCCESS, 'Calibration Saved', result.message);
this.hideSteps(); this.hideSteps();
this.elements.calibrationNameInput.value = ''; this.elements.calibrationNameInput.value = '';
@ -291,11 +276,11 @@ export class CalibrationManager {
} }
} catch (e) { } catch (e) {
console.error('Save calibration failed:', e); console.error('Save calibration failed:', e);
this.notify('error', 'Calibration Error', 'Failed to save calibration'); this.notify(ERROR, 'Calibration Error', 'Failed to save calibration');
} finally { } finally {
ButtonState.set(this.elements.saveCalibrationBtn, { state: 'disabled', icon: 'save', text: 'Save Calibration' }); ButtonState.set(this.elements.saveCalibrationBtn, { state: 'disabled', icon: 'save', text: 'Save Calibration' });
} }
}), 400 }), TIMING.DEBOUNCE_CALIBRATION
); );
} }
@ -308,26 +293,23 @@ export class CalibrationManager {
try { try {
ButtonState.set(this.elements.setCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Setting...' }); ButtonState.set(this.elements.setCalibrationBtn, { state: 'loading', icon: 'loader', text: 'Setting...' });
const r = await fetch('/api/v1/settings/calibration/set', { const result = await apiPost(API.SETTINGS.CALIBRATION_SET, {
method: 'POST', name,
headers: { 'Content-Type': 'application/json' }, preset_filename: this.currentPreset.filename
body: JSON.stringify({ name, preset_filename: this.currentPreset.filename })
}); });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const result = await r.json();
this.notify('success', 'Calibration Set', result.message); this.notify(SUCCESS, 'Calibration Set', result.message);
if (this.onCalibrationSet) { if (this.onCalibrationSet) {
await this.onCalibrationSet(); await this.onCalibrationSet();
} }
} catch (e) { } catch (e) {
console.error('Set calibration failed:', e); console.error('Set calibration failed:', e);
this.notify('error', 'Calibration Error', 'Failed to set active calibration'); this.notify(ERROR, 'Calibration Error', 'Failed to set active calibration');
} finally { } finally {
ButtonState.set(this.elements.setCalibrationBtn, { state: 'normal', icon: 'check', text: 'Set Active' }); ButtonState.set(this.elements.setCalibrationBtn, { state: 'normal', icon: 'check', text: 'Set Active' });
} }
}), 300 }), TIMING.DEBOUNCE_CALIBRATION
); );
} }

View File

@ -4,6 +4,8 @@
*/ */
import { Debouncer, RequestGuard, ButtonState } from '../utils.js'; import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
import { apiGet, apiPost } from '../api-client.js';
import { API, TIMING } from '../constants.js';
export class PresetManager { export class PresetManager {
constructor(notifications) { constructor(notifications) {
@ -30,9 +32,7 @@ export class PresetManager {
async loadPresets() { async loadPresets() {
try { try {
const r = await fetch('/api/v1/settings/presets'); const presets = await apiGet(API.SETTINGS.PRESETS);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const presets = await r.json();
this.populateDropdown(presets); this.populateDropdown(presets);
} catch (e) { } catch (e) {
console.error('Presets load failed:', e); console.error('Presets load failed:', e);
@ -88,13 +88,7 @@ export class PresetManager {
try { try {
ButtonState.set(this.elements.setPresetBtn, { state: 'loading', icon: 'loader', text: 'Setting...' }); ButtonState.set(this.elements.setPresetBtn, { state: 'loading', icon: 'loader', text: 'Setting...' });
const r = await fetch('/api/v1/settings/preset/set', { const result = await apiPost(API.SETTINGS.PRESET_SET, { filename });
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename })
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const result = await r.json();
this.notify('success', 'Preset Set', result.message); this.notify('success', 'Preset Set', result.message);
this.currentPreset = { filename }; this.currentPreset = { filename };
@ -108,7 +102,7 @@ export class PresetManager {
} finally { } finally {
ButtonState.set(this.elements.setPresetBtn, { state: 'normal', icon: 'check', text: 'Set Active' }); ButtonState.set(this.elements.setPresetBtn, { state: 'normal', icon: 'check', text: 'Set Active' });
} }
}), 300 }), TIMING.DEBOUNCE_PRESET
); );
} }

View File

@ -4,6 +4,10 @@
*/ */
import { Debouncer, RequestGuard, ButtonState } from '../utils.js'; import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
import { apiGet, apiPost, apiDelete, buildUrl } from '../api-client.js';
import { API, TIMING, NOTIFICATION_TYPES } from '../constants.js';
const { SUCCESS, ERROR, WARNING } = NOTIFICATION_TYPES;
export class ReferenceManager { export class ReferenceManager {
constructor(notifications) { constructor(notifications) {
@ -56,13 +60,15 @@ export class ReferenceManager {
return; return;
} }
const referencesResponse = await fetch(`/api/v1/settings/references?preset_filename=${encodeURIComponent(this.currentPreset.filename)}`); const referencesUrl = buildUrl(API.SETTINGS.REFERENCES, {
if (!referencesResponse.ok) throw new Error(`HTTP ${referencesResponse.status}`); preset_filename: this.currentPreset.filename
this.availableReferences = await referencesResponse.json(); });
this.availableReferences = await apiGet(referencesUrl);
const currentResponse = await fetch(`/api/v1/settings/reference/current?preset_filename=${encodeURIComponent(this.currentPreset.filename)}`); const currentUrl = buildUrl(API.SETTINGS.REFERENCE_CURRENT, {
if (!currentResponse.ok) throw new Error(`HTTP ${currentResponse.status}`); preset_filename: this.currentPreset.filename
this.currentReference = await currentResponse.json(); });
this.currentReference = await apiGet(currentUrl);
this.renderDropdown(this.availableReferences); this.renderDropdown(this.availableReferences);
this.updateInfo(this.currentReference); this.updateInfo(this.currentReference);
@ -139,7 +145,7 @@ export class ReferenceManager {
async handleCreateReference() { async handleCreateReference() {
const name = this.elements.referenceNameInput.value.trim(); const name = this.elements.referenceNameInput.value.trim();
if (!name) { if (!name) {
this.notify('warning', 'Missing Name', 'Please enter a name for the reference'); this.notify(WARNING, 'Missing Name', 'Please enter a name for the reference');
return; return;
} }
@ -150,16 +156,9 @@ export class ReferenceManager {
try { try {
ButtonState.set(this.elements.createReferenceBtn, { state: 'loading', icon: 'loader', text: 'Creating...' }); ButtonState.set(this.elements.createReferenceBtn, { state: 'loading', icon: 'loader', text: 'Creating...' });
const response = await fetch('/api/v1/settings/reference/create', { const result = await apiPost(API.SETTINGS.REFERENCE_CREATE, { name, description });
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`); this.notify(SUCCESS, 'Reference Created', result.message);
const result = await response.json();
this.notify('success', 'Reference Created', result.message);
this.elements.referenceNameInput.value = ''; this.elements.referenceNameInput.value = '';
this.elements.referenceDescriptionInput.value = ''; this.elements.referenceDescriptionInput.value = '';
@ -167,11 +166,11 @@ export class ReferenceManager {
await this.loadReferences(); await this.loadReferences();
} catch (error) { } catch (error) {
console.error('Create reference failed:', error); console.error('Create reference failed:', error);
this.notify('error', 'Reference Error', 'Failed to create reference'); this.notify(ERROR, 'Reference Error', 'Failed to create reference');
} finally { } finally {
ButtonState.set(this.elements.createReferenceBtn, { state: 'normal', icon: 'target', text: 'Capture Reference' }); ButtonState.set(this.elements.createReferenceBtn, { state: 'normal', icon: 'target', text: 'Capture Reference' });
} }
}), 500 }), TIMING.DEBOUNCE_REFERENCE
); );
} }
@ -188,25 +187,18 @@ export class ReferenceManager {
try { try {
ButtonState.set(this.elements.setReferenceBtn, { state: 'loading', icon: 'loader', text: 'Setting...' }); ButtonState.set(this.elements.setReferenceBtn, { state: 'loading', icon: 'loader', text: 'Setting...' });
const response = await fetch('/api/v1/settings/reference/set', { const result = await apiPost(API.SETTINGS.REFERENCE_SET, { name: referenceName });
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: referenceName })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`); this.notify(SUCCESS, 'Reference Set', result.message);
const result = await response.json();
this.notify('success', 'Reference Set', result.message);
await this.loadReferences(); await this.loadReferences();
} catch (error) { } catch (error) {
console.error('Set reference failed:', error); console.error('Set reference failed:', error);
this.notify('error', 'Reference Error', 'Failed to set reference'); this.notify(ERROR, 'Reference Error', 'Failed to set reference');
} finally { } finally {
ButtonState.set(this.elements.setReferenceBtn, { state: 'normal', icon: 'check', text: 'Set Active' }); ButtonState.set(this.elements.setReferenceBtn, { state: 'normal', icon: 'check', text: 'Set Active' });
} }
}), 400 }), TIMING.DEBOUNCE_REFERENCE
); );
} }
@ -216,23 +208,18 @@ export class ReferenceManager {
try { try {
ButtonState.set(this.elements.clearReferenceBtn, { state: 'loading', icon: 'loader', text: 'Clearing...' }); ButtonState.set(this.elements.clearReferenceBtn, { state: 'loading', icon: 'loader', text: 'Clearing...' });
const response = await fetch('/api/v1/settings/reference/current', { const result = await apiDelete(API.SETTINGS.REFERENCE_CURRENT);
method: 'DELETE'
});
if (!response.ok) throw new Error(`HTTP ${response.status}`); this.notify(SUCCESS, 'Reference Cleared', result.message);
const result = await response.json();
this.notify('success', 'Reference Cleared', result.message);
await this.loadReferences(); await this.loadReferences();
} catch (error) { } catch (error) {
console.error('Clear reference failed:', error); console.error('Clear reference failed:', error);
this.notify('error', 'Reference Error', 'Failed to clear reference'); this.notify(ERROR, 'Reference Error', 'Failed to clear reference');
} finally { } finally {
ButtonState.set(this.elements.clearReferenceBtn, { state: 'normal', icon: 'x', text: 'Clear' }); ButtonState.set(this.elements.clearReferenceBtn, { state: 'normal', icon: 'x', text: 'Clear' });
} }
}), 400 }), TIMING.DEBOUNCE_REFERENCE
); );
} }
@ -249,23 +236,18 @@ export class ReferenceManager {
try { try {
ButtonState.set(this.elements.deleteReferenceBtn, { state: 'loading', icon: 'loader', text: 'Deleting...' }); ButtonState.set(this.elements.deleteReferenceBtn, { state: 'loading', icon: 'loader', text: 'Deleting...' });
const response = await fetch(`/api/v1/settings/reference/${encodeURIComponent(referenceName)}`, { const result = await apiDelete(API.SETTINGS.REFERENCE_ITEM(referenceName));
method: 'DELETE'
});
if (!response.ok) throw new Error(`HTTP ${response.status}`); this.notify(SUCCESS, 'Reference Deleted', result.message);
const result = await response.json();
this.notify('success', 'Reference Deleted', result.message);
await this.loadReferences(); await this.loadReferences();
} catch (error) { } catch (error) {
console.error('Delete reference failed:', error); console.error('Delete reference failed:', error);
this.notify('error', 'Reference Error', 'Failed to delete reference'); this.notify(ERROR, 'Reference Error', 'Failed to delete reference');
} finally { } finally {
ButtonState.set(this.elements.deleteReferenceBtn, { state: 'normal', icon: 'trash-2', text: 'Delete' }); ButtonState.set(this.elements.deleteReferenceBtn, { state: 'normal', icon: 'trash-2', text: 'Delete' });
} }
}), 400 }), TIMING.DEBOUNCE_REFERENCE
); );
} }