aegis-framework 0.3.0 → 0.3.2

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/aegis/cli/cli.py CHANGED
@@ -463,18 +463,189 @@ aegis build
463
463
  └── icon.png # App icon (256x256 recommended)
464
464
  ```
465
465
 
466
- ## 🔌 Aegis API Reference
466
+ ---
467
467
 
468
- ### File Operations
468
+ # Aegis Utility API
469
+
470
+ Aegis includes a powerful utility API that simplifies common JavaScript patterns!
471
+
472
+ ## Event Shortcuts
473
+
474
+ ```javascript
475
+ // Click event (works on single or multiple elements!)
476
+ Aegis.click('#btn', () => alert('Clicked!'));
477
+ Aegis.click('.card', (e) => console.log(e.target));
478
+
479
+ // More event shortcuts
480
+ Aegis.submit('#form', handler);
481
+ Aegis.change('#select', handler);
482
+ Aegis.input('#search', handler);
483
+ Aegis.keypress('#input', handler);
484
+
485
+ // Hover with enter/leave
486
+ Aegis.hover('#card', onEnter, onLeave);
487
+
488
+ // Universal event binding
489
+ Aegis.on('.field', 'focus blur', handler); // Multiple events!
490
+ Aegis.once('#btn', 'click', handler); // Fire once only
491
+ ```
492
+
493
+ ## Element Selection & Manipulation
494
+
495
+ ```javascript
496
+ // Select elements
497
+ const el = Aegis.get('#my-id');
498
+ const all = Aegis.getAll('.cards');
499
+
500
+ // Create elements
501
+ Aegis.create('div', {{
502
+ id: 'my-div',
503
+ class: 'card active',
504
+ html: '<h1>Hello!</h1>',
505
+ style: {{ color: 'red' }},
506
+ on: {{ click: handler }},
507
+ parent: '#container'
508
+ }});
509
+
510
+ // Classes
511
+ Aegis.addClass('#el', 'active');
512
+ Aegis.removeClass('#el', 'hidden');
513
+ Aegis.toggleClass('#el', 'visible');
514
+
515
+ // Styles & Visibility
516
+ Aegis.css('#el', {{ color: 'blue', fontSize: '18px' }});
517
+ Aegis.hide('#modal');
518
+ Aegis.show('#modal');
519
+ Aegis.fadeIn('#el', 300);
520
+ Aegis.fadeOut('#el', 300);
521
+
522
+ // Content
523
+ Aegis.html('#container', '<p>New content</p>');
524
+ Aegis.text('#title', 'Hello World');
525
+ Aegis.val('#input', 'value');
526
+ ```
527
+
528
+ ## Forms
529
+
530
+ ```javascript
531
+ // Serialize form to object
532
+ const data = Aegis.form.serialize('#my-form');
533
+ // {{ name: 'John', email: 'john@email.com' }}
534
+
535
+ // Fill form with data
536
+ Aegis.form.fill('#form', {{ name: 'Jane', email: 'jane@email.com' }});
537
+
538
+ // Validate
539
+ const result = Aegis.form.validate('#form', {{
540
+ email: {{ required: true, email: true }},
541
+ password: {{ required: true, minLength: 8 }}
542
+ }});
543
+ if (!result.valid) console.log(result.errors);
544
+ ```
545
+
546
+ ## HTTP Requests
547
+
548
+ ```javascript
549
+ const users = await Aegis.http.get('/api/users');
550
+ await Aegis.http.post('/api/users', {{ name: 'John' }});
551
+ await Aegis.http.put('/api/users/1', {{ name: 'Jane' }});
552
+ await Aegis.http.delete('/api/users/1');
553
+ ```
554
+
555
+ ## String Utilities
556
+
557
+ ```javascript
558
+ Aegis.string.capitalize('hello'); // 'Hello'
559
+ Aegis.string.slugify('Hello World!'); // 'hello-world'
560
+ Aegis.string.camelCase('hello-world'); // 'helloWorld'
561
+ Aegis.string.truncate('Long text', 5); // 'Long ...'
562
+ Aegis.string.template('Hi {{{{name}}}}!', {{ name: 'John' }}); // 'Hi John!'
563
+ ```
564
+
565
+ ## Array Utilities
566
+
567
+ ```javascript
568
+ Aegis.array.unique([1, 2, 2, 3]); // [1, 2, 3]
569
+ Aegis.array.shuffle([1, 2, 3]); // Random order
570
+ Aegis.array.groupBy(users, 'role'); // Group by key
571
+ Aegis.array.sortBy(users, 'name'); // Sort by key
572
+ Aegis.array.chunk([1,2,3,4,5], 2); // [[1,2], [3,4], [5]]
573
+ ```
574
+
575
+ ## Object Utilities
576
+
577
+ ```javascript
578
+ Aegis.object.clone(obj); // Deep clone
579
+ Aegis.object.merge(obj1, obj2); // Merge
580
+ Aegis.object.pick(obj, ['name']); // Pick keys
581
+ Aegis.object.get(user, 'address.city'); // Deep get
582
+ ```
583
+
584
+ ## Date Utilities
585
+
586
+ ```javascript
587
+ Aegis.date.format(date, 'YYYY-MM-DD'); // '2024-12-28'
588
+ Aegis.date.ago(date); // '5 minutes ago'
589
+ Aegis.date.isToday(date); // true/false
590
+ ```
591
+
592
+ ## Number Utilities
593
+
594
+ ```javascript
595
+ Aegis.number.format(1234567); // '1.234.567'
596
+ Aegis.number.currency(1234.56); // 'R$ 1.234,56'
597
+ Aegis.number.bytes(1536000); // '1.46 MB'
598
+ ```
599
+
600
+ ## Toast Notifications
601
+
602
+ ```javascript
603
+ Aegis.toast('Hello!');
604
+ Aegis.toast('Success!', {{ type: 'success' }});
605
+ Aegis.toast('Error!', {{ type: 'error', duration: 5000 }});
606
+ ```
607
+
608
+ ## Storage
609
+
610
+ ```javascript
611
+ Aegis.storage.set('user', {{ name: 'John' }}); // Auto JSON
612
+ Aegis.storage.get('user'); // Auto parse
613
+ Aegis.storage.remove('user');
614
+ ```
615
+
616
+ ## Validation
617
+
618
+ ```javascript
619
+ Aegis.is.email('test@email.com'); // true
620
+ Aegis.is.url('https://...'); // true
621
+ Aegis.is.empty([]); // true
622
+ Aegis.is.mobile(); // true if mobile
623
+ ```
624
+
625
+ ## More Utilities
626
+
627
+ ```javascript
628
+ await Aegis.clipboard.copy('text'); // Copy to clipboard
629
+ Aegis.debounce(fn, 300); // Debounce
630
+ Aegis.throttle(fn, 100); // Throttle
631
+ await Aegis.delay(1000); // Wait 1 second
632
+ Aegis.uid('card'); // 'card-123456789-abc'
633
+ Aegis.random.uuid(); // Full UUID
634
+ ```
635
+
636
+ ---
637
+
638
+ # 🔌 Backend API (Python Bridge)
639
+
640
+ ## File Operations
469
641
 
470
642
  ```javascript
471
643
  // Read directory contents
472
644
  const dir = await Aegis.read({{ path: '/home/user' }});
473
- console.log(dir.entries); // [{{ name: 'file.txt', isFile: true, size: 1234 }}, ...]
645
+ console.log(dir.entries);
474
646
 
475
647
  // Read file content
476
648
  const file = await Aegis.read({{ path: '/home/user', file: 'data.txt' }});
477
- console.log(file.content);
478
649
 
479
650
  // Write file
480
651
  await Aegis.write({{
@@ -483,166 +654,76 @@ await Aegis.write({{
483
654
  content: 'Hello, Aegis!'
484
655
  }});
485
656
 
486
- // Check if file/directory exists
657
+ // Check if exists
487
658
  const info = await Aegis.exists({{ path: '/home/user/file.txt' }});
488
- if (info.exists && info.isFile) {{
489
- console.log('File exists!');
490
- }}
491
659
 
492
660
  // Create directory
493
661
  await Aegis.mkdir({{ path: '/home/user/new-folder' }});
494
662
 
495
- // Delete file or directory
663
+ // Delete
496
664
  await Aegis.remove({{ path: '/home/user/old-file.txt' }});
497
- await Aegis.remove({{ path: '/home/user/old-folder', recursive: true }});
498
-
499
- // Copy file or directory
500
- await Aegis.copy({{
501
- src: '/home/user/file.txt',
502
- dest: '/home/user/backup/file.txt'
503
- }});
504
665
 
505
- // Move/rename file or directory
506
- await Aegis.move({{
507
- src: '/home/user/old-name.txt',
508
- dest: '/home/user/new-name.txt'
509
- }});
666
+ // Copy & Move
667
+ await Aegis.copy({{ src: '/from', dest: '/to' }});
668
+ await Aegis.move({{ src: '/from', dest: '/to' }});
510
669
  ```
511
670
 
512
- ### Execute Commands
671
+ ## Execute Commands
513
672
 
514
673
  ```javascript
515
- // Run shell command
674
+ // Shell command
516
675
  const result = await Aegis.run({{ sh: 'ls -la' }});
517
- console.log(result.output);
518
- console.log(result.exitCode);
519
-
520
- // Run Python code
521
- const pyResult = await Aegis.run({{ py: 'print(2 + 2)' }});
522
- console.log(pyResult.output); // "4"
523
676
 
524
- // Run async command with streaming output (no UI freeze!)
677
+ // Async with streaming (UI doesn't freeze!)
525
678
  await Aegis.runAsync(
526
- {{ sh: 'apt update' }},
527
- (progress) => {{
528
- console.log(progress.line); // Each line as it comes
529
- }}
679
+ {{ sh: 'long-command' }},
680
+ (progress) => console.log(progress.line)
530
681
  );
531
682
  ```
532
683
 
533
- ### Dialogs
684
+ ## Dialogs
534
685
 
535
686
  ```javascript
536
- // Info dialog
537
- await Aegis.dialog.message({{
538
- type: 'info',
539
- title: 'Success',
540
- message: 'Operation completed!'
541
- }});
542
-
543
- // Confirmation dialog
544
- const confirm = await Aegis.dialog.message({{
545
- type: 'question',
546
- title: 'Confirm',
547
- message: 'Are you sure?',
548
- buttons: 'yesno'
549
- }});
550
- if (confirm.response) {{
551
- // User clicked Yes
552
- }}
553
-
554
- // Open file dialog
555
- const file = await Aegis.dialog.open({{
556
- title: 'Select a file',
557
- filters: [{{ name: 'Images', extensions: ['png', 'jpg', 'gif'] }}]
558
- }});
559
- console.log(file.path);
560
-
561
- // Save file dialog
562
- const savePath = await Aegis.dialog.save({{
563
- title: 'Save as',
564
- defaultName: 'document.txt'
565
- }});
687
+ await Aegis.dialog.message({{ type: 'info', title: 'Hi', message: 'Hello!' }});
688
+ const file = await Aegis.dialog.open({{ title: 'Select file' }});
689
+ const path = await Aegis.dialog.save({{ title: 'Save as' }});
566
690
  ```
567
691
 
568
- ### Download with Progress
692
+ ## Download with Progress
569
693
 
570
694
  ```javascript
571
- // Download file with progress bar
572
695
  await Aegis.download(
573
- {{
574
- url: 'https://example.com/file.zip',
575
- dest: '/home/user/downloads/file.zip'
576
- }},
577
- (progress) => {{
578
- const percent = progress.percent.toFixed(1);
579
- progressBar.style.width = percent + '%';
580
- statusText.textContent = `${{progress.downloaded}} / ${{progress.total}} bytes`;
581
- }}
696
+ {{ url: 'https://example.com/file.zip', dest: '/path/file.zip' }},
697
+ (progress) => console.log(progress.percent + '%')
582
698
  );
583
699
  ```
584
700
 
585
- ### App Control
701
+ ## App Control
586
702
 
587
703
  ```javascript
588
- // Window controls
589
704
  Aegis.app.minimize();
590
705
  Aegis.app.maximize();
591
706
  Aegis.app.quit();
592
-
593
- // Get system paths (localized for your language!)
594
707
  const home = await Aegis.app.getPath({{ name: 'home' }});
595
- const docs = await Aegis.app.getPath({{ name: 'documents' }}); // Returns "Documentos" on pt-BR
596
- const downloads = await Aegis.app.getPath({{ name: 'downloads' }});
597
- // Also: desktop, music, pictures, videos
598
708
  ```
599
709
 
600
- ### Window Control (Frameless Windows)
710
+ ## Window Control (Frameless)
601
711
 
602
712
  ```javascript
603
- // Make element draggable for window movement
604
713
  Aegis.window.moveBar('#titlebar', {{ exclude: '.btn-close' }});
605
-
606
- // Setup resize handles
607
- Aegis.window.resizeHandles({{
608
- '.resize-n': 'n',
609
- '.resize-s': 's',
610
- '.resize-se': 'se',
611
- // Options: n, s, e, w, ne, nw, se, sw
612
- }});
613
-
614
- // Get/set window size
615
- const size = await Aegis.window.getSize();
616
- await Aegis.window.setSize({{ width: 1024, height: 768 }});
617
-
618
- // Get/set window position
619
- const pos = await Aegis.window.getPosition();
620
- await Aegis.window.setPosition({{ x: 100, y: 100 }});
714
+ Aegis.window.resizeHandles({{ '.resize-se': 'se' }});
621
715
  ```
622
716
 
623
- ## 🔒 Security (preload.js)
717
+ ---
624
718
 
625
- Control which APIs your app can access:
719
+ ## 🔒 Security (preload.js)
626
720
 
627
721
  ```javascript
628
- // src/preload.js
629
722
  Aegis.expose([
630
- 'read', // File reading
631
- 'write', // File writing
632
- 'run', // Command execution
633
- 'dialog', // Native dialogs
634
- 'app', // App control
635
- 'window', // Window control
636
- 'download', // Download with progress
637
- 'exists', // File existence
638
- 'mkdir', // Create directories
639
- 'remove', // Delete files
640
- 'copy', // Copy files
641
- 'move' // Move/rename files
723
+ 'read', 'write', 'run', 'dialog', 'app', 'window',
724
+ 'download', 'exists', 'mkdir', 'remove', 'copy', 'move'
642
725
  ]);
643
-
644
- // For maximum security, only expose what you need!
645
- // If you omit 'run', the app cannot execute shell commands
726
+ // Omit 'run' to prevent shell command execution!
646
727
  ```
647
728
 
648
729
  ## ⚙️ Configuration (aegis.config.json)
@@ -650,53 +731,36 @@ Aegis.expose([
650
731
  ```json
651
732
  {{
652
733
  "name": "{project_name}",
653
- "title": "My Awesome App",
654
- "version": "1.0.0",
655
- "main": "src/index.html",
656
- "preload": "src/preload.js",
734
+ "title": "My App",
657
735
  "width": 1200,
658
736
  "height": 800,
659
- "resizable": true,
660
737
  "frame": true,
661
- "icon": "assets/icon.png"
738
+ "resizable": true
662
739
  }}
663
740
  ```
664
741
 
665
- | Option | Description |
666
- |--------|-------------|
667
- | `frame` | Set to `false` for frameless window (custom titlebar) |
668
- | `resizable` | Allow window resizing |
669
- | `width/height` | Initial window size |
670
- | `devTools` | Enable right-click → Inspect Element |
671
-
672
- ## 📦 Building AppImage
742
+ ## 📦 Build AppImage
673
743
 
674
744
  ```bash
675
745
  aegis build
676
746
  ```
677
747
 
678
- This creates a portable AppImage (~200KB!) in the `dist/` folder.
748
+ Creates a portable ~200KB AppImage in `dist/`.
679
749
 
680
- **Note:** The AppImage requires `python3-gi` and `gir1.2-webkit2-4.1` on the target system.
681
-
682
- ## 🆚 Why Aegis over Electron?
750
+ ## 🆚 Aegis vs Electron
683
751
 
684
752
  | Aspect | Electron | Aegis |
685
753
  |--------|----------|-------|
686
754
  | App Size | ~150 MB | **~200 KB** |
687
- | Backend | Node.js | Python |
688
- | Renderer | Chromium (bundled) | WebKit2GTK (system) |
689
- | RAM Usage | High (~100MB+) | Low (~30MB) |
755
+ | RAM Usage | ~100 MB | ~30 MB |
690
756
  | Platform | Cross-platform | Linux |
691
757
 
692
- ## 📚 Learn More
693
-
694
- - [Aegis GitHub](https://github.com/Diegopam/aegis-framework)
695
- - [npm Package](https://www.npmjs.com/package/aegis-framework)
758
+ ## 📚 More Info
696
759
 
697
- ## License
760
+ - [GitHub](https://github.com/Diegopam/aegis-framework)
761
+ - [npm](https://www.npmjs.com/package/aegis-framework)
698
762
 
699
- MIT
763
+ MIT License
700
764
  '''
701
765
  with open(project_dir / 'README.md', 'w') as f:
702
766
  f.write(readme_content)
@@ -118,7 +118,8 @@ class AegisWindow(Gtk.Window):
118
118
  print(f"[Aegis] Action: {action}, Payload: {payload}")
119
119
 
120
120
  # Check if this is an async action
121
- async_actions = {'run.async', 'download', 'copy.async'}
121
+ # 'run' is now async by default to prevent UI freezing
122
+ async_actions = {'run', 'run.async', 'download', 'copy.async'}
122
123
 
123
124
  if action in async_actions:
124
125
  # Handle in background thread - response sent via callback
@@ -241,35 +242,45 @@ class AegisWindow(Gtk.Window):
241
242
 
242
243
  return {'success': True, 'path': full_path}
243
244
 
244
- def _handle_run(self, payload):
245
- """Execute Python or shell commands"""
245
+ def _handle_run(self, payload, callback_id=None):
246
+ """Execute Python or shell commands (Thread-Safe)"""
246
247
  import subprocess
247
248
 
249
+ result = {'error': 'No command specified'}
250
+
248
251
  if 'py' in payload:
249
252
  # Execute Python code
250
253
  try:
251
- result = eval(payload['py'])
252
- return {'output': str(result), 'exitCode': 0}
254
+ res = eval(payload['py'])
255
+ result = {'output': str(res), 'exitCode': 0}
253
256
  except:
254
257
  exec_globals = {}
255
258
  exec(payload['py'], exec_globals)
256
- return {'output': '', 'exitCode': 0}
259
+ result = {'output': '', 'exitCode': 0}
257
260
 
258
261
  elif 'sh' in payload:
259
262
  # Execute shell command
260
- result = subprocess.run(
261
- payload['sh'],
262
- shell=True,
263
- capture_output=True,
264
- text=True
265
- )
266
- return {
267
- 'output': result.stdout,
268
- 'error': result.stderr,
269
- 'exitCode': result.returncode
270
- }
263
+ try:
264
+ # Ensure we wait here in the thread, not blocking UI
265
+ proc_res = subprocess.run(
266
+ payload['sh'],
267
+ shell=True,
268
+ capture_output=True,
269
+ text=True
270
+ )
271
+ result = {
272
+ 'output': proc_res.stdout,
273
+ 'error': proc_res.stderr,
274
+ 'exitCode': proc_res.returncode
275
+ }
276
+ except Exception as e:
277
+ result = {'error': str(e), 'exitCode': -1}
271
278
 
272
- return {'error': 'No command specified'}
279
+ # If running async (callback_id present), send response to main thread
280
+ if callback_id:
281
+ GLib.idle_add(self._send_response, callback_id, result)
282
+ else:
283
+ return result
273
284
 
274
285
  def _handle_exists(self, payload):
275
286
  """Check if path exists"""
@@ -610,6 +621,7 @@ class AegisWindow(Gtk.Window):
610
621
  def _process_async_action(self, action, payload, callback_id):
611
622
  """Process async actions in background threads"""
612
623
  handlers = {
624
+ 'run': self._handle_run, # Now supported in background thread
613
625
  'run.async': self._handle_run_async,
614
626
  'download': self._handle_download,
615
627
  'copy.async': self._handle_copy_async,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aegis-framework",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Lightweight AppImage framework using WebKit2GTK and Python - An alternative to Electron that creates ~200KB apps instead of 150MB!",
5
5
  "keywords": [
6
6
  "appimage",
@@ -48,4 +48,4 @@
48
48
  "engines": {
49
49
  "node": ">=14"
50
50
  }
51
- }
51
+ }