u2a 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,10 +10,6 @@
10
10
  </a>
11
11
  </div>
12
12
 
13
- ![U2A Banner](https://img.shields.io/badge/U2A-URL%20to%20App-blue)
14
- ![License](https://img.shields.io/badge/license-GPL--3.0-green)
15
- ![Version](https://img.shields.io/badge/version-1.0.0-orange)
16
-
17
13
  **Convert any website into a desktop application with a single command.**
18
14
 
19
15
  U2A is a command-line utility that allows you to transform any web URL into a standalone desktop application using Electron. It works across Windows, macOS, and Linux platforms.
@@ -27,145 +23,22 @@ U2A is a command-line utility that allows you to transform any web URL into a st
27
23
  - 📊 Detailed logging for troubleshooting
28
24
  - 🏷️ Customizable application names and window sizes
29
25
 
30
- ## Installation
31
-
32
- ```bash
33
- npm install -g u2a
34
- ```
35
-
36
- ## Usage
37
-
38
- ### Creating an App
39
-
40
- To create a desktop application from a website:
41
-
42
- ```bash
43
- u2a create <url> [--name <appName>] [--width <width>] [--height <height>]
44
- ```
45
-
46
- Example:
47
- ```bash
48
- u2a create github.com --name "GitHub App" --width 1200 --height 800
49
- ```
50
-
51
- This will:
52
- 1. Download the website's favicon (if available)
53
- 2. Create an Electron wrapper application with the specified name and window size
54
- 3. Add the application to your system menu/launcher
55
- 4. Track the application in the U2A database
56
-
57
- ### Listing Your Apps
58
-
59
- To see all the applications you've created:
60
-
61
- ```bash
62
- u2a list
63
- ```
64
-
65
- This will display a list of all created applications with their details:
66
- - Application name
67
- - Original URL
68
- - Creation date
69
- - Application directory
70
-
71
- ### Removing an App
72
-
73
- To remove an application:
74
-
75
- ```bash
76
- u2a remove <appName>
77
- ```
78
-
79
- Example:
80
- ```bash
81
- u2a remove "GitHub App"
82
- ```
83
-
84
- This will:
85
- 1. Remove the application from your system menu/launcher
86
- 2. Delete the application files
87
- 3. Remove the entry from the U2A database
88
-
89
-
90
- ## Executables && Setups
91
-
92
- U2A also supports the creation of executables files and setup files. We will see how to it here.
93
-
94
- > [!NOTE]
95
- > Windows executables and setups are confirmed working, macos and linux ones may be unstable.
96
-
97
-
98
- ### Creating an executable
99
-
100
- To directly create a windows, macos or linux executable, you can use the following command:
101
-
102
- ```bash
103
- u2a create <url> [...] --executable [windows|darwin|linux] [--arch <architecture>]
104
- ```
105
-
106
- This will:
107
- 1. Temporarily install the application
108
- 2. Create an executable and move it to your working directory
109
- 3. Delete the application
110
-
111
- > [!WARNING]
112
- > To launch an executable, you will need all the files that are created by U2A.
113
-
114
- ### Creating a setup
115
-
116
- You can also directly create a setup file, so people can install it on their machine. Use the `--executable [...] --setup` argument to do so.
117
-
118
- This will:
119
- 1. Temporarily install the application
120
- 2. Create an executable and move it to your working directory
121
- 3. Create a setup file and move it to your working directory
122
- 4. Delete the application
123
-
124
-
125
- > [!WARNING]
126
- > To use the setup, you will need all the files that are created by U2A.
127
-
128
-
129
- ## How It Works
130
-
131
- `This dont apply for executables and setup`
132
- U2A creates a minimal Electron application that loads the specified website URL. It:
133
-
134
- 1. Downloads the site's favicon to use as the application icon
135
- 2. Generates a main.js file with Electron configuration
136
- 3. Creates a package.json with necessary dependencies
137
- 4. Installs required Node modules
138
- 5. Adds appropriate desktop integration for your operating system
139
- 6. Maintains a database of created applications for easy management
140
-
141
- ## System Requirements
142
-
143
- - Node.js 12.0 or higher
144
- - npm 6.0 or higher
145
- - Windows, macOS, or Linux
146
-
147
- ## Configuration
148
-
149
- U2A stores all configuration, application data, and logs in the `.u2a` directory in your home folder:
150
-
151
- - `~/.u2a/apps/` - Application files
152
- - `~/.u2a/logs/` - Log files
153
- - `~/.u2a/db.json` - Application database
154
-
155
- ## Troubleshooting
26
+ ### Quick links:
27
+ - [Install UrlToApp on your system](https://docs.urltoapp.xyz/?p=installation)
156
28
 
157
- If you encounter any issues, check the log files in the `~/.u2a/logs/` directory. Each component has its own log file with detailed information.
29
+ - [Create Webapps locally](https://docs.urltoapp.xyz/?p=create-local-apps) (Applications that load an url)
158
30
 
159
- Set the `DEBUG` environment variable to see additional debug messages:
31
+ - [Delete local Webapps](https://docs.urltoapp.xyz/?p=delete-local-apps)
160
32
 
161
- ```bash
162
- DEBUG=1 u2a create example.com
163
- ```
33
+ - [Create an executable for Windows, MacOs or Linux](https://docs.urltoapp.xyz/?p=create-executable)
164
34
 
165
- ## License
35
+ - [Create a setup file for Windows, MacOs or Linux](https://docs.urltoapp.xyz/?p=create-setup)
166
36
 
167
- This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
37
+ - [Common issues](https://docs.urltoapp.xyz/?p=common-issues)
168
38
 
169
- ## Author
170
39
 
171
- Created by [Douxx](https://douxx.tech)
40
+ > [!WARNING]
41
+ > Full support for MacOs and Linux apps not guaranteed.
42
+
43
+ ---
44
+ Brought to you by [@douxxtech](https://github.com/douxxtech)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u2a",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "URL to App - Turn any URL into a desktop application",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -190,7 +190,7 @@ function generateMainJs(appName, url, iconPath, options = {}) {
190
190
  const height = options.height || 800;
191
191
 
192
192
  return `
193
- const { app, BrowserWindow, Menu, shell } = require('electron');
193
+ const { app, BrowserWindow, Menu, shell, nativeTheme } = require('electron');
194
194
  const path = require('path');
195
195
  const fs = require('fs');
196
196
 
@@ -202,7 +202,6 @@ const WINDOW_HEIGHT = ${height};
202
202
 
203
203
  let mainWindow;
204
204
  let splashWindow;
205
- let loadErrors = [];
206
205
 
207
206
  function logAppInfo() {
208
207
  const packageJsonPath = path.join(__dirname, 'package.json');
@@ -239,26 +238,15 @@ function logAppInfo() {
239
238
  console.log('--------------------------------\\n');
240
239
  }
241
240
 
242
- function updateSplashScreen(message, isError = false) {
243
- if (splashWindow && !splashWindow.isDestroyed()) {
244
- splashWindow.webContents.executeJavaScript(\`
245
- try {
246
- document.getElementById('loading-text').innerText = "\${message.replace(/"/g, '\\"')}";
247
- } catch (e) {
248
- console.error('Failed to update splash screen:', e);
249
- }
250
- \`).catch(err => console.error('Failed to update splash screen:', err));
251
-
252
- }
253
- }
254
-
255
241
  function createSplashScreen() {
256
242
  splashWindow = new BrowserWindow({
257
- width: 550,
258
- height: 400,
243
+ width: 200,
244
+ height: 40,
259
245
  transparent: true,
260
246
  frame: false,
261
247
  alwaysOnTop: true,
248
+ skipTaskbar: true,
249
+ resizable: false,
262
250
  icon: APP_ICON_PATH,
263
251
  webPreferences: {
264
252
  nodeIntegration: false,
@@ -266,153 +254,85 @@ function createSplashScreen() {
266
254
  }
267
255
  });
268
256
 
257
+ const isDarkMode = nativeTheme.shouldUseDarkColors;
258
+ const bgColor = isDarkMode ? '#333333' : '#f5f5f5';
259
+ const loaderBgColor = isDarkMode ? '#555555' : '#e0e0e0';
260
+ const loaderColor = isDarkMode ? '#ffffff' : '#2563eb';
261
+ const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
262
+
269
263
  const splashHtml = \`
270
264
  <!DOCTYPE html>
271
265
  <html>
272
266
  <head>
273
267
  <meta charset="UTF-8">
274
268
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
275
- <title>Loading...</title>
269
+ <title>Loading</title>
276
270
  <style>
277
- body {
271
+ html, body {
278
272
  margin: 0;
279
273
  padding: 0;
280
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
281
- background-color: var(--bg-primary, #0f172a);
282
- color: var(--text-primary, #f8fafc);
283
- height: 100vh;
284
- display: flex;
285
- flex-direction: column;
286
- justify-content: center;
287
- align-items: center;
288
- overflow: hidden;
289
- border-radius: 20px;
274
+ width: 100%;
275
+ height: 100%;
290
276
  overflow: hidden;
291
- box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.2);
277
+ background-color: transparent;
292
278
  }
293
-
279
+
294
280
  .container {
295
- text-align: center;
296
- width: 90%;
297
- max-width: 500px;
298
- }
299
-
300
- .domain {
301
- font-size: 24px;
302
- margin-bottom: 20px;
303
- color: var(--primary, #2563eb);
304
- }
305
-
306
- .spinner {
307
- width: 50px;
308
- height: 50px;
309
- border: 5px solid rgba(37, 99, 235, 0.2);
310
- border-radius: 50%;
311
- border-top-color: var(--primary, #2563eb);
312
- animation: spin 1s ease-in-out infinite;
313
- margin: 0 auto 20px;
314
- }
315
-
316
- @keyframes spin {
317
- to { transform: rotate(360deg); }
318
- }
319
-
320
- .loading-text {
321
- font-size: 16px;
322
- color: var(--text-secondary, #cbd5e1);
323
- margin-top: 15px;
324
- }
325
-
326
- .loading-text.error {
327
- color: var(--danger, #ef4444);
328
- font-weight: bold;
329
- }
330
-
331
- .progress-bar {
332
281
  width: 100%;
333
- height: 4px;
334
- background-color: var(--bg-secondary, #1e293b);
335
- border-radius: 2px;
336
- overflow: hidden;
337
- margin-top: 15px;
338
- }
339
-
340
- .progress {
341
282
  height: 100%;
342
- width: 0%;
343
- background-color: var(--primary, #2563eb);
344
- animation: progress 3s ease-in-out infinite;
283
+ display: flex;
284
+ justify-content: center;
285
+ align-items: center;
345
286
  }
346
-
347
- @keyframes progress {
348
- 0% { width: 0%; }
349
- 50% { width: 70%; }
350
- 100% { width: 100%; }
287
+
288
+ .loader-container {
289
+ width: 180px;
290
+ height: 12px;
291
+ background-color: \${bgColor};
292
+ border-radius: 6px;
293
+ overflow: hidden;
294
+ box-shadow: 0 2px 8px \${shadowColor};
295
+ padding: 3px;
351
296
  }
352
-
353
- #errors-container {
354
- display: none;
355
- margin-top: 20px;
356
- background-color: rgba(239, 68, 68, 0.1);
357
- border-left: 3px solid var(--danger, #ef4444);
358
- padding: 10px;
359
- border-radius: 4px;
360
- text-align: left;
361
- max-height: 150px;
362
- overflow-y: auto;
297
+
298
+ .loader-bg {
363
299
  width: 100%;
300
+ height: 100%;
301
+ background-color: \${loaderBgColor};
302
+ border-radius: 4px;
303
+ overflow: hidden;
304
+ position: relative;
364
305
  }
365
-
366
- #errors-list {
367
- margin: 0;
368
- padding-left: 20px;
369
- color: var(--danger, #ef4444);
370
- font-size: 14px;
371
- }
372
-
373
- #errors-list li {
374
- margin-bottom: 5px;
375
- }
376
-
377
- .retry-button {
378
- background-color: var(--primary, #2563eb);
379
- color: white;
380
- border: none;
381
- padding: 8px 16px;
306
+
307
+ .loader {
308
+ position: absolute;
309
+ top: 0;
310
+ left: 0;
311
+ height: 100%;
312
+ width: 30%;
313
+ background-color: \${loaderColor};
382
314
  border-radius: 4px;
383
- margin-top: 15px;
384
- cursor: pointer;
385
- font-weight: bold;
386
- transition: background-color 0.2s;
387
- display: none;
315
+ animation: loading 1.5s infinite ease-in-out;
388
316
  }
389
-
390
- .retry-button:hover {
391
- background-color: var(--primary-dark, #1d4ed8);
317
+
318
+ @keyframes loading {
319
+ 0% {
320
+ left: -30%;
321
+ }
322
+ 100% {
323
+ left: 100%;
324
+ }
392
325
  }
393
326
  </style>
394
327
  </head>
395
328
  <body>
396
329
  <div class="container">
397
- <div class="domain">\${APP_NAME}</div>
398
- <div class="spinner"></div>
399
- <div id="loading-text" class="loading-text">Loading...</div>
400
- <div class="progress-bar">
401
- <div class="progress"></div>
330
+ <div class="loader-container">
331
+ <div class="loader-bg">
332
+ <div class="loader"></div>
333
+ </div>
402
334
  </div>
403
- <div id="errors-container">
404
- <h3 style="margin-top: 0; color: var(--danger, #ef4444);">Errors detected:</h3>
405
- <ul id="errors-list"></ul>
406
- </div>
407
- <button id="retry-button" class="retry-button" onclick="window.location.reload()">Retry</button>
408
335
  </div>
409
- <script>
410
- window.showRetryButton = function() {
411
- document.getElementById('retry-button').style.display = 'inline-block';
412
- document.querySelector('.spinner').style.animationPlayState = 'paused';
413
- document.querySelector('.progress').style.animationPlayState = 'paused';
414
- }
415
- </script>
416
336
  </body>
417
337
  </html>
418
338
  \`;
@@ -421,7 +341,15 @@ function createSplashScreen() {
421
341
  fs.writeFileSync(splashPath, splashHtml);
422
342
 
423
343
  splashWindow.loadFile(splashPath);
424
- splashWindow.center();
344
+
345
+ const { screen } = require('electron');
346
+ const primaryDisplay = screen.getPrimaryDisplay();
347
+ const { width, height } = primaryDisplay.workAreaSize;
348
+
349
+ splashWindow.setPosition(
350
+ Math.floor(width / 2 - 100),
351
+ Math.floor(height / 2 - 20)
352
+ );
425
353
  }
426
354
 
427
355
  function createWindow() {
@@ -441,53 +369,7 @@ function createWindow() {
441
369
  }
442
370
  });
443
371
 
444
- mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
445
- const errorMessage = \`Load error (\${errorCode}): \${errorDescription}\`;
446
- loadErrors.push(errorMessage);
447
-
448
- updateSplashScreen(errorMessage, true);
449
-
450
- if (isMainFrame) {
451
- splashWindow.webContents.executeJavaScript('window.showRetryButton()').catch(err => {
452
- console.error('Failed to show retry button:', err);
453
- });
454
- }
455
- });
456
-
457
- mainWindow.webContents.on('certificate-error', (event, url, error, certificate, callback) => {
458
- event.preventDefault();
459
- const errorMessage = \`Certificate error: \${error}\`;
460
- console.error(errorMessage);
461
- loadErrors.push(errorMessage);
462
-
463
- updateSplashScreen(errorMessage, true);
464
- callback(false);
465
- });
466
-
467
- mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
468
- if (level === 2) {
469
- const errorMessage = \`Console: \${message} (line \${line})\`;
470
- loadErrors.push(errorMessage);
471
-
472
- updateSplashScreen('JavaScript error detected', true);
473
- }
474
- });
475
-
476
- mainWindow.webContents.on('did-start-loading', () => {
477
- updateSplashScreen('Connecting to ' + APP_NAME + '...');
478
- });
479
-
480
- mainWindow.webContents.on('did-start-navigation', (event, url) => {
481
- updateSplashScreen('Navigating to ' + new URL(url).host + '...');
482
- });
483
-
484
- mainWindow.webContents.on('dom-ready', () => {
485
- updateSplashScreen('DOM ready, loading resources...');
486
- });
487
-
488
372
  mainWindow.webContents.on('did-finish-load', () => {
489
- updateSplashScreen('Loading complete!');
490
-
491
373
  setTimeout(() => {
492
374
  if (splashWindow && !splashWindow.isDestroyed()) {
493
375
  splashWindow.close();
@@ -523,15 +405,13 @@ function createWindow() {
523
405
 
524
406
  setTimeout(() => {
525
407
  if (splashWindow && !splashWindow.isDestroyed()) {
526
- if (loadErrors.length === 0) {
527
- splashWindow.close();
528
- if (!mainWindow.isVisible()) {
529
- mainWindow.show();
530
- mainWindow.focus();
531
- }
408
+ splashWindow.close();
409
+ if (!mainWindow.isVisible()) {
410
+ mainWindow.show();
411
+ mainWindow.focus();
532
412
  }
533
413
  }
534
- }, 15000);
414
+ }, 10000);
535
415
  }
536
416
 
537
417
  if (process.platform === 'win32') {
@@ -557,9 +437,6 @@ app.on('activate', () => {
557
437
  `;
558
438
  }
559
439
 
560
-
561
-
562
-
563
440
  async function generatePackageJson(appName, iconPath, isExecutable = false, createSetup = false) {
564
441
  const u2aPackagePath = path.resolve(__dirname, '../../package.json');
565
442