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.
- package/Dockerfile +61 -0
- package/docker-entrypoint.sh +75 -0
- package/kernel/api/hf/index.js +1 -1
- package/kernel/api/index.js +1 -1
- package/kernel/api/shell/index.js +6 -0
- package/kernel/api/terminal/index.js +166 -0
- package/kernel/bin/conda.js +3 -2
- package/kernel/bin/index.js +53 -2
- package/kernel/bin/setup.js +32 -0
- package/kernel/bin/vs.js +11 -2
- package/kernel/index.js +42 -2
- package/kernel/info.js +36 -0
- package/kernel/peer.js +42 -15
- package/kernel/router/index.js +23 -15
- package/kernel/router/localhost_static_router.js +0 -3
- package/kernel/router/pinokio_domain_router.js +333 -0
- package/kernel/shells.js +21 -1
- package/kernel/util.js +2 -2
- package/package.json +2 -1
- package/script/install-mode.js +33 -0
- package/script/pinokio.json +7 -0
- package/server/index.js +513 -173
- package/server/public/Socket.js +48 -0
- package/server/public/common.js +1441 -276
- package/server/public/fseditor.js +71 -12
- package/server/public/install.js +1 -1
- package/server/public/layout.js +740 -0
- package/server/public/modalinput.js +0 -1
- package/server/public/style.css +97 -105
- package/server/public/tab-idle-notifier.js +629 -0
- package/server/public/terminal_input_tracker.js +63 -0
- package/server/public/urldropdown.css +319 -53
- package/server/public/urldropdown.js +615 -159
- package/server/public/window_storage.js +97 -28
- package/server/socket.js +40 -9
- package/server/views/500.ejs +2 -2
- package/server/views/app.ejs +3136 -1367
- package/server/views/bookmarklet.ejs +1 -1
- package/server/views/bootstrap.ejs +1 -1
- package/server/views/columns.ejs +2 -13
- package/server/views/connect.ejs +3 -4
- package/server/views/container.ejs +1 -2
- package/server/views/d.ejs +223 -53
- package/server/views/editor.ejs +1 -1
- package/server/views/file_explorer.ejs +1 -1
- package/server/views/index.ejs +12 -11
- package/server/views/index2.ejs +4 -4
- package/server/views/init/index.ejs +4 -5
- package/server/views/install.ejs +1 -1
- package/server/views/layout.ejs +105 -0
- package/server/views/net.ejs +39 -7
- package/server/views/network.ejs +20 -6
- package/server/views/network2.ejs +1 -1
- package/server/views/old_network.ejs +2 -2
- package/server/views/partials/dynamic.ejs +3 -5
- package/server/views/partials/menu.ejs +3 -5
- package/server/views/partials/running.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/prototype/index.ejs +1 -1
- package/server/views/review.ejs +11 -23
- package/server/views/rows.ejs +2 -13
- package/server/views/screenshots.ejs +293 -138
- package/server/views/settings.ejs +3 -4
- package/server/views/setup.ejs +1 -2
- package/server/views/shell.ejs +277 -26
- package/server/views/terminal.ejs +322 -49
- 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
|
|
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
|
|
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
|
|
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
|
|
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>
|
|
756
|
+
<h2>Screen Captures</h2>
|
|
701
757
|
<div class="gallery-stats">
|
|
702
|
-
<span id="image-count">0
|
|
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="
|
|
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="
|
|
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
|
|
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
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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 (
|
|
808
|
-
this.
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
821
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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()
|
|
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(
|
|
1053
|
+
openModal(src, filename, type = 'image') {
|
|
1054
|
+
this.closeModal()
|
|
1055
|
+
|
|
909
1056
|
const modal = document.createElement('div')
|
|
910
1057
|
modal.className = 'image-modal'
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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
|
-
|
|
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
|
|
1119
|
+
title: 'Delete Capture?',
|
|
949
1120
|
html: `
|
|
950
1121
|
<div style="text-align: center; margin: 20px 0;">
|
|
951
|
-
|
|
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.
|
|
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
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
-
},
|
|
1013
|
-
|
|
1014
|
-
// Show success message
|
|
1172
|
+
}, 260)
|
|
1173
|
+
|
|
1015
1174
|
Swal.fire({
|
|
1016
1175
|
title: 'Deleted!',
|
|
1017
|
-
text: '
|
|
1176
|
+
text: 'Capture has been deleted.',
|
|
1018
1177
|
icon: 'success',
|
|
1019
|
-
timer:
|
|
1178
|
+
timer: 1800,
|
|
1020
1179
|
showConfirmButton: false
|
|
1021
1180
|
})
|
|
1022
|
-
|
|
1181
|
+
|
|
1023
1182
|
} catch (error) {
|
|
1024
|
-
console.error('Failed to delete
|
|
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
|
|
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="
|
|
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="
|
|
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>
|
package/server/views/setup.ejs
CHANGED
|
@@ -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
|
<% } %>
|