add services/web-ui/public/js/api.js
This commit is contained in:
parent
481f8f43f0
commit
ee9e6865ab
1 changed files with 348 additions and 0 deletions
348
services/web-ui/public/js/api.js
Normal file
348
services/web-ui/public/js/api.js
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
// Wild Dragon MAM - API Helper Module
|
||||
|
||||
const API_BASE = '/api/v1';
|
||||
const CAPTURE_BASE = '/capture';
|
||||
|
||||
/**
|
||||
* Wrapper around fetch with JSON parsing and error handling
|
||||
*/
|
||||
async function api(path, options = {}) {
|
||||
const url = `${API_BASE}${path}`;
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
...options,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }));
|
||||
throw new Error(error.message || `API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
console.error(`API Error on ${path}:`, error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around fetch for capture endpoints
|
||||
*/
|
||||
async function captureApi(path, options = {}) {
|
||||
const url = `${CAPTURE_BASE}${path}`;
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
...options,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }));
|
||||
throw new Error(error.message || `Capture API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
console.error(`Capture API Error on ${path}:`, error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ASSET API CALLS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get all assets with optional filtering
|
||||
*/
|
||||
async function getAssets(filters = {}) {
|
||||
const params = new URLSearchParams(filters);
|
||||
const path = `/assets${params.toString() ? '?' + params.toString() : ''}`;
|
||||
return api(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single asset by ID
|
||||
*/
|
||||
async function getAsset(assetId) {
|
||||
return api(`/assets/${assetId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get signed streaming URL for an asset
|
||||
*/
|
||||
async function getAssetStreamUrl(assetId) {
|
||||
return api(`/assets/${assetId}/stream`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update asset metadata (tags, notes, etc)
|
||||
*/
|
||||
async function updateAsset(assetId, data) {
|
||||
return api(`/assets/${assetId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search assets by query
|
||||
*/
|
||||
async function searchAssets(query) {
|
||||
return api(`/assets/search?q=${encodeURIComponent(query)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset status and metadata
|
||||
*/
|
||||
async function getAssetMetadata(assetId) {
|
||||
return api(`/assets/${assetId}/metadata`);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PROJECT API CALLS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get all projects
|
||||
*/
|
||||
async function getProjects() {
|
||||
return api('/projects');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single project by ID
|
||||
*/
|
||||
async function getProject(projectId) {
|
||||
return api(`/projects/${projectId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new project
|
||||
*/
|
||||
async function createProject(name, description = '') {
|
||||
return api('/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, description }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project metadata
|
||||
*/
|
||||
async function updateProject(projectId, data) {
|
||||
return api(`/projects/${projectId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a project
|
||||
*/
|
||||
async function deleteProject(projectId) {
|
||||
return api(`/projects/${projectId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// BIN API CALLS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get all bins for a project
|
||||
*/
|
||||
async function getBins(projectId) {
|
||||
return api(`/projects/${projectId}/bins`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single bin by ID
|
||||
*/
|
||||
async function getBin(projectId, binId) {
|
||||
return api(`/projects/${projectId}/bins/${binId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bin in a project
|
||||
*/
|
||||
async function createBin(projectId, name, description = '') {
|
||||
return api(`/projects/${projectId}/bins`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, description }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bin metadata
|
||||
*/
|
||||
async function updateBin(projectId, binId, data) {
|
||||
return api(`/projects/${projectId}/bins/${binId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bin
|
||||
*/
|
||||
async function deleteBin(projectId, binId) {
|
||||
return api(`/projects/${projectId}/bins/${binId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// CAPTURE API CALLS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get list of available capture devices
|
||||
*/
|
||||
async function getCaptureDevices() {
|
||||
return captureApi('/devices');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get capture status
|
||||
*/
|
||||
async function getCaptureStatus() {
|
||||
return captureApi('/status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current recording state
|
||||
*/
|
||||
async function getRecordingStatus() {
|
||||
return captureApi('/recording/status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording
|
||||
*/
|
||||
async function startRecording(deviceId, projectId, binId, clipName) {
|
||||
return captureApi('/recording/start', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
device_id: deviceId,
|
||||
project_id: projectId,
|
||||
bin_id: binId,
|
||||
clip_name: clipName,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop recording
|
||||
*/
|
||||
async function stopRecording() {
|
||||
return captureApi('/recording/stop', {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent capture sessions
|
||||
*/
|
||||
async function getRecentCaptures(limit = 10) {
|
||||
return captureApi(`/sessions?limit=${limit}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timecode for current recording
|
||||
*/
|
||||
async function getRecordingTimecode() {
|
||||
return captureApi('/recording/timecode');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Format bytes to human-readable size
|
||||
*/
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration in seconds to HH:MM:SS
|
||||
*/
|
||||
function formatDuration(seconds) {
|
||||
if (!seconds || seconds < 0) return '00:00:00';
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return [hours, minutes, secs]
|
||||
.map(v => v.toString().padStart(2, '0'))
|
||||
.join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status badge text from status code
|
||||
*/
|
||||
function getStatusLabel(status) {
|
||||
const labels = {
|
||||
ingesting: 'Ingesting',
|
||||
processing: 'Processing',
|
||||
ready: 'Ready',
|
||||
error: 'Error',
|
||||
archived: 'Archived',
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status badge CSS class from status code
|
||||
*/
|
||||
function getStatusBadgeClass(status) {
|
||||
return `badge-${status}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce function for search
|
||||
*/
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttle function for scroll/resize events
|
||||
*/
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue