Skip to content

Commit 10551da

Browse files
authored
Add files via upload
1 parent 6d1741a commit 10551da

File tree

6 files changed

+278
-0
lines changed

6 files changed

+278
-0
lines changed

src - English/icon.ico

30.2 KB
Binary file not shown.

src - English/index.html

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!DOCTYPE html>
2+
<html lang="fr">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>TCX activity combiner by 4dapt3rs</title>
7+
<link rel="stylesheet" href="styles.css">
8+
</head>
9+
<body>
10+
<div class="container">
11+
<h1>Select two activities to combine (.tcx)</h1>
12+
<div class="buttons-container">
13+
<button id="openFileBtn" class="btn btn-primary">Activity 1</button>
14+
<button id="openSecondFileBtn" class="btn btn-primary">Activity 2</button>
15+
</div>
16+
<p id="filePath" class="file-path1">No activity selected</p>
17+
<p id="secondFilePath" class="file-path2"></p>
18+
19+
<button id="validateBtn" class="btn btn-success">Combine activities</button>
20+
</div>
21+
22+
<script>
23+
let firstFilePath = '';
24+
let secondFilePath = '';
25+
26+
document.getElementById('openFileBtn').addEventListener('click', async () => {
27+
const filePaths = await window.electronAPI.openFile();
28+
if (filePaths && filePaths.length > 0) {
29+
firstFilePath = filePaths[0];
30+
document.getElementById('filePath').textContent = `Activity 1: ${firstFilePath.split(/[/\\]/).pop()}`;
31+
} else {
32+
firstFilePath = '';
33+
document.getElementById('filePath').textContent = 'No activity selected';
34+
}
35+
});
36+
37+
document.getElementById('openSecondFileBtn').addEventListener('click', async () => {
38+
const filePaths = await window.electronAPI.openFile();
39+
if (filePaths && filePaths.length > 0) {
40+
secondFilePath = filePaths[0];
41+
document.getElementById('secondFilePath').textContent = `Activity 2: ${secondFilePath.split(/[/\\]/).pop()}`;
42+
} else {
43+
secondFilePath = '';
44+
document.getElementById('secondFilePath').textContent = '';
45+
}
46+
});
47+
48+
document.getElementById('validateBtn').addEventListener('click', async () => {
49+
if (!firstFilePath || !secondFilePath) {
50+
alert("Please choose two activities !");
51+
return;
52+
}
53+
54+
try {
55+
const combinedPath = await window.electronAPI.combineFiles(firstFilePath, secondFilePath);
56+
57+
if (!combinedPath) {
58+
alert("An error has occurred !");
59+
return;
60+
}
61+
62+
alert("New activity successfully created !");
63+
} catch (err) {
64+
alert('Error when combining activities');
65+
console.error(err);
66+
}
67+
});
68+
</script>
69+
</body>
70+
</html>

src - English/main.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
2+
const fs = require('fs');
3+
const path = require('path');
4+
let win;
5+
6+
function createWindow() {
7+
win = new BrowserWindow({
8+
icon: 'icon.ico',
9+
width: 700,
10+
height: 500,
11+
webPreferences: {
12+
nodeIntegration: false,
13+
contextIsolation: true,
14+
preload: path.join(__dirname, 'preload.js'),
15+
},
16+
autoHideMenuBar: true,
17+
});
18+
19+
win.loadFile('index.html');
20+
21+
win.on('closed', () => {
22+
win = null;
23+
});
24+
}
25+
26+
27+
28+
app.whenReady().then(createWindow);
29+
30+
app.on('window-all-closed', () => {
31+
if (process.platform !== 'darwin') {
32+
app.quit();
33+
}
34+
});
35+
36+
app.on('activate', () => {
37+
if (win === null) {
38+
createWindow();
39+
}
40+
});
41+
42+
43+
44+
ipcMain.handle('open-file-dialog', async () => {
45+
const result = await dialog.showOpenDialog(win, {
46+
properties: ['openFile']
47+
});
48+
return result.filePaths;
49+
});
50+
51+
ipcMain.handle('combine-files', async (event, file1, file2) => {
52+
try {
53+
const activity1 = fs.readFileSync(file1, 'utf-8');
54+
const activity2 = fs.readFileSync(file2, 'utf-8');
55+
56+
const lignes1 = activity1.split('\n');
57+
const lignes2 = activity2.split('\n');
58+
59+
// <Creator xsi:type
60+
const index1 = lignes1.findIndex(ligne => ligne.includes('<Creator xsi:type'));
61+
const part1 = index1 !== -1 ? lignes1.slice(0, index1) : lignes1;
62+
63+
// <Lap StartTime
64+
const index2 = lignes2.findIndex(ligne => ligne.includes('<Lap StartTime'));
65+
const part2 = index2 !== -1 ? lignes2.slice(index2) : [];
66+
67+
const combinedContent = [
68+
...part1,
69+
...part2,
70+
].join('\n');
71+
72+
const { filePath, canceled } = await dialog.showSaveDialog(win, {
73+
title: 'Save your new activity',
74+
defaultPath: 'new_activity.tcx',
75+
filters: [{ name: 'tcx files', extensions: ['tcx'] }]
76+
});
77+
78+
if (canceled || !filePath) {
79+
return null;
80+
}
81+
82+
fs.writeFileSync(filePath, combinedContent);
83+
return filePath;
84+
85+
} catch (error) {
86+
console.error("Error when creating the activity:", error);
87+
throw error;
88+
}
89+
});
90+

src - English/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "tcx-activity-combiner",
3+
"version": "2.0",
4+
"description": "TCX activity combiner (English version)",
5+
"main": "main.js",
6+
"scripts": {
7+
"start": "electron .",
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "Adapters",
11+
"license": "GPL-3.0-only",
12+
"devDependencies": {
13+
"electron": "^23.3.13"
14+
}
15+
}

src - English/preload.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const { contextBridge, ipcRenderer } = require('electron');
2+
3+
contextBridge.exposeInMainWorld('electronAPI', {
4+
openFile: () => ipcRenderer.invoke('open-file-dialog'),
5+
combineFiles: (file1, file2) => ipcRenderer.invoke('combine-files', file1, file2)
6+
});

src - English/styles.css

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* Global styles */
2+
* {
3+
margin: 0;
4+
padding: 0;
5+
box-sizing: border-box;
6+
}
7+
8+
body {
9+
font-family: 'Arial', sans-serif;
10+
background-color: #242424;
11+
color: #333;
12+
display: flex;
13+
justify-content: center;
14+
align-items: center;
15+
height: 100vh;
16+
-webkit-touch-callout: none;
17+
-webkit-user-select: none;
18+
-khtml-user-select: none;
19+
-moz-user-select: none;
20+
-ms-user-select: none;
21+
user-select: none;
22+
}
23+
24+
25+
/* Main container */
26+
.container {
27+
text-align: center;
28+
background-color: #111010;
29+
padding: 30px;
30+
border-radius: 8px;
31+
box-shadow: 0 0 15px #818181;
32+
width: 75%;
33+
max-width: 600px;
34+
}
35+
36+
37+
/* Text style */
38+
h1 {
39+
font-size: 2rem;
40+
color: #c5c5c5;
41+
margin-bottom: 20px;
42+
}
43+
44+
45+
/* Button style */
46+
.buttons-container {
47+
margin-bottom: 10px;
48+
}
49+
50+
.btn {
51+
font-size: 1rem;
52+
padding: 12px 24px;
53+
margin: 10px;
54+
border: none;
55+
border-radius: 5px;
56+
cursor: pointer;
57+
transition: background-color 0.3s, transform 0.3s;
58+
}
59+
60+
.btn:hover {
61+
transform: scale(1.05);
62+
box-shadow: 4px 3px 3px #818181;
63+
}
64+
65+
.btn-primary {
66+
background-color: #4c4eaf;
67+
color: white;
68+
}
69+
70+
.btn-primary:hover {
71+
background-color: #4c4eaf;
72+
box-shadow: 0 0 3px #818181;
73+
}
74+
75+
.btn-success {
76+
background-color: #4c4eaf;
77+
color: white;
78+
}
79+
80+
.btn-success:hover {
81+
background-color: #4c4eaf;
82+
box-shadow: 0 0 3px #818181;
83+
}
84+
85+
.file-path1 {
86+
font-size: 1rem;
87+
color: #c5c5c5;
88+
margin-top: 5px;
89+
}
90+
91+
.file-path2 {
92+
font-size: 1rem;
93+
color: #c5c5c5;
94+
margin-top: 5px;
95+
margin-bottom: 10px ;
96+
}
97+

0 commit comments

Comments
 (0)