start of react migration
This commit is contained in:
818
electron-ui/src/old/renderer.js
Normal file
818
electron-ui/src/old/renderer.js
Normal file
@ -0,0 +1,818 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user