fix: refactor

This commit is contained in:
2026-07-20 10:39:07 +07:00
parent 3c77e98956
commit c8ebdb50b0
21 changed files with 2862 additions and 110 deletions
+49
View File
@@ -0,0 +1,49 @@
/* SonicForge Studio - DAW Custom Stylesheet */
body {
background-color: #1a1a1a;
color: #c0c0c0;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
overflow: hidden;
user-select: none;
}
.daw-bg { background-color: #1e1e1e; }
.daw-panel { background-color: #262626; }
.daw-header { background-color: #2e2e2e; }
.daw-border { border-color: #181818; }
.daw-track-active { background-color: #333333; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: #141414; }
::-webkit-scrollbar-thumb { background: #3a3a3a; border: 2px solid #141414; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #4a4a4a; }
.knob-container { position: relative; width: 28px; height: 28px; }
.knob-dial { transform-origin: center; transition: transform 0.1s ease; }
.selection-interactive-box { min-width: 4px; }
.no-scrollbar {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.no-scrollbar::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
/* Axis Labels & Waveform HD Canvas styling */
.axis-label {
font-size: 10px;
font-weight: 600;
color: #64748b;
font-family: monospace;
}
.clip-title-tag {
background: rgba(15, 23, 42, 0.85);
border: 1px solid rgba(51, 65, 85, 0.6);
color: #e2e8f0;
font-weight: 600;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
backdrop-filter: blur(4px);
}
+1
View File
@@ -0,0 +1 @@
// [DEPRECATED] Superseded by inline version in index.html (single-file DAW). Keep for reference only.
+1
View File
@@ -0,0 +1 @@
// [DEPRECATED] Superseded by inline version in index.html (single-file DAW). Keep for reference only.
+1
View File
@@ -0,0 +1 @@
// [DEPRECATED] Superseded by inline version in index.html (single-file DAW). Keep for reference only.
@@ -0,0 +1 @@
// [DEPRECATED] Superseded by inline version in index.html (single-file DAW). Keep for reference only.
@@ -0,0 +1 @@
// [DEPRECATED] Superseded by inline version in index.html (single-file DAW). Keep for reference only.
@@ -0,0 +1 @@
// [DEPRECATED] Superseded by inline version in index.html (single-file DAW). Keep for reference only.
+50
View File
@@ -0,0 +1,50 @@
// SonicForge Studio API Service
window.API_BASE_URL = window.API_BASE_URL || window.location.origin;
(function() {
function getAuthToken() {
return localStorage.getItem('sonic_token') || '';
}
function getAuthHeaders() {
const token = getAuthToken();
return {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
};
}
async function apiRequest(endpoint, options = {}) {
const url = `${window.API_BASE_URL}${endpoint}`;
const headers = { ...getAuthHeaders(), ...options.headers };
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
localStorage.removeItem('sonic_token');
localStorage.removeItem('sonic_user');
}
const data = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(data.detail || data.message || 'Lỗi kết nối API Server');
}
return data;
}
window.SonicAPI = {
login: (username, password) => apiRequest('/api/v1/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) }),
register: (username, email, password) => apiRequest('/api/v1/auth/register', { method: 'POST', body: JSON.stringify({ username, email, password }) }),
changePassword: (old_password, new_password) => apiRequest('/api/v1/auth/change-password', { method: 'POST', body: JSON.stringify({ old_password, new_password }) }),
getProfile: () => apiRequest('/api/v1/auth/profile', { method: 'GET' }),
listUsers: () => apiRequest('/api/v1/admin/users', { method: 'GET' }),
updateUserQuota: (userId, storageLimitMb, maxTracks = 16) => apiRequest(`/api/v1/admin/quotas/${userId}`, { method: 'PUT', body: JSON.stringify({ storage_limit_mb: storageLimitMb, max_tracks: maxTracks }) }),
updateUserRole: (userId, role, isActive = true) => apiRequest(`/api/v1/admin/users/${userId}/role`, { method: 'PUT', body: JSON.stringify({ role, is_active: isActive }) }),
deleteUser: (userId) => apiRequest(`/api/v1/admin/users/${userId}`, { method: 'DELETE' }),
saveTempProject: (dataJson) => apiRequest('/api/v1/projects/temp', { method: 'POST', body: JSON.stringify({ data_json: dataJson }) }),
getTempProject: () => apiRequest('/api/v1/projects/temp', { method: 'GET' }),
saveCloudProject: (name, dataJson) => apiRequest('/api/v1/projects/cloud', { method: 'POST', body: JSON.stringify({ name, data_json: dataJson }) }),
listCloudProjects: () => apiRequest('/api/v1/projects/cloud', { method: 'GET' })
};
})();
+42
View File
@@ -0,0 +1,42 @@
// SonicForge Studio Audio Engine Service
(function() {
let audioCtx = null;
function getAudioContext() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
return audioCtx;
}
function analyzeAudioBufferChannels(audioBuffer) {
if (!audioBuffer) return { channels: 1, isStereo: false, label: 'MONO' };
const numChannels = audioBuffer.numberOfChannels;
const isStereo = numChannels >= 2;
return {
channels: numChannels,
isStereo: isStereo,
label: isStereo ? 'STEREO' : 'MONO',
sampleRate: audioBuffer.sampleRate,
duration: audioBuffer.duration,
length: audioBuffer.length
};
}
async function decodeAudioFile(file) {
const ctx = getAudioContext();
const arrayBuffer = await file.arrayBuffer();
const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
const channelInfo = analyzeAudioBufferChannels(audioBuffer);
return { audioBuffer, channelInfo };
}
window.SonicAudio = {
getAudioContext,
analyzeAudioBufferChannels,
decodeAudioFile
};
})();
+73
View File
@@ -0,0 +1,73 @@
// SonicForge Studio Project Storage & .sfs File Service
(function() {
const SFS_VERSION = "1.0.0";
function exportProjectToSFS(projectState) {
const sfsBundle = {
format: "SONICFORGE_STUDIO_PROJECT",
version: SFS_VERSION,
timestamp: Date.now(),
domain: window.location.origin,
project: {
id: projectState.id || `proj_${Date.now()}`,
name: projectState.name || "Dự án mới",
tracks: (projectState.tracks || []).map(t => ({
id: t.id,
name: t.name,
startTime: t.startTime,
height: t.height,
volumeDb: t.volumeDb,
pan: t.pan,
muted: t.muted,
solo: t.solo,
color: t.color,
markers: t.markers || [],
serverFileId: t.serverFileId || null
}))
}
};
const jsonStr = JSON.stringify(sfsBundle, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${(projectState.name || 'project').replace(/\s+/g, '_')}.sfs`;
a.click();
URL.revokeObjectURL(url);
}
async function importProjectFromSFSFile(file) {
const text = await file.text();
const sfsBundle = JSON.parse(text);
if (sfsBundle.format !== "SONICFORGE_STUDIO_PROJECT") {
throw new Error("Tệp tin không đúng định dạng .sfs của SonicForge Studio");
}
return sfsBundle.project;
}
let autoSaveTimer = null;
function scheduleTempAutoSave(getProjectStateCallback) {
if (autoSaveTimer) clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(async () => {
try {
const state = getProjectStateCallback();
if (!state || !state.tracks || state.tracks.length === 0) return;
const dataJson = JSON.stringify(state);
localStorage.setItem('sonic_temp_project', dataJson);
if (window.SonicAPI && localStorage.getItem('sonic_token')) {
await window.SonicAPI.saveTempProject(dataJson).catch(() => {});
}
} catch (e) {
console.warn("Auto-save temp project warning:", e);
}
}, 2000);
}
window.SonicStorage = {
exportProjectToSFS,
importProjectFromSFSFile,
scheduleTempAutoSave
};
})();