pinokiod 3.86.0 → 3.87.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.
Files changed (67) hide show
  1. package/Dockerfile +61 -0
  2. package/docker-entrypoint.sh +75 -0
  3. package/kernel/api/hf/index.js +1 -1
  4. package/kernel/api/index.js +1 -1
  5. package/kernel/api/shell/index.js +6 -0
  6. package/kernel/api/terminal/index.js +166 -0
  7. package/kernel/bin/conda.js +3 -2
  8. package/kernel/bin/index.js +53 -2
  9. package/kernel/bin/setup.js +32 -0
  10. package/kernel/bin/vs.js +11 -2
  11. package/kernel/index.js +42 -2
  12. package/kernel/info.js +36 -0
  13. package/kernel/peer.js +42 -15
  14. package/kernel/router/index.js +23 -15
  15. package/kernel/router/localhost_static_router.js +0 -3
  16. package/kernel/router/pinokio_domain_router.js +333 -0
  17. package/kernel/shells.js +21 -1
  18. package/kernel/util.js +2 -2
  19. package/package.json +2 -1
  20. package/script/install-mode.js +33 -0
  21. package/script/pinokio.json +7 -0
  22. package/server/index.js +513 -173
  23. package/server/public/Socket.js +48 -0
  24. package/server/public/common.js +1441 -276
  25. package/server/public/fseditor.js +71 -12
  26. package/server/public/install.js +1 -1
  27. package/server/public/layout.js +740 -0
  28. package/server/public/modalinput.js +0 -1
  29. package/server/public/style.css +97 -105
  30. package/server/public/tab-idle-notifier.js +629 -0
  31. package/server/public/terminal_input_tracker.js +63 -0
  32. package/server/public/urldropdown.css +319 -53
  33. package/server/public/urldropdown.js +615 -159
  34. package/server/public/window_storage.js +97 -28
  35. package/server/socket.js +40 -9
  36. package/server/views/500.ejs +2 -2
  37. package/server/views/app.ejs +3136 -1367
  38. package/server/views/bookmarklet.ejs +1 -1
  39. package/server/views/bootstrap.ejs +1 -1
  40. package/server/views/columns.ejs +2 -13
  41. package/server/views/connect.ejs +3 -4
  42. package/server/views/container.ejs +1 -2
  43. package/server/views/d.ejs +223 -53
  44. package/server/views/editor.ejs +1 -1
  45. package/server/views/file_explorer.ejs +1 -1
  46. package/server/views/index.ejs +12 -11
  47. package/server/views/index2.ejs +4 -4
  48. package/server/views/init/index.ejs +4 -5
  49. package/server/views/install.ejs +1 -1
  50. package/server/views/layout.ejs +105 -0
  51. package/server/views/net.ejs +39 -7
  52. package/server/views/network.ejs +20 -6
  53. package/server/views/network2.ejs +1 -1
  54. package/server/views/old_network.ejs +2 -2
  55. package/server/views/partials/dynamic.ejs +3 -5
  56. package/server/views/partials/menu.ejs +3 -5
  57. package/server/views/partials/running.ejs +1 -1
  58. package/server/views/pro.ejs +1 -1
  59. package/server/views/prototype/index.ejs +1 -1
  60. package/server/views/review.ejs +11 -23
  61. package/server/views/rows.ejs +2 -13
  62. package/server/views/screenshots.ejs +293 -138
  63. package/server/views/settings.ejs +3 -4
  64. package/server/views/setup.ejs +1 -2
  65. package/server/views/shell.ejs +277 -26
  66. package/server/views/terminal.ejs +322 -49
  67. package/server/views/tools.ejs +448 -4
@@ -387,6 +387,7 @@ body.dark .gallery-stats {
387
387
  gap: 4px;
388
388
  opacity: 0;
389
389
  transition: opacity 0.2s;
390
+ z-index: 3;
390
391
  }
391
392
 
392
393
  .gallery-item:hover .gallery-item-actions {
@@ -451,18 +452,74 @@ body.dark .gallery-item {
451
452
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
452
453
  }
453
454
 
454
- .gallery-item img {
455
+ .gallery-item-media {
456
+ position: relative;
455
457
  width: 100%;
456
458
  height: 200px;
459
+ background: #050505;
460
+ overflow: hidden;
461
+ }
462
+
463
+ .gallery-item img,
464
+ .gallery-item video {
465
+ width: 100%;
466
+ height: 100%;
457
467
  object-fit: cover;
458
468
  display: block;
459
469
  transition: opacity 0.2s;
460
470
  }
461
471
 
462
- .gallery-item img.loading {
472
+ .gallery-item img.loading,
473
+ .gallery-item video.loading {
463
474
  opacity: 0.5;
464
475
  }
465
476
 
477
+ .gallery-item video {
478
+ background: #000;
479
+ }
480
+
481
+ .gallery-item-badge {
482
+ position: absolute;
483
+ top: 8px;
484
+ left: 8px;
485
+ background: rgba(0,0,0,0.7);
486
+ color: #fff;
487
+ padding: 4px 8px;
488
+ border-radius: 999px;
489
+ font-size: 11px;
490
+ display: flex;
491
+ align-items: center;
492
+ gap: 4px;
493
+ pointer-events: none;
494
+ z-index: 2;
495
+ }
496
+
497
+ body.dark .gallery-item-badge {
498
+ background: rgba(255,255,255,0.2);
499
+ color: #fff;
500
+ }
501
+
502
+ .gallery-item-media-fallback {
503
+ position: absolute;
504
+ inset: 0;
505
+ display: flex;
506
+ flex-direction: column;
507
+ align-items: center;
508
+ justify-content: center;
509
+ gap: 8px;
510
+ color: rgba(255,255,255,0.85);
511
+ background: rgba(0,0,0,0.6);
512
+ font-size: 13px;
513
+ text-align: center;
514
+ padding: 0 12px;
515
+ pointer-events: none;
516
+ }
517
+
518
+ .gallery-item-media-fallback i {
519
+ font-size: 24px;
520
+ opacity: 0.85;
521
+ }
522
+
466
523
  .gallery-item-info {
467
524
  padding: 10px;
468
525
  text-align: center;
@@ -653,7 +710,6 @@ document.addEventListener('DOMContentLoaded', function() {
653
710
  <header class='navheader grabbable'>
654
711
  <h1>
655
712
  <a class='home' href="/"><img class='icon' src="/pinokio-black.png"></a>
656
- <button id='collapse' class='btn2' data-tippy-content="toggle fullscreen view"><i class="fa-solid fa-bars"></i></button>
657
713
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
658
714
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
659
715
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -661,7 +717,7 @@ document.addEventListener('DOMContentLoaded', function() {
661
717
  <button class='btn2 mobile-link-button' id='mobile-link-button' data-tippy-content="enter url"><i class="fa-solid fa-link"></i></button>
662
718
  <form class='urlbar'>
663
719
  <div class='url-input-container'>
664
- <input type='url' placeholder='enter a local url'>
720
+ <input type='url' placeholder='enter any local url to open in pinokio'>
665
721
  <div class='url-dropdown' id='url-dropdown'></div>
666
722
  </div>
667
723
  </form>
@@ -691,15 +747,15 @@ document.addEventListener('DOMContentLoaded', function() {
691
747
  </header>
692
748
  <main>
693
749
  <div class='container'>
694
- <div id="gallery-loading" class="loading">Loading screenshots...</div>
750
+ <div id="gallery-loading" class="loading">Loading captures...</div>
695
751
  <div id="gallery-error" class="gallery-error" style="display: none;">
696
- <p>Failed to load screenshots. <button id="retry-btn" class="btn">Retry</button></p>
752
+ <p>Failed to load captures. <button id="retry-btn" class="btn">Retry</button></p>
697
753
  </div>
698
754
  <div id="gallery-container" class="gallery-container" style="display: none;">
699
755
  <div class="gallery-header">
700
- <h2>Screenshots</h2>
756
+ <h2>Screen Captures</h2>
701
757
  <div class="gallery-stats">
702
- <span id="image-count">0 images</span>
758
+ <span id="image-count">0 captures</span>
703
759
  </div>
704
760
  </div>
705
761
  <div id="gallery-grid" class="gallery-grid">
@@ -710,7 +766,7 @@ document.addEventListener('DOMContentLoaded', function() {
710
766
  <aside>
711
767
  <div class='btn-tab'>
712
768
  <button type='button' class='btn' id='create-launcher-button'><i class="fa-solid fa-plus"></i><div class='caption'>Create</div></button>
713
- <a class='btn' id='explore' href="/?mode=explore"><i class="fa-solid fa-globe"></i><div class='caption'>Discover</div></a>
769
+ <a class='btn' id='explore' href="/home?mode=explore"><i class="fa-solid fa-globe"></i><div class='caption'>Discover</div></a>
714
770
  </div>
715
771
  <a href="/" class='tab'><i class='fas fa-laptop-code'></i><div class='caption'>This machine</div></a>
716
772
  <a href="/network" class='tab'><i class="fa-solid fa-wifi"></i><div class='caption'>Local network</div></a>
@@ -726,7 +782,7 @@ document.addEventListener('DOMContentLoaded', function() {
726
782
  <a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><i class="fa-solid fa-download"></i><div class='caption'>Download logs</div></a>
727
783
  <a class='tab selected' href="/screenshots"><i class="fa-solid fa-camera"></i><div class='caption'>Screenshots</div></a>
728
784
  <a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
729
- <a class='tab' href="/?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
785
+ <a class='tab' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
730
786
  </aside>
731
787
  </main>
732
788
  <script>
@@ -739,6 +795,7 @@ class ScreenshotGallery {
739
795
  this.gridEl = document.getElementById('gallery-grid')
740
796
  this.imageCountEl = document.getElementById('image-count')
741
797
  this.retryBtn = document.getElementById('retry-btn')
798
+ this.currentItems = []
742
799
 
743
800
  this.init()
744
801
  }
@@ -768,7 +825,7 @@ class ScreenshotGallery {
768
825
  this.displayScreenshots(data.files || [])
769
826
 
770
827
  } catch (error) {
771
- console.error('Failed to load screenshots:', error)
828
+ console.error('Failed to load captures:', error)
772
829
  this.showError(error.message)
773
830
  }
774
831
  }
@@ -783,9 +840,11 @@ class ScreenshotGallery {
783
840
  this.loadingEl.style.display = 'none'
784
841
  this.errorEl.style.display = 'block'
785
842
  this.containerEl.style.display = 'none'
843
+ this.currentItems = []
844
+ this.updateStats()
786
845
 
787
846
  const errorMsg = this.errorEl.querySelector('p')
788
- errorMsg.innerHTML = `Failed to load screenshots: ${message}. <button id="retry-btn" class="btn">Retry</button>`
847
+ errorMsg.innerHTML = `Failed to load captures: ${message}. <button id="retry-btn" class="btn">Retry</button>`
789
848
 
790
849
  // Re-attach event listener to new retry button
791
850
  const newRetryBtn = document.getElementById('retry-btn')
@@ -796,42 +855,81 @@ class ScreenshotGallery {
796
855
  this.loadingEl.style.display = 'none'
797
856
  this.errorEl.style.display = 'none'
798
857
  this.containerEl.style.display = 'block'
799
-
800
- // Update image count
801
- const validImages = files.filter(file => this.isImageFile(file))
802
- this.imageCountEl.textContent = `${validImages.length} image${validImages.length !== 1 ? 's' : ''}`
803
-
804
- // Clear previous content
858
+
859
+ const items = (files || []).map(file => {
860
+ const type = this.getMediaType(file)
861
+ return {
862
+ file,
863
+ type,
864
+ timestamp: this.extractTimestamp(file)
865
+ }
866
+ }).filter(item => item.type !== 'unknown')
867
+
868
+ this.currentItems = items
869
+ this.updateStats()
870
+
805
871
  this.gridEl.innerHTML = ''
806
-
807
- if (validImages.length === 0) {
808
- this.gridEl.innerHTML = `
809
- <div style="grid-column: 1 / -1; text-align: center; padding: 40px; color: rgba(0,0,0,0.5);">
810
- <i class="fa-solid fa-camera" style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;"></i>
811
- <p>No screenshots found</p>
812
- <p style="font-size: 14px;">Take your first screenshot using the camera button in the header</p>
813
- </div>
814
- `
872
+
873
+ if (items.length === 0) {
874
+ this.showEmptyState()
815
875
  return
816
876
  }
817
-
818
- // Sort files by timestamp (newest first)
819
- const sortedFiles = validImages.sort((a, b) => {
820
- const timestampA = this.extractTimestamp(a)
821
- const timestampB = this.extractTimestamp(b)
822
- return timestampB - timestampA
823
- })
824
-
825
- // Create gallery items
826
- sortedFiles.forEach(file => {
827
- const item = this.createGalleryItem(file)
828
- this.gridEl.appendChild(item)
877
+
878
+ const sortedItems = items.sort((a, b) => b.timestamp - a.timestamp)
879
+ sortedItems.forEach(item => {
880
+ const node = this.createGalleryItem(item)
881
+ this.gridEl.appendChild(node)
829
882
  })
830
883
  }
884
+
885
+ showEmptyState() {
886
+ this.imageCountEl.textContent = '0 captures'
887
+ this.gridEl.innerHTML = `
888
+ <div style="grid-column: 1 / -1; text-align: center; padding: 40px; color: rgba(0,0,0,0.5);">
889
+ <i class="fa-solid fa-camera" style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;"></i>
890
+ <p>No captures found</p>
891
+ <p style="font-size: 14px;">Use the camera button above to save a screenshot or recording</p>
892
+ </div>
893
+ `
894
+ }
895
+
896
+ updateStats() {
897
+ if (!this.imageCountEl) return
898
+ const images = this.currentItems.filter(item => item.type === 'image').length
899
+ const videos = this.currentItems.filter(item => item.type === 'video').length
900
+ const total = this.currentItems.length
901
+
902
+ if (!total) {
903
+ this.imageCountEl.textContent = '0 captures'
904
+ return
905
+ }
906
+
907
+ const parts = []
908
+ if (images) parts.push(`${images} image${images !== 1 ? 's' : ''}`)
909
+ if (videos) parts.push(`${videos} video${videos !== 1 ? 's' : ''}`)
910
+ if (!parts.length) {
911
+ parts.push(`${total} capture${total !== 1 ? 's' : ''}`)
912
+ }
913
+ this.imageCountEl.textContent = parts.join(' · ')
914
+ }
831
915
 
832
916
  isImageFile(filename) {
833
917
  const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']
834
- return imageExtensions.some(ext => filename.toLowerCase().endsWith(ext))
918
+ const lower = filename.toLowerCase()
919
+ return imageExtensions.some(ext => lower.endsWith(ext))
920
+ }
921
+
922
+ isVideoFile(filename) {
923
+ const videoExtensions = ['.webm', '.mp4', '.mkv', '.mov', '.avi', '.m4v']
924
+ const lower = filename.toLowerCase()
925
+ return videoExtensions.some(ext => lower.endsWith(ext))
926
+ }
927
+
928
+ getMediaType(filename) {
929
+ if (!filename) return 'unknown'
930
+ if (this.isImageFile(filename)) return 'image'
931
+ if (this.isVideoFile(filename)) return 'video'
932
+ return 'unknown'
835
933
  }
836
934
 
837
935
  extractTimestamp(filename) {
@@ -847,109 +945,183 @@ class ScreenshotGallery {
847
945
  return filepath.split('/').pop()
848
946
  }
849
947
 
850
- createGalleryItem(filepath) {
948
+ createGalleryItem(item) {
949
+ const { file: filepath, type, timestamp } = item
851
950
  const container = document.createElement('div')
852
951
  container.className = 'gallery-item'
853
-
854
- const timestamp = this.extractTimestamp(filepath)
952
+ container.dataset.type = type
953
+
855
954
  const filename = this.extractFilename(filepath)
856
955
  const timeAgo = timestamp ? timeago.format(new Date(timestamp)) : 'Unknown time'
857
-
858
- container.innerHTML = `
859
- <img src="${filepath}" alt="${filename}" class="loading" loading="lazy">
860
- <div class="gallery-item-actions">
861
- <button class="gallery-item-action-btn gallery-item-delete-btn" title="Delete screenshot">
862
- <i class="fa-solid fa-trash"></i>
863
- </button>
864
- </div>
865
- <div class="gallery-item-info">
866
- <div class="gallery-item-filename">${filename}</div>
867
- <div class="gallery-item-timestamp">${timeAgo}</div>
868
- </div>
869
- `
870
-
871
- const img = container.querySelector('img')
872
-
873
- // Handle image loading
874
- img.addEventListener('load', () => {
875
- img.classList.remove('loading')
876
- })
877
-
878
- img.addEventListener('error', () => {
879
- img.classList.remove('loading')
880
- img.alt = 'Failed to load image'
881
- img.style.background = 'rgba(0,0,0,0.1)'
882
- img.style.display = 'flex'
883
- img.style.alignItems = 'center'
884
- img.style.justifyContent = 'center'
885
- img.style.color = 'rgba(0,0,0,0.5)'
886
- img.innerHTML = '<i class="fa-solid fa-image-slash"></i>'
887
- })
888
-
889
- // Handle delete button click
890
- const deleteBtn = container.querySelector('.gallery-item-delete-btn')
956
+
957
+ const mediaWrapper = document.createElement('div')
958
+ mediaWrapper.className = 'gallery-item-media'
959
+
960
+ let mediaElement
961
+ if (type === 'image') {
962
+ mediaElement = document.createElement('img')
963
+ mediaElement.src = filepath
964
+ mediaElement.alt = filename
965
+ mediaElement.loading = 'lazy'
966
+ mediaElement.classList.add('loading')
967
+ mediaElement.addEventListener('load', () => {
968
+ mediaElement.classList.remove('loading')
969
+ })
970
+ mediaElement.addEventListener('error', () => {
971
+ mediaElement.classList.remove('loading')
972
+ mediaWrapper.innerHTML = `
973
+ <div class="gallery-item-media-fallback">
974
+ <i class="fa-solid fa-image-slash"></i>
975
+ <span>Preview unavailable</span>
976
+ </div>
977
+ `
978
+ })
979
+ } else {
980
+ mediaElement = document.createElement('video')
981
+ mediaElement.src = filepath
982
+ mediaElement.preload = 'metadata'
983
+ mediaElement.muted = true
984
+ mediaElement.loop = true
985
+ mediaElement.playsInline = true
986
+ mediaElement.classList.add('loading')
987
+ mediaElement.addEventListener('loadeddata', () => {
988
+ mediaElement.classList.remove('loading')
989
+ })
990
+ mediaElement.addEventListener('mouseenter', () => {
991
+ mediaElement.play().catch(() => {})
992
+ })
993
+ mediaElement.addEventListener('mouseleave', () => {
994
+ mediaElement.pause()
995
+ mediaElement.currentTime = 0
996
+ })
997
+ mediaElement.addEventListener('error', () => {
998
+ mediaElement.classList.remove('loading')
999
+ mediaWrapper.innerHTML = `
1000
+ <div class="gallery-item-media-fallback">
1001
+ <i class="fa-solid fa-video-slash"></i>
1002
+ <span>Preview unavailable</span>
1003
+ </div>
1004
+ `
1005
+ })
1006
+ }
1007
+
1008
+ mediaWrapper.appendChild(mediaElement)
1009
+ container.appendChild(mediaWrapper)
1010
+
1011
+ if (type === 'video') {
1012
+ const badge = document.createElement('div')
1013
+ badge.className = 'gallery-item-badge'
1014
+ badge.innerHTML = '<i class="fa-solid fa-video"></i><span>Video</span>'
1015
+ container.appendChild(badge)
1016
+ }
1017
+
1018
+ const actions = document.createElement('div')
1019
+ actions.className = 'gallery-item-actions'
1020
+ const deleteBtn = document.createElement('button')
1021
+ deleteBtn.className = 'gallery-item-action-btn gallery-item-delete-btn'
1022
+ deleteBtn.title = 'Delete capture'
1023
+ deleteBtn.innerHTML = '<i class="fa-solid fa-trash"></i>'
1024
+ actions.appendChild(deleteBtn)
1025
+ container.appendChild(actions)
1026
+
1027
+ const info = document.createElement('div')
1028
+ info.className = 'gallery-item-info'
1029
+ const filenameEl = document.createElement('div')
1030
+ filenameEl.className = 'gallery-item-filename'
1031
+ filenameEl.textContent = filename
1032
+ const timestampEl = document.createElement('div')
1033
+ timestampEl.className = 'gallery-item-timestamp'
1034
+ timestampEl.textContent = timeAgo
1035
+ info.append(filenameEl, timestampEl)
1036
+ container.appendChild(info)
1037
+
891
1038
  deleteBtn.addEventListener('click', (e) => {
892
- e.stopPropagation() // Prevent opening modal
893
- this.confirmDelete(filepath, filename, container)
1039
+ e.stopPropagation()
1040
+ this.confirmDelete(filepath, filename, container, type)
894
1041
  })
895
-
896
- // Handle click to open in modal
1042
+
897
1043
  container.addEventListener('click', (e) => {
898
- // Don't open modal if clicking on action buttons
899
1044
  if (e.target.closest('.gallery-item-actions')) {
900
1045
  return
901
1046
  }
902
- this.openModal(filepath, filename)
1047
+ this.openModal(filepath, filename, type)
903
1048
  })
904
-
1049
+
905
1050
  return container
906
1051
  }
907
1052
 
908
- openModal(imageSrc, filename) {
1053
+ openModal(src, filename, type = 'image') {
1054
+ this.closeModal()
1055
+
909
1056
  const modal = document.createElement('div')
910
1057
  modal.className = 'image-modal'
911
- modal.innerHTML = `
912
- <button class="image-modal-close" title="Close (Esc)">×</button>
913
- <img src="${imageSrc}" alt="${filename}">
914
- `
915
-
1058
+
1059
+ const closeBtn = document.createElement('button')
1060
+ closeBtn.className = 'image-modal-close'
1061
+ closeBtn.title = 'Close (Esc)'
1062
+ closeBtn.textContent = '×'
1063
+
1064
+ let media
1065
+ if (type === 'video') {
1066
+ media = document.createElement('video')
1067
+ media.src = src
1068
+ media.controls = true
1069
+ media.autoplay = true
1070
+ media.playsInline = true
1071
+ media.style.maxWidth = '90vw'
1072
+ media.style.maxHeight = '80vh'
1073
+ } else {
1074
+ media = document.createElement('img')
1075
+ media.src = src
1076
+ media.alt = filename
1077
+ }
1078
+
1079
+ modal.append(closeBtn, media)
916
1080
  document.body.appendChild(modal)
917
-
918
- // Close modal handlers
919
- const closeBtn = modal.querySelector('.image-modal-close')
1081
+
920
1082
  closeBtn.addEventListener('click', (e) => {
921
1083
  e.stopPropagation()
922
1084
  this.closeModal()
923
1085
  })
924
-
1086
+
925
1087
  modal.addEventListener('click', () => {
926
1088
  this.closeModal()
927
1089
  })
928
-
929
- // Prevent closing when clicking on image
930
- const img = modal.querySelector('img')
931
- img.addEventListener('click', (e) => {
1090
+
1091
+ media.addEventListener('click', (e) => {
932
1092
  e.stopPropagation()
933
1093
  })
934
-
935
- // Store reference for closing
1094
+
936
1095
  this.currentModal = modal
937
1096
  }
938
1097
 
939
1098
  closeModal() {
940
1099
  if (this.currentModal) {
1100
+ const video = this.currentModal.querySelector('video')
1101
+ if (video) {
1102
+ video.pause()
1103
+ video.removeAttribute('src')
1104
+ video.load()
1105
+ }
941
1106
  document.body.removeChild(this.currentModal)
942
1107
  this.currentModal = null
943
1108
  }
944
1109
  }
945
1110
 
946
- confirmDelete(filepath, filename, containerElement) {
1111
+ confirmDelete(filepath, filename, containerElement, type = 'image') {
1112
+ const isVideo = type === 'video'
1113
+ const preview = isVideo
1114
+ ? `<video src="${filepath}" controls muted style="max-width: 200px; max-height: 150px; border-radius: 4px; margin-bottom: 10px; background: #000;"></video>`
1115
+ : `<img src="${filepath}" style="max-width: 200px; max-height: 150px; border-radius: 4px; margin-bottom: 10px;" alt="Preview">`
1116
+ const label = isVideo ? 'Video capture' : 'Screenshot'
1117
+
947
1118
  Swal.fire({
948
- title: 'Delete Screenshot?',
1119
+ title: 'Delete Capture?',
949
1120
  html: `
950
1121
  <div style="text-align: center; margin: 20px 0;">
951
- <img src="${filepath}" style="max-width: 200px; max-height: 150px; border-radius: 4px; margin-bottom: 10px;" alt="Preview">
1122
+ ${preview}
952
1123
  <p style="margin: 10px 0; font-weight: bold;">${filename}</p>
1124
+ <p style="margin: 0; color: #888; font-size: 13px;">${label}</p>
953
1125
  <p style="margin: 0; color: #666; font-size: 14px;">This action cannot be undone.</p>
954
1126
  </div>
955
1127
  `,
@@ -961,16 +1133,15 @@ class ScreenshotGallery {
961
1133
  cancelButtonText: 'Cancel'
962
1134
  }).then((result) => {
963
1135
  if (result.isConfirmed) {
964
- this.deleteScreenshot(filepath, filename, containerElement)
1136
+ this.deleteScreenshot(filepath, filename, containerElement, type)
965
1137
  }
966
1138
  })
967
1139
  }
968
1140
 
969
- async deleteScreenshot(filepath, filename, containerElement) {
1141
+ async deleteScreenshot(filepath, filename, containerElement, type = 'image') {
970
1142
  try {
971
- // Add deleting state
972
1143
  containerElement.classList.add('deleting')
973
-
1144
+
974
1145
  const response = await fetch('/snapshots', {
975
1146
  method: 'POST',
976
1147
  headers: {
@@ -978,55 +1149,39 @@ class ScreenshotGallery {
978
1149
  },
979
1150
  body: JSON.stringify({ filename: filepath })
980
1151
  })
981
-
1152
+
982
1153
  const result = await response.json()
983
-
1154
+
984
1155
  if (!response.ok) {
985
1156
  throw new Error(result.error || `HTTP ${response.status}`)
986
1157
  }
987
-
988
- // Remove from DOM with animation
1158
+
989
1159
  containerElement.style.transition = 'all 0.3s ease'
990
- containerElement.style.transform = 'scale(0.8)'
1160
+ containerElement.style.transform = 'scale(0.94)'
991
1161
  containerElement.style.opacity = '0'
992
-
1162
+
993
1163
  setTimeout(() => {
994
1164
  if (containerElement.parentNode) {
995
1165
  containerElement.parentNode.removeChild(containerElement)
996
1166
  }
997
-
998
- // Update image count
999
- const remainingImages = this.gridEl.children.length
1000
- this.imageCountEl.textContent = `${remainingImages} image${remainingImages !== 1 ? 's' : ''}`
1001
-
1002
- // Show empty state if no images left
1003
- if (remainingImages === 0) {
1004
- this.gridEl.innerHTML = `
1005
- <div style="grid-column: 1 / -1; text-align: center; padding: 40px; color: rgba(0,0,0,0.5);">
1006
- <i class="fa-solid fa-camera" style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;"></i>
1007
- <p>No screenshots found</p>
1008
- <p style="font-size: 14px;">Take your first screenshot using the camera button in the header</p>
1009
- </div>
1010
- `
1167
+ this.currentItems = this.currentItems.filter(item => item.file !== filepath)
1168
+ this.updateStats()
1169
+ if (this.currentItems.length === 0) {
1170
+ this.showEmptyState()
1011
1171
  }
1012
- }, 300)
1013
-
1014
- // Show success message
1172
+ }, 260)
1173
+
1015
1174
  Swal.fire({
1016
1175
  title: 'Deleted!',
1017
- text: 'Screenshot has been deleted.',
1176
+ text: 'Capture has been deleted.',
1018
1177
  icon: 'success',
1019
- timer: 2000,
1178
+ timer: 1800,
1020
1179
  showConfirmButton: false
1021
1180
  })
1022
-
1181
+
1023
1182
  } catch (error) {
1024
- console.error('Failed to delete screenshot:', error)
1025
-
1026
- // Remove deleting state
1183
+ console.error('Failed to delete capture:', error)
1027
1184
  containerElement.classList.remove('deleting')
1028
-
1029
- // Show error message
1030
1185
  Swal.fire({
1031
1186
  title: 'Delete Failed',
1032
1187
  text: error.message,
@@ -375,7 +375,6 @@ document.addEventListener('DOMContentLoaded', function() {
375
375
  <header class='navheader grabbable'>
376
376
  <h1>
377
377
  <a class='home' href="/"><img class='icon' src="/pinokio-black.png"></a>
378
- <button id='collapse' class='btn2' data-tippy-content="toggle fullscreen view"><i class="fa-solid fa-bars"></i></button>
379
378
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
380
379
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
381
380
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -390,7 +389,7 @@ document.addEventListener('DOMContentLoaded', function() {
390
389
  <button class='btn2 mobile-link-button' id='mobile-link-button' data-tippy-content="enter url"><i class="fa-solid fa-link"></i></button>
391
390
  <form class='urlbar'>
392
391
  <div class='url-input-container'>
393
- <input type='url' placeholder='enter a local url'>
392
+ <input type='url' placeholder='enter any local url to open in pinokio'>
394
393
  <div class='url-dropdown' id='url-dropdown'></div>
395
394
  </div>
396
395
  </form>
@@ -583,7 +582,7 @@ document.addEventListener('DOMContentLoaded', function() {
583
582
  <aside>
584
583
  <div class='btn-tab'>
585
584
  <button type='button' class='btn' id='create-launcher-button'><i class="fa-solid fa-plus"></i><div class='caption'>Create</div></button>
586
- <a class='btn' id='explore' href="/?mode=explore"><i class="fa-solid fa-globe"></i><div class='caption'>Discover</div></a>
585
+ <a class='btn' id='explore' href="/home?mode=explore"><i class="fa-solid fa-globe"></i><div class='caption'>Discover</div></a>
587
586
  </div>
588
587
  <a href="/" class='tab'><i class='fas fa-laptop-code'></i><div class='caption'>This machine</div></a>
589
588
  <a href="/network" class='tab'><i class="fa-solid fa-wifi"></i><div class='caption'>Local network</div></a>
@@ -599,7 +598,7 @@ document.addEventListener('DOMContentLoaded', function() {
599
598
  <a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><i class="fa-solid fa-download"></i><div class='caption'>Download logs</div></a>
600
599
  <a class='tab' href="/screenshots"><i class="fa-solid fa-camera"></i><div class='caption'>Screenshots</div></a>
601
600
  <a class='tab' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
602
- <a class='tab selected' href="/?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
601
+ <a class='tab selected' href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
603
602
  </aside>
604
603
  </main>
605
604
  <script>
@@ -131,7 +131,6 @@ body {
131
131
  <script src="/install.js"></script>
132
132
  <script src="/timeago.min.js"></script>
133
133
  <script src="/common.js"></script>
134
- <script src="/report.js"></script>
135
134
  </head>
136
135
  <body class='<%=theme%>' data-agent="<%=agent%>">
137
136
  <div id='dragger'></div>
@@ -245,7 +244,7 @@ body {
245
244
  <% } %>
246
245
  <script>
247
246
  document.addEventListener("DOMContentLoaded", async () => {
248
- Reporter()
247
+ // Reporter()
249
248
  <% if (error) { %>
250
249
  document.querySelector(".requirements .content").innerHTML = '<div class="loading"><i class="fa-solid fa-circle-exclamation"></i> <%=error%></div>'
251
250
  <% } %>