@@ -578,69 +574,67 @@ document.addEventListener("DOMContentLoaded", async () => {
`;
// 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";
+ 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"
+ '#existing-collections',
);
- const newSaveTitleInput = overlay.querySelector("#new-save-title");
+ const newSaveTitleInput = overlay.querySelector('#new-save-title');
const createCollectionBtn = overlay.querySelector(
- "#create-collection-btn"
+ '#create-collection-btn',
);
const saveToCollectionBtn = overlay.querySelector(
- "#save-to-collection-btn"
+ '#save-to-collection-btn',
);
- const cancelSaveBtn = overlay.querySelector("#cancel-save-btn");
+ const cancelSaveBtn = overlay.querySelector('#cancel-save-btn');
if (savedTrimInfo.title) {
newSaveTitleInput.value = savedTrimInfo.title;
}
// Save to collection
- saveToCollectionBtn.addEventListener("click", async () => {
+ saveToCollectionBtn.addEventListener('click', async () => {
const newTitle = document
- .getElementById("new-save-title")
+ .getElementById('new-save-title')
.value.trim();
- const settings = await ipcRenderer.invoke("load-settings");
+ const settings = await ipcRenderer.invoke('load-settings');
const selectedCollection = existingCollectionsSelect.value;
if (!selectedCollection) {
- alert("Please select or create a collection");
+ alert('Please select or create a collection');
return;
}
try {
await ipcRenderer.invoke(
- "delete-old-file",
+ 'delete-old-file',
settings.outputFolder,
globalState.currentSection,
- savedTrimInfo.title
+ savedTrimInfo.title,
);
await ipcRenderer.invoke(
- "save-trimmed-file",
+ 'save-trimmed-file',
path.basename(filePath),
globalState.currentSection,
selectedCollection,
instanceState.trimStart,
instanceState.trimEnd,
- newTitle
+ newTitle,
);
-
-
const saveResult = await ipcRenderer.invoke(
- "save-trimmed-audio",
+ 'save-trimmed-audio',
{
originalFilePath: filePath,
outputFolder: settings.outputFolder,
@@ -648,7 +642,7 @@ document.addEventListener("DOMContentLoaded", async () => {
title: newTitle,
trimStart: instanceState.trimStart,
trimEnd: instanceState.trimEnd,
- }
+ },
);
if (saveResult.success) {
@@ -667,39 +661,40 @@ document.addEventListener("DOMContentLoaded", async () => {
// Refresh the view
} catch (error) {
- alert("Error saving file: " + error.message);
+ alert('Error saving file: ' + error.message);
}
});
// Cancel button
- cancelSaveBtn.addEventListener("click", () => {
+ cancelSaveBtn.addEventListener('click', () => {
document.body.removeChild(overlay);
});
} catch (error) {
- console.error("Error creating save dialog:", error);
+ console.error('Error creating save dialog:', error);
}
});
- deletebutton.addEventListener("click", async () => {
+ 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.`);
+ 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);
+ await ipcRenderer.invoke('delete-file', filePath);
// Remove from UI
trimmerContainer.remove();
// Optional: Notify user
- alert("File deleted successfully");
+ alert('File deleted successfully');
// Refresh the current section view
await loadCollectionFiles(globalState.currentSection);
await populateCollectionsList();
} catch (error) {
- console.error("Error deleting file:", error);
+ console.error('Error deleting file:', error);
}
}
});
@@ -709,13 +704,13 @@ document.addEventListener("DOMContentLoaded", async () => {
}
// Initial load of untrimmed files and collections
- await loadCollectionFiles("untrimmed");
+ await loadCollectionFiles('untrimmed');
await populateCollectionsList();
// Listen for new untrimmed files
- ipcRenderer.on("new-untrimmed-file", async (event, filePath) => {
+ ipcRenderer.on('new-untrimmed-file', async (event, filePath) => {
// Refresh the untrimmed section
- await loadCollectionFiles("untrimmed");
+ await loadCollectionFiles('untrimmed');
await populateCollectionsList();
});
@@ -728,8 +723,8 @@ document.addEventListener("DOMContentLoaded", async () => {
// Add collection button handler
document
- .getElementById("add-collection-btn")
- .addEventListener("click", async () => {
+ .getElementById('add-collection-btn')
+ .addEventListener('click', async () => {
try {
// Create a dialog to input new collection name
const dialogHtml = `
@@ -760,32 +755,32 @@ document
`;
// 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";
+ 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 newCollectionInput = overlay.querySelector('#new-collection-input');
const createCollectionConfirmBtn = overlay.querySelector(
- "#create-collection-confirm-btn"
+ '#create-collection-confirm-btn',
);
const createCollectionCancelBtn = overlay.querySelector(
- "#create-collection-cancel-btn"
+ '#create-collection-cancel-btn',
);
// Create collection when confirm button is clicked
- createCollectionConfirmBtn.addEventListener("click", async () => {
+ createCollectionConfirmBtn.addEventListener('click', async () => {
const newCollectionName = newCollectionInput.value.trim();
if (newCollectionName) {
try {
- await ipcRenderer.invoke("add-new-collection", newCollectionName);
+ await ipcRenderer.invoke('add-new-collection', newCollectionName);
// Remove dialog
document.body.removeChild(overlay);
@@ -794,30 +789,30 @@ document
await populateCollectionsList();
} catch (error) {
// Show error in the dialog
- const errorDiv = document.createElement("div");
+ const errorDiv = document.createElement('div');
errorDiv.textContent = error.message;
- errorDiv.style.color = "red";
- errorDiv.style.marginTop = "10px";
- overlay.querySelector("div").appendChild(errorDiv);
+ 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);
+ 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", () => {
+ 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);
+ console.error('Error creating new collection dialog:', error);
}
});
diff --git a/electron-ui/src/styles.css b/electron-ui/src/old/styles.css
similarity index 100%
rename from electron-ui/src/styles.css
rename to electron-ui/src/old/styles.css
diff --git a/electron-ui/src/renderer/App.css b/electron-ui/src/renderer/App.css
new file mode 100644
index 0000000..d054f54
--- /dev/null
+++ b/electron-ui/src/renderer/App.css
@@ -0,0 +1,62 @@
+/*
+ * @NOTE: Prepend a `~` to css file paths that are in your node_modules
+ * See https://github.com/webpack-contrib/sass-loader#imports
+ */
+body {
+ position: relative;
+ color: white;
+ height: 100vh;
+ background: linear-gradient(
+ 200.96deg,
+ #fedc2a -29.09%,
+ #dd5789 51.77%,
+ #7a2c9e 129.35%
+ );
+ font-family: sans-serif;
+ overflow-y: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+button {
+ background-color: white;
+ padding: 10px 20px;
+ border-radius: 10px;
+ border: none;
+ appearance: none;
+ font-size: 1.3rem;
+ box-shadow: 0px 8px 28px -6px rgba(24, 39, 75, 0.12),
+ 0px 18px 88px -4px rgba(24, 39, 75, 0.14);
+ transition: all ease-in 0.1s;
+ cursor: pointer;
+ opacity: 0.9;
+}
+
+button:hover {
+ transform: scale(1.05);
+ opacity: 1;
+}
+
+li {
+ list-style: none;
+}
+
+a {
+ text-decoration: none;
+ height: fit-content;
+ width: fit-content;
+ margin: 10px;
+}
+
+a:hover {
+ opacity: 1;
+ text-decoration: none;
+}
+
+.Hello {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 20px 0;
+}
\ No newline at end of file
diff --git a/electron-ui/src/renderer/App.tsx b/electron-ui/src/renderer/App.tsx
new file mode 100644
index 0000000..c2159e8
--- /dev/null
+++ b/electron-ui/src/renderer/App.tsx
@@ -0,0 +1,57 @@
+import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
+import icon from '../../assets/icon.svg';
+import './App.css';
+import AudioTrimmer from './components/AudioTrimer';
+
+function Hello() {
+ return (
+
+ {/*
+

+
+
electron-react-boilerplate
+
*/}
+
+
+ );
+}
+
+export default function App() {
+ return (
+
+
+ } />
+
+
+ );
+}
diff --git a/electron-ui/src/renderer/components/AudioTrimer.tsx b/electron-ui/src/renderer/components/AudioTrimer.tsx
new file mode 100644
index 0000000..8b4c4be
--- /dev/null
+++ b/electron-ui/src/renderer/components/AudioTrimer.tsx
@@ -0,0 +1,235 @@
+import React, { useEffect, useMemo, useState } from 'react';
+// import WaveSurfer from 'wavesurfer.js';
+import Regions from 'wavesurfer.js/dist/plugins/regions.esm.js';
+// import { useWavesurfer } from '@wavesurfer/react';
+import { BlockList } from 'net';
+
+import WavesurferPlayer from '@wavesurfer/react';
+// import { IpcRenderer } from 'electron';
+
+export interface AudioTrimmerProps {
+ filePath: string;
+ section: string;
+ title?: string;
+ trimStart?: number;
+ trimEnd?: number;
+ onSave?: (trimStart: number, trimEnd: number, title?: string) => void;
+ onDelete?: () => void;
+}
+
+function getBaseName(filePath: string) {
+ return filePath.split(/[\\/]/).pop() || filePath;
+}
+
+export default function AudioTrimmer({ filePath }: { filePath: string }) {
+ const [blobUrl, setBlobUrl] = useState
(undefined);
+ const [wavesurfer, setWavesurfer] = useState(null);
+ const [isPlaying, setIsPlaying] = useState(false);
+
+ const plugins = useMemo(() => {
+ return [Regions.create()];
+ }, []);
+
+ useEffect(() => {
+ let url: string | null = null;
+ async function fetchAudio() {
+ // console.log('Loading audio buffer for file:', filePath);
+ const buffer =
+ await window.electron.ipcRenderer.loadAudioBuffer(filePath);
+ if (buffer && !buffer.error) {
+ const audioData = buffer.data ? new Uint8Array(buffer.data) : buffer;
+ url = URL.createObjectURL(new Blob([audioData]));
+ setBlobUrl(url);
+ console.log('Audio blob URL created:', url);
+ }
+ }
+ fetchAudio();
+ return () => {
+ if (url) URL.revokeObjectURL(url);
+ };
+ }, [filePath]);
+
+ const onReady = (ws) => {
+ setWavesurfer(ws);
+ setIsPlaying(false);
+ // setDuration(ws.getDuration());
+ // console.log('Wavesurfer ready, duration:', ws.getDuration());
+ // console.log('Wavesurfer regions plugin:', ws.plugins[0]);
+ ws.plugins[0].addRegion?.({
+ start: 0,
+ end: ws.getDuration(),
+ color: 'rgba(132, 81, 224, 0.3)',
+ drag: false,
+ resize: true,
+ });
+ };
+
+ const onPlayPause = () => {
+ if (wavesurfer === null) return;
+ wavesurfer.playPause();
+ };
+
+ // useEffect(() => {
+ // if (!containerRef.current || !blobUrl) return;
+ // const ws = WaveSurfer.create({
+ // container: containerRef.current,
+ // waveColor: 'purple',
+ // url: blobUrl,
+ // height: 100,
+ // width: 600,
+ // });
+ // return () => ws.destroy();
+ // }, [blobUrl]);
+
+ return (
+
+ {/*
*/}
+
setIsPlaying(true)}
+ onPause={() => setIsPlaying(false)}
+ plugins={plugins}
+ />
+
+
+ );
+}
+
+// export default function AudioTrimmer({
+// filePath,
+// section,
+// title,
+// trimStart = 0,
+// trimEnd,
+// onSave,
+// onDelete,
+// }: AudioTrimmerProps) {
+// const [wavesurfer, setWavesurfer] = useState(null);
+// const waveformRef = useRef(null);
+// const wavesurferRef = useRef(null);
+// const [region, setRegion] = useState<{ start: number; end: number }>({
+// start: trimStart,
+// end: trimEnd || 0,
+// });
+// // eslint-disable-next-line @typescript-eslint/no-unused-vars
+// const [duration, setDuration] = useState(0);
+
+// useEffect(() => {
+// if (!waveformRef.current) return;
+
+// // const regions = Regions.create({
+// // color: 'rgba(132, 81, 224, 0.3)',
+// // drag: false,
+// // resize: true,
+// // });
+
+// const regions = Regions.create();
+// const ws = WaveSurfer.create({
+// container: waveformRef.current,
+// waveColor: '#ccb1ff',
+// progressColor: '#6e44ba',
+// // responsive: true,
+// height: 100,
+// hideScrollbar: true,
+// backend: 'WebAudio',
+// plugins: [regions],
+// });
+
+// wavesurferRef.current = ws;
+// ws.load(`file://${filePath}`);
+
+// ws.on('ready', () => {
+// setDuration(ws.getDuration());
+// regions.clearRegions();
+// // ws.clearRegions();
+// regions.addRegion({
+// start: trimStart,
+// end: trimEnd || ws.getDuration(),
+// color: 'rgba(132, 81, 224, 0.3)',
+// drag: false,
+// resize: true,
+// });
+// });
+
+// regions.on('region-updated', (updatedRegion: any) => {
+// setRegion({
+// start: Math.max(0, updatedRegion.start),
+// end: Math.min(ws.getDuration(), updatedRegion.end),
+// });
+// });
+
+// // eslint-disable-next-line consistent-return
+// return () => {
+// ws.destroy();
+// };
+// }, [filePath, trimStart, trimEnd]);
+
+// const formatTime = (seconds: number) => {
+// const minutes = Math.floor(seconds / 60);
+// const remainingSeconds = Math.floor(seconds % 60);
+// return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
+// };
+
+// return (
+//
+//
+//
+//
+// {title || getBaseName(filePath)}
+//
+//
+// {title ? getBaseName(filePath) : 'hidden'}
+//
+//
{section}
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// Start:
+// {formatTime(region.start)}
+//
+//
+// End:
+// {formatTime(region.end)}
+//
+//
+//
+// );
+// }
+
+// export default AudioTrimmer;
diff --git a/electron-ui/src/renderer/index.ejs b/electron-ui/src/renderer/index.ejs
new file mode 100644
index 0000000..167cf37
--- /dev/null
+++ b/electron-ui/src/renderer/index.ejs
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Hello Electron React!
+
+
+
+
+
diff --git a/electron-ui/src/renderer/index.tsx b/electron-ui/src/renderer/index.tsx
new file mode 100644
index 0000000..8864218
--- /dev/null
+++ b/electron-ui/src/renderer/index.tsx
@@ -0,0 +1,13 @@
+import { createRoot } from 'react-dom/client';
+import App from './App';
+
+const container = document.getElementById('root') as HTMLElement;
+const root = createRoot(container);
+root.render();
+
+// calling IPC exposed from preload script
+window.electron?.ipcRenderer.once('ipc-example', (arg) => {
+ // eslint-disable-next-line no-console
+ console.log(arg);
+});
+window.electron?.ipcRenderer.sendMessage('ipc-example', ['ping']);
diff --git a/electron-ui/src/renderer/preload.d.ts b/electron-ui/src/renderer/preload.d.ts
new file mode 100644
index 0000000..53cc2d7
--- /dev/null
+++ b/electron-ui/src/renderer/preload.d.ts
@@ -0,0 +1,10 @@
+import { ElectronHandler } from '../main/preload';
+
+declare global {
+ // eslint-disable-next-line no-unused-vars
+ interface Window {
+ electron: ElectronHandler;
+ }
+}
+
+export {};
diff --git a/electron-ui/src/renderer/types/index.ts b/electron-ui/src/renderer/types/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/electron-ui/tsconfig.json b/electron-ui/tsconfig.json
new file mode 100644
index 0000000..a3c015d
--- /dev/null
+++ b/electron-ui/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "incremental": true,
+ "target": "es2022",
+ "module": "node16",
+ "lib": ["dom", "es2022"],
+ "jsx": "react-jsx",
+ "strict": true,
+ "sourceMap": true,
+ "moduleResolution": "node16",
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "outDir": ".erb/dll"
+ },
+ "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"]
+}
\ No newline at end of file