819 lines
28 KiB
JavaScript
819 lines
28 KiB
JavaScript
const { ipcRenderer } = require('electron');
|
|
// const path = require('path');
|
|
const WaveSurfer = require('wavesurfer.js');
|
|
const RegionsPlugin = require('wavesurfer.js/dist/plugin/wavesurfer.regions.js');
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Settings Modal Logic
|
|
const settingsModal = document.getElementById('settings-modal');
|
|
const settingsBtn = document.getElementById('settings-btn');
|
|
const restartBtn = document.getElementById('restart-btn');
|
|
const closeModalBtn = document.querySelector('.close-modal');
|
|
const saveSettingsBtn = document.getElementById('save-settings');
|
|
const selectOutputFolderBtn = document.getElementById('select-output-folder');
|
|
const recordingLengthInput = document.getElementById('recording-length');
|
|
const oscPortInput = document.getElementById('osc-port');
|
|
const outputFolderInput = document.getElementById('output-folder');
|
|
const inputDeviceSelect = document.getElementById('input-device');
|
|
|
|
// Open settings modal
|
|
settingsBtn.addEventListener('click', async () => {
|
|
try {
|
|
// Request microphone permissions first
|
|
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
|
|
// Load current settings
|
|
const settings = await ipcRenderer.invoke('load-settings');
|
|
|
|
// Populate input devices
|
|
const devices = await ipcRenderer.invoke('get-input-devices');
|
|
|
|
if (devices.length === 0) {
|
|
inputDeviceSelect.innerHTML = '<option>No microphones found</option>';
|
|
} else {
|
|
inputDeviceSelect.innerHTML = devices
|
|
.map(
|
|
(device) => `<option value="${device.id}">${device.name}</option>`,
|
|
)
|
|
.join('');
|
|
}
|
|
|
|
// Set current settings
|
|
recordingLengthInput.value = settings.recordingLength;
|
|
outputFolderInput.value = settings.outputFolder;
|
|
inputDeviceSelect.value = settings.inputDevice;
|
|
oscPortInput.value = settings.oscPort;
|
|
|
|
settingsModal.style.display = 'block';
|
|
} catch (error) {
|
|
console.error('Error loading settings or devices:', error);
|
|
alert('Please grant microphone permissions to list audio devices');
|
|
}
|
|
});
|
|
|
|
restartBtn.addEventListener('click', async () => {
|
|
try {
|
|
await ipcRenderer.invoke('restart');
|
|
} catch (error) {
|
|
console.error('Error restarting:', error);
|
|
alert('Failed to restart Clipper');
|
|
}
|
|
});
|
|
|
|
// Close settings modal
|
|
closeModalBtn.addEventListener('click', () => {
|
|
settingsModal.style.display = 'none';
|
|
});
|
|
|
|
// Select output folder
|
|
selectOutputFolderBtn.addEventListener('click', async () => {
|
|
const folderPath = await ipcRenderer.invoke('select-output-folder');
|
|
if (folderPath) {
|
|
outputFolderInput.value = folderPath;
|
|
}
|
|
});
|
|
|
|
// Save settings
|
|
saveSettingsBtn.addEventListener('click', async () => {
|
|
const settings = {
|
|
recordingLength: parseInt(recordingLengthInput.value),
|
|
oscPort: parseInt(oscPortInput.value),
|
|
outputFolder: outputFolderInput.value,
|
|
inputDevice: inputDeviceSelect.value,
|
|
};
|
|
|
|
const saved = await ipcRenderer.invoke('save-settings', settings);
|
|
if (saved) {
|
|
settingsModal.style.display = 'none';
|
|
} else {
|
|
alert('Failed to save settings');
|
|
}
|
|
});
|
|
|
|
// Close modal if clicked outside
|
|
window.addEventListener('click', (event) => {
|
|
if (event.target === settingsModal) {
|
|
settingsModal.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
const audioTrimmersList = document.getElementById('audio-trimmers-list');
|
|
const collectionsList = document.getElementById('collections-list');
|
|
//const currentSectionTitle = document.getElementById("current-section-title");
|
|
|
|
// Global state to persist wavesurfer instances and trimmer states
|
|
const globalState = {
|
|
wavesurferInstances: {},
|
|
trimmerStates: {},
|
|
currentSection: 'untrimmed',
|
|
trimmerElements: {},
|
|
};
|
|
// Utility function to format time
|
|
function formatTime(seconds) {
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = Math.floor(seconds % 60);
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
// Populate collections list
|
|
async function populateCollectionsList() {
|
|
const collections = await ipcRenderer.invoke('get-collections');
|
|
|
|
collectionsList.innerHTML = '';
|
|
|
|
// Always add Untrimmed section first
|
|
const untrimmedItem = document.createElement('div');
|
|
untrimmedItem.classList.add('collection-item');
|
|
untrimmedItem.textContent = 'Untrimmed';
|
|
untrimmedItem.dataset.collection = 'untrimmed';
|
|
|
|
untrimmedItem.addEventListener('click', () => {
|
|
loadCollectionFiles('untrimmed');
|
|
});
|
|
|
|
collectionsList.appendChild(untrimmedItem);
|
|
|
|
// Add other collections
|
|
collections.forEach((collection) => {
|
|
if (collection === 'untrimmed') {
|
|
return;
|
|
}
|
|
const collectionItem = document.createElement('div');
|
|
collectionItem.classList.add('collection-item');
|
|
collectionItem.textContent = collection;
|
|
collectionItem.dataset.collection = collection;
|
|
|
|
collectionItem.addEventListener('click', () => {
|
|
loadCollectionFiles(collection);
|
|
});
|
|
|
|
collectionsList.appendChild(collectionItem);
|
|
});
|
|
}
|
|
|
|
// Modify loadCollectionFiles function
|
|
async function loadCollectionFiles(collection) {
|
|
if (collection !== globalState.currentSection) {
|
|
//Clear existing trimmers and reset global state
|
|
Object.keys(globalState.trimmerElements).forEach((filePath) => {
|
|
const trimmerElement = globalState.trimmerElements[filePath];
|
|
if (trimmerElement && trimmerElement.parentNode) {
|
|
trimmerElement.parentNode.removeChild(trimmerElement);
|
|
}
|
|
});
|
|
|
|
// Reset global state
|
|
globalState.trimmerElements = {};
|
|
globalState.wavesurferInstances = {};
|
|
globalState.trimmerStates = {};
|
|
}
|
|
|
|
// Reset active states
|
|
document.querySelectorAll('.collection-item').forEach((el) => {
|
|
el.classList.remove('active');
|
|
});
|
|
|
|
// Set active state only for existing items
|
|
const activeItem = document.querySelector(
|
|
`.collection-item[data-collection="${collection}"]`,
|
|
);
|
|
|
|
// Only add active class if the item exists
|
|
if (activeItem) {
|
|
activeItem.classList.add('active');
|
|
}
|
|
|
|
// Update section title and global state
|
|
//currentSectionTitle.textContent = collection;
|
|
globalState.currentSection = collection;
|
|
|
|
// Load files
|
|
const files = await ipcRenderer.invoke('get-collection-files', collection);
|
|
|
|
// Add new trimmers with saved trim information
|
|
for (const file of files) {
|
|
const filePath = file.originalPath || file.fileName;
|
|
|
|
// If loading a collection, use saved trim information
|
|
//if (collection !== "untrimmed") {
|
|
// Store trim information in global state before creating trimmer
|
|
// globalState.trimmerStates[filePath] = {
|
|
// trimStart: file.trimStart || 0,
|
|
// trimEnd: file.trimEnd || 0,
|
|
// regionStart: file.trimStart || 0,
|
|
// regionEnd: file.trimEnd || 0,
|
|
// originalPath: file.originalPath,
|
|
// };
|
|
//}
|
|
|
|
createAudioTrimmer(filePath, collection);
|
|
}
|
|
}
|
|
// Create audio trimmer for a single file
|
|
async function createAudioTrimmer(filePath, section) {
|
|
// Check if trimmer already exists
|
|
if (globalState.trimmerElements[filePath]) {
|
|
return globalState.trimmerElements[filePath];
|
|
}
|
|
|
|
const savedTrimInfo = await ipcRenderer.invoke(
|
|
'get-trim-info',
|
|
globalState.currentSection,
|
|
path.basename(filePath),
|
|
);
|
|
// Create trimmer container
|
|
const trimmerContainer = document.createElement('div');
|
|
trimmerContainer.classList.add('audio-trimmer-item');
|
|
trimmerContainer.dataset.filepath = filePath;
|
|
|
|
// Create header with title and controls
|
|
const trimmerHeader = document.createElement('div');
|
|
trimmerHeader.classList.add('audio-trimmer-header');
|
|
|
|
// Title container
|
|
const titleContainer = document.createElement('div');
|
|
titleContainer.classList.add('audio-trimmer-title-container');
|
|
|
|
if (savedTrimInfo.title) {
|
|
// Title
|
|
const title = document.createElement('div');
|
|
title.classList.add('audio-trimmer-title');
|
|
title.textContent = savedTrimInfo.title;
|
|
titleContainer.appendChild(title);
|
|
|
|
// Filename
|
|
const fileName = document.createElement('div');
|
|
fileName.classList.add('audio-trimmer-filename');
|
|
fileName.textContent = path.basename(filePath);
|
|
titleContainer.appendChild(fileName);
|
|
} else {
|
|
// Title (using filename if no custom title)
|
|
const title = document.createElement('div');
|
|
title.classList.add('audio-trimmer-title');
|
|
title.textContent = path.basename(filePath);
|
|
titleContainer.appendChild(title);
|
|
|
|
// Filename
|
|
const fileName = document.createElement('div');
|
|
fileName.classList.add('audio-trimmer-filename');
|
|
fileName.textContent = 'hidden';
|
|
fileName.style.opacity = 0;
|
|
titleContainer.appendChild(fileName);
|
|
}
|
|
|
|
// Controls container
|
|
const controlsContainer = document.createElement('div');
|
|
controlsContainer.classList.add('audio-trimmer-controls');
|
|
|
|
// Play/Pause and Save buttons
|
|
const playPauseBtn = document.createElement('button');
|
|
playPauseBtn.classList.add('play-pause-btn');
|
|
playPauseBtn.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z"/>
|
|
</svg>
|
|
`;
|
|
|
|
const saveTrimButton = document.createElement('button');
|
|
saveTrimButton.classList.add('save-trim');
|
|
saveTrimButton.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
<path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/>
|
|
</svg>
|
|
`;
|
|
|
|
const deletebutton = document.createElement('button');
|
|
deletebutton.classList.add('play-pause-btn');
|
|
deletebutton.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
|
</svg>
|
|
`;
|
|
|
|
controlsContainer.appendChild(playPauseBtn);
|
|
controlsContainer.appendChild(saveTrimButton);
|
|
controlsContainer.appendChild(deletebutton);
|
|
|
|
// Assemble header
|
|
trimmerHeader.appendChild(titleContainer);
|
|
trimmerHeader.appendChild(controlsContainer);
|
|
trimmerContainer.appendChild(trimmerHeader);
|
|
|
|
// Waveform container
|
|
const waveformContainer = document.createElement('div');
|
|
waveformContainer.classList.add('waveform-container');
|
|
const waveformId = `waveform-${path.basename(
|
|
filePath,
|
|
path.extname(filePath),
|
|
)}`;
|
|
waveformContainer.innerHTML = `
|
|
<div id="${waveformId}" class="waveform"></div>
|
|
`;
|
|
trimmerContainer.appendChild(waveformContainer);
|
|
|
|
// Time displays
|
|
const timeInfo = document.createElement('div');
|
|
timeInfo.classList.add('trim-info');
|
|
timeInfo.innerHTML = `
|
|
<div class="trim-time">
|
|
<span>Start: </span>
|
|
<span class="trim-start-time">0:00</span>
|
|
</div>
|
|
<div class="trim-time">
|
|
<span>End: </span>
|
|
<span class="trim-end-time">0:00</span>
|
|
</div>
|
|
`;
|
|
// const zoomContainer = document.createElement('div');
|
|
// zoomContainer.className = 'zoom-controls';
|
|
// zoomContainer.innerHTML = `
|
|
// <button class="zoom-in">+</button>
|
|
// <button class="zoom-out">-</button>
|
|
// <input type="range" min="1" max="200" value="100" class="zoom-slider">
|
|
// `;
|
|
// timeInfo.appendChild(zoomContainer);
|
|
|
|
// const zoomInBtn = zoomContainer.querySelector('.zoom-in');
|
|
// const zoomOutBtn = zoomContainer.querySelector('.zoom-out');
|
|
// const zoomSlider = zoomContainer.querySelector('.zoom-slider');
|
|
|
|
// // Zoom functionality
|
|
// const updateZoom = (zoomLevel) => {
|
|
// // Get the current scroll position and width
|
|
// const scrollContainer = wavesurfer.container.querySelector('wave');
|
|
// const currentScroll = scrollContainer.scrollLeft;
|
|
// const containerWidth = scrollContainer.clientWidth;
|
|
|
|
// // Calculate the center point of the current view
|
|
// //const centerTime = wavesurfer.getCurrentTime();
|
|
|
|
// // Apply zoom
|
|
// wavesurfer.zoom(zoomLevel);
|
|
|
|
// // Recalculate scroll to keep the center point in view
|
|
// const newDuration = wavesurfer.getDuration();
|
|
// const pixelsPerSecond = wavesurfer.drawer.width / newDuration;
|
|
// const centerPixel = centerTime * pixelsPerSecond;
|
|
|
|
// // Adjust scroll to keep the center point in the same relative position
|
|
// const newScrollLeft = centerPixel - (containerWidth / 2);
|
|
// scrollContainer.scrollLeft = Math.max(0, newScrollLeft);
|
|
// console.log(currentScroll, newScrollLeft);
|
|
// };
|
|
|
|
// zoomInBtn.addEventListener('click', () => {
|
|
// const currentZoom = parseInt(zoomSlider.value);
|
|
// zoomSlider.value = Math.min(currentZoom + 20, 200);
|
|
// updateZoom(zoomSlider.value);
|
|
// });
|
|
|
|
// zoomOutBtn.addEventListener('click', () => {
|
|
// const currentZoom = parseInt(zoomSlider.value);
|
|
// zoomSlider.value = Math.max(currentZoom - 20, 1);
|
|
// updateZoom(zoomSlider.value);
|
|
// });
|
|
|
|
// zoomSlider.addEventListener('input', (e) => {
|
|
// updateZoom(e.target.value);
|
|
// });
|
|
|
|
trimmerContainer.appendChild(timeInfo);
|
|
|
|
// Add to list and global state
|
|
audioTrimmersList.appendChild(trimmerContainer);
|
|
globalState.trimmerElements[filePath] = trimmerContainer;
|
|
|
|
// Determine the file to load (original or current)
|
|
const fileToLoad =
|
|
section === 'untrimmed'
|
|
? filePath
|
|
: globalState.trimmerStates[filePath]?.originalPath || filePath;
|
|
|
|
// Setup wavesurfer
|
|
const wavesurfer = WaveSurfer.create({
|
|
container: `#${waveformId}`,
|
|
waveColor: '#ccb1ff',
|
|
progressColor: '#6e44ba',
|
|
responsive: true,
|
|
height: 100,
|
|
hideScrollbar: true,
|
|
// barWidth: 2,
|
|
// barRadius: 3,
|
|
cursorWidth: 1,
|
|
backend: 'WebAudio',
|
|
plugins: [
|
|
RegionsPlugin.create({
|
|
color: 'rgba(132, 81, 224, 0.3)',
|
|
drag: false,
|
|
resize: true,
|
|
dragSelection: {
|
|
slop: 20,
|
|
},
|
|
}),
|
|
// ZoomPlugin.create({
|
|
// // the amount of zoom per wheel step, e.g. 0.5 means a 50% magnification per scroll
|
|
// scale: 0.5,
|
|
// // Optionally, specify the maximum pixels-per-second factor while zooming
|
|
// maxZoom: 100,
|
|
// }),
|
|
],
|
|
});
|
|
|
|
// Store wavesurfer instance in global state
|
|
globalState.wavesurferInstances[filePath] = wavesurfer;
|
|
|
|
// Use existing trim state or create new one
|
|
globalState.trimmerStates[filePath] = globalState.trimmerStates[filePath] ||
|
|
savedTrimInfo || {
|
|
trimStart: 0,
|
|
trimEnd: 0,
|
|
regionStart: undefined,
|
|
regionEnd: undefined,
|
|
originalPath: fileToLoad,
|
|
};
|
|
const startTimeDisplay = timeInfo.querySelector('.trim-start-time');
|
|
const endTimeDisplay = timeInfo.querySelector('.trim-end-time');
|
|
|
|
// Load audio file
|
|
wavesurfer.load(`file://${fileToLoad}`);
|
|
|
|
// Setup play/pause button
|
|
playPauseBtn.onclick = () => {
|
|
const instanceState = globalState.trimmerStates[filePath];
|
|
if (wavesurfer.isPlaying()) {
|
|
wavesurfer.pause();
|
|
playPauseBtn.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z"/>
|
|
</svg>
|
|
`;
|
|
} else {
|
|
// Always start from the trim start
|
|
wavesurfer.play(instanceState.trimStart, instanceState.trimEnd);
|
|
playPauseBtn.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
|
</svg>
|
|
`;
|
|
}
|
|
};
|
|
|
|
// When audio is ready
|
|
wavesurfer.on('ready', async () => {
|
|
const instanceState = globalState.trimmerStates[filePath];
|
|
|
|
// Set trim times based on saved state or full duration
|
|
if (instanceState.trimStart) {
|
|
// Create initial region covering trim or full duration
|
|
wavesurfer.clearRegions();
|
|
const region = wavesurfer.addRegion({
|
|
start: instanceState.trimStart,
|
|
end: instanceState.trimEnd,
|
|
color: 'rgba(132, 81, 224, 0.3)',
|
|
drag: false,
|
|
resize: true,
|
|
});
|
|
}
|
|
instanceState.trimStart = instanceState.trimStart || 0;
|
|
instanceState.trimEnd = instanceState.trimEnd || wavesurfer.getDuration();
|
|
|
|
// Update time displays
|
|
startTimeDisplay.textContent = formatTime(instanceState.trimStart);
|
|
endTimeDisplay.textContent = formatTime(instanceState.trimEnd);
|
|
|
|
// Store region details
|
|
instanceState.regionStart = instanceState.trimStart;
|
|
instanceState.regionEnd = instanceState.trimEnd;
|
|
|
|
// Listen for region updates
|
|
wavesurfer.on('region-update-end', async (updatedRegion) => {
|
|
// Ensure the region doesn't exceed audio duration
|
|
instanceState.trimStart = Math.max(0, updatedRegion.start);
|
|
instanceState.trimEnd = Math.min(
|
|
wavesurfer.getDuration(),
|
|
updatedRegion.end,
|
|
);
|
|
|
|
// Update time displays
|
|
startTimeDisplay.textContent = formatTime(instanceState.trimStart);
|
|
endTimeDisplay.textContent = formatTime(instanceState.trimEnd);
|
|
|
|
// Store updated region details
|
|
instanceState.regionStart = instanceState.trimStart;
|
|
instanceState.regionEnd = instanceState.trimEnd;
|
|
|
|
globalState.trimmerStates[filePath] = instanceState;
|
|
|
|
// Adjust region if it exceeds bounds
|
|
updatedRegion.update({
|
|
start: instanceState.trimStart,
|
|
end: instanceState.trimEnd,
|
|
});
|
|
});
|
|
|
|
// Handle region creation
|
|
wavesurfer.on('region-created', (newRegion) => {
|
|
// Remove all other regions
|
|
Object.keys(wavesurfer.regions.list).forEach((id) => {
|
|
if (wavesurfer.regions.list[id] !== newRegion) {
|
|
wavesurfer.regions.list[id].remove();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Reset to trim start when audio finishes
|
|
wavesurfer.on('finish', () => {
|
|
wavesurfer.setCurrentTime(instanceState.trimStart);
|
|
playPauseBtn.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z"/>
|
|
</svg>
|
|
`;
|
|
});
|
|
|
|
// Save trimmed audio functionality
|
|
saveTrimButton.addEventListener('click', async () => {
|
|
try {
|
|
// Get current collections
|
|
const collections = await ipcRenderer.invoke('get-collections');
|
|
|
|
// Create a dialog to select or create a collection
|
|
const dialogHtml = `
|
|
<div id="save-collection-dialog"
|
|
style="
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
background-color: #2a2a2a;
|
|
transform: translate(-50%, -50%);
|
|
padding: 10px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
z-index: 1000;
|
|
">
|
|
<div style="">
|
|
<input type="text" id="new-save-title" placeholder="Title">
|
|
</div>
|
|
<select id="existing-collections" style="width: 100%; margin-top: 10px;">
|
|
${collections
|
|
.map((col) =>
|
|
col === 'untrimmed'
|
|
? ''
|
|
: `<option value="${col}" ${
|
|
globalState.currentSection === col ? 'selected' : ''
|
|
}>${col}</option>`,
|
|
)
|
|
.join('')}
|
|
</select>
|
|
|
|
<div style="margin-top: 10px; display: flex; justify-content: space-between;">
|
|
<button class="play-pause-btn" id="cancel-save-btn" style="width: 48%; ">Cancel</button>
|
|
<button class="play-pause-btn" id="save-to-collection-btn" style="width: 48%;">Save</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Create dialog overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.style.position = 'fixed';
|
|
overlay.style.top = '0';
|
|
overlay.style.left = '0';
|
|
overlay.style.width = '100%';
|
|
overlay.style.height = '100%';
|
|
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
|
|
overlay.style.zIndex = '999';
|
|
overlay.innerHTML = dialogHtml;
|
|
document.body.appendChild(overlay);
|
|
|
|
const existingCollectionsSelect = overlay.querySelector(
|
|
'#existing-collections',
|
|
);
|
|
|
|
const newSaveTitleInput = overlay.querySelector('#new-save-title');
|
|
const createCollectionBtn = overlay.querySelector(
|
|
'#create-collection-btn',
|
|
);
|
|
const saveToCollectionBtn = overlay.querySelector(
|
|
'#save-to-collection-btn',
|
|
);
|
|
const cancelSaveBtn = overlay.querySelector('#cancel-save-btn');
|
|
|
|
if (savedTrimInfo.title) {
|
|
newSaveTitleInput.value = savedTrimInfo.title;
|
|
}
|
|
|
|
// Save to collection
|
|
saveToCollectionBtn.addEventListener('click', async () => {
|
|
const newTitle = document
|
|
.getElementById('new-save-title')
|
|
.value.trim();
|
|
const settings = await ipcRenderer.invoke('load-settings');
|
|
|
|
const selectedCollection = existingCollectionsSelect.value;
|
|
|
|
if (!selectedCollection) {
|
|
alert('Please select or create a collection');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await ipcRenderer.invoke(
|
|
'delete-old-file',
|
|
settings.outputFolder,
|
|
globalState.currentSection,
|
|
savedTrimInfo.title,
|
|
);
|
|
await ipcRenderer.invoke(
|
|
'save-trimmed-file',
|
|
path.basename(filePath),
|
|
globalState.currentSection,
|
|
selectedCollection,
|
|
instanceState.trimStart,
|
|
instanceState.trimEnd,
|
|
newTitle,
|
|
);
|
|
|
|
const saveResult = await ipcRenderer.invoke(
|
|
'save-trimmed-audio',
|
|
{
|
|
originalFilePath: filePath,
|
|
outputFolder: settings.outputFolder,
|
|
collectionName: selectedCollection,
|
|
title: newTitle,
|
|
trimStart: instanceState.trimStart,
|
|
trimEnd: instanceState.trimEnd,
|
|
},
|
|
);
|
|
|
|
if (saveResult.success) {
|
|
// Close save dialog
|
|
// Remove dialog
|
|
document.body.removeChild(overlay);
|
|
trimmerContainer.remove();
|
|
await loadCollectionFiles(globalState.currentSection);
|
|
await populateCollectionsList();
|
|
|
|
// Optional: Show success message
|
|
//alert(`Trimmed audio saved to ${saveResult.filePath}`);
|
|
} else {
|
|
alert(`Failed to save trimmed audio: ${saveResult.error}`);
|
|
}
|
|
|
|
// Refresh the view
|
|
} catch (error) {
|
|
alert('Error saving file: ' + error.message);
|
|
}
|
|
});
|
|
|
|
// Cancel button
|
|
cancelSaveBtn.addEventListener('click', () => {
|
|
document.body.removeChild(overlay);
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating save dialog:', error);
|
|
}
|
|
});
|
|
deletebutton.addEventListener('click', async () => {
|
|
// Create confirmation dialog
|
|
const confirmDelete = confirm(
|
|
`Are you sure you want to delete this audio file?\nThis will remove the original file and any trimmed versions.`,
|
|
);
|
|
|
|
if (confirmDelete) {
|
|
try {
|
|
// Delete original file
|
|
await ipcRenderer.invoke('delete-file', filePath);
|
|
|
|
// Remove from UI
|
|
trimmerContainer.remove();
|
|
|
|
// Optional: Notify user
|
|
alert('File deleted successfully');
|
|
|
|
// Refresh the current section view
|
|
await loadCollectionFiles(globalState.currentSection);
|
|
await populateCollectionsList();
|
|
} catch (error) {
|
|
console.error('Error deleting file:', error);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
return trimmerContainer;
|
|
}
|
|
|
|
// Initial load of untrimmed files and collections
|
|
await loadCollectionFiles('untrimmed');
|
|
await populateCollectionsList();
|
|
|
|
// Listen for new untrimmed files
|
|
ipcRenderer.on('new-untrimmed-file', async (event, filePath) => {
|
|
// Refresh the untrimmed section
|
|
await loadCollectionFiles('untrimmed');
|
|
await populateCollectionsList();
|
|
});
|
|
|
|
// Periodic refresh
|
|
setInterval(async () => {
|
|
await populateCollectionsList();
|
|
await loadCollectionFiles(globalState.currentSection);
|
|
}, 5000);
|
|
});
|
|
|
|
// Add collection button handler
|
|
document
|
|
.getElementById('add-collection-btn')
|
|
.addEventListener('click', async () => {
|
|
try {
|
|
// Create a dialog to input new collection name
|
|
const dialogHtml = `
|
|
<div id="new-collection-dialog" style="
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background-color: #2a2a2a;
|
|
padding: 0px 10px 10px 10px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
z-index: 1000;
|
|
">
|
|
<h4>Create New Collection</h4>
|
|
<input
|
|
type="text"
|
|
id="new-collection-input"
|
|
placeholder="Enter collection name"
|
|
style="width: 100%; align-self: center; padding: 10px; margin-bottom: 10px;"
|
|
>
|
|
<div style="display: flex; justify-content: space-between;">
|
|
|
|
<button id="create-collection-cancel-btn" class="play-pause-btn" style="width: 48%; ">Cancel</button>
|
|
<button id="create-collection-confirm-btn" class="play-pause-btn" style="width: 48%; ">Create</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Create dialog overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.style.position = 'fixed';
|
|
overlay.style.top = '0';
|
|
overlay.style.left = '0';
|
|
overlay.style.width = '100%';
|
|
overlay.style.height = '100%';
|
|
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
|
|
overlay.style.zIndex = '999';
|
|
overlay.innerHTML = dialogHtml;
|
|
document.body.appendChild(overlay);
|
|
|
|
const newCollectionInput = overlay.querySelector('#new-collection-input');
|
|
const createCollectionConfirmBtn = overlay.querySelector(
|
|
'#create-collection-confirm-btn',
|
|
);
|
|
const createCollectionCancelBtn = overlay.querySelector(
|
|
'#create-collection-cancel-btn',
|
|
);
|
|
|
|
// Create collection when confirm button is clicked
|
|
createCollectionConfirmBtn.addEventListener('click', async () => {
|
|
const newCollectionName = newCollectionInput.value.trim();
|
|
|
|
if (newCollectionName) {
|
|
try {
|
|
await ipcRenderer.invoke('add-new-collection', newCollectionName);
|
|
|
|
// Remove dialog
|
|
document.body.removeChild(overlay);
|
|
|
|
// Refresh collections list
|
|
await populateCollectionsList();
|
|
} catch (error) {
|
|
// Show error in the dialog
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.textContent = error.message;
|
|
errorDiv.style.color = 'red';
|
|
errorDiv.style.marginTop = '10px';
|
|
overlay.querySelector('div').appendChild(errorDiv);
|
|
}
|
|
} else {
|
|
// Show error if input is empty
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.textContent = 'Collection name cannot be empty';
|
|
errorDiv.style.color = 'red';
|
|
errorDiv.style.marginTop = '10px';
|
|
overlay.querySelector('div').appendChild(errorDiv);
|
|
}
|
|
});
|
|
|
|
// Cancel button closes the dialog
|
|
createCollectionCancelBtn.addEventListener('click', () => {
|
|
document.body.removeChild(overlay);
|
|
});
|
|
|
|
// Focus the input when dialog opens
|
|
newCollectionInput.focus();
|
|
} catch (error) {
|
|
console.error('Error creating new collection dialog:', error);
|
|
}
|
|
});
|