pinokiod 7.5.27 → 7.5.29

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 (37) hide show
  1. package/kernel/index.js +2 -5
  2. package/kernel/info.js +20 -0
  3. package/kernel/shell.js +2 -54
  4. package/package.json +1 -1
  5. package/server/public/common.js +42 -0
  6. package/server/public/style.css +126 -14
  7. package/server/views/app.ejs +171 -13
  8. package/server/views/app_search_test.ejs +3 -0
  9. package/server/views/connect/x.ejs +3 -0
  10. package/server/views/container.ejs +3 -0
  11. package/server/views/download.ejs +3 -0
  12. package/server/views/explore.ejs +3 -0
  13. package/server/views/form.ejs +3 -0
  14. package/server/views/frame.ejs +3 -0
  15. package/server/views/help.ejs +3 -0
  16. package/server/views/index.ejs +21 -66
  17. package/server/views/index2.ejs +3 -0
  18. package/server/views/init/index.ejs +3 -0
  19. package/server/views/mini.ejs +3 -0
  20. package/server/views/net.ejs +3 -0
  21. package/server/views/network.ejs +3 -0
  22. package/server/views/network2.ejs +3 -0
  23. package/server/views/old_network.ejs +3 -0
  24. package/server/views/partials/app_navheader.ejs +3 -0
  25. package/server/views/partials/home_action_modal.ejs +18 -31
  26. package/server/views/partials/home_run_menu.ejs +1 -1
  27. package/server/views/partials/main_sidebar.ejs +101 -4
  28. package/server/views/prototype/index.ejs +3 -0
  29. package/server/views/review.ejs +3 -0
  30. package/server/views/screenshots.ejs +3 -0
  31. package/server/views/settings.ejs +3 -0
  32. package/server/views/setup_home.ejs +3 -0
  33. package/server/views/terminal.ejs +3 -0
  34. package/server/views/terminals.ejs +3 -0
  35. package/test/info-ready.test.js +88 -0
  36. package/test/launch-settings-ui.test.js +10 -0
  37. package/test/shell-cleaned-log.test.js +66 -0
package/kernel/index.js CHANGED
@@ -697,7 +697,6 @@ class Kernel {
697
697
  async clearLog(group) {
698
698
  let relativePath = path.relative(this.homedir, group)
699
699
  for(let type of ["info", "buf", "cleaned"]) {
700
- //for(let type of ["info", "cleaned"]) {
701
700
  let logPath = path.resolve(this.homedir, "logs", "shell", type, relativePath)
702
701
  let logFolder = path.dirname(logPath)
703
702
  let filename = path.basename(logPath)
@@ -775,8 +774,7 @@ class Kernel {
775
774
  let relativePath = path.relative(this.homedir, group)
776
775
 
777
776
  if (!path.isAbsolute(relativePath)) {
778
- //for(let type of ["info", "buf", "cleaned"]) {
779
- for(let type of ["info", "cleaned"]) {
777
+ for(let type of ["cleaned"]) {
780
778
  let logPath = path.resolve(this.homedir, "logs", "shell", type, relativePath + "." + info.index + ".txt")
781
779
  let logFolder = path.dirname(logPath)
782
780
  await fs.promises.mkdir(logFolder, { recursive: true }).catch((e) => { })
@@ -784,8 +782,7 @@ class Kernel {
784
782
  }
785
783
  }
786
784
  } else {
787
- //for(let type of ["info", "buf", "cleaned"]) {
788
- for(let type of ["info", "cleaned"]) {
785
+ for(let type of ["cleaned"]) {
789
786
  let logPath = path.resolve(this.homedir, "logs", "shell", type, "index.txt")
790
787
  let logFolder = path.dirname(logPath)
791
788
  await fs.promises.mkdir(logFolder, { recursive: true }).catch((e) => { })
package/kernel/info.js CHANGED
@@ -2,6 +2,7 @@
2
2
  info.local(...args)
3
3
  info.running(...args)
4
4
  info.exists(...args)
5
+ info.ready(...args)
5
6
  info.path(...args)
6
7
  */
7
8
  const path = require('path')
@@ -57,6 +58,25 @@ class Info {
57
58
  let exists = fs.existsSync(resolved_path)
58
59
  return exists ? exists: false
59
60
  }
61
+ ready(...args) {
62
+ if (!this.kernel || typeof this.kernel.isScriptReady !== "function") {
63
+ return false
64
+ }
65
+ let cwd = path.dirname(this.caller())
66
+ let resolved_path = path.resolve(cwd, ...args)
67
+ if (this.kernel.isScriptReady(resolved_path)) {
68
+ return true
69
+ }
70
+ if (!this.kernel.script || typeof this.kernel.script.resolve !== "function") {
71
+ return false
72
+ }
73
+ try {
74
+ let script_path = this.kernel.script.resolve(...args)
75
+ return script_path ? this.kernel.isScriptReady(script_path) : false
76
+ } catch (e) {
77
+ return false
78
+ }
79
+ }
60
80
  local(...args) {
61
81
  let cwd = path.dirname(this.caller())
62
82
  let resolved_path = path.resolve(cwd, ...args)
package/kernel/shell.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const { Terminal } = require('@xterm/headless');
2
2
  const { SerializeAddon } = require("xterm-addon-serialize");
3
3
  const sanitize = require("sanitize-filename");
4
- const YAML = require('yaml')
5
4
  const kill = require('kill-sync')
6
5
  const fastq = require('fastq')
7
6
  const normalize = require('normalize-path');
@@ -1749,59 +1748,10 @@ class Shell {
1749
1748
  this.nudgeRestoreTimer = null
1750
1749
  }, 100)
1751
1750
  }
1752
- _log(buf, cleaned) {
1753
-
1754
-
1755
- /*
1756
-
1757
- /logs
1758
- /shell
1759
- /[...group]
1760
- ### info
1761
-
1762
- ### stdout
1763
-
1764
- */
1765
- let info = {
1766
- path: this.path,
1767
- cmd: this.cmd,
1768
- index: this.index,
1769
- group: this.group,
1770
- env: ShellRunTemplate.redactEnvArgs(this.env),
1771
- done: this.done,
1772
- ready: this.ready,
1773
- id: this.id,
1774
- ts: Date.now()
1775
- }
1776
-
1751
+ _log(_buf, cleaned) {
1777
1752
  let time = `${new Date().toLocaleString()} (${Date.now()})`
1778
1753
 
1779
- let infoYAML = YAML.stringify(info)
1780
1754
  let data = {}
1781
- data.info = `######################################################################
1782
- #
1783
- # group: ${this.group}
1784
- # id: ${this.id}
1785
- # index: ${this.index}
1786
- # cmd: ${this.cmd}
1787
- # timestamp: ${time}
1788
-
1789
- ${infoYAML}
1790
-
1791
- `
1792
-
1793
- data.buf = `######################################################################
1794
- #
1795
- # group: ${this.group}
1796
- # id: ${this.id}
1797
- # index: ${this.index}
1798
- # cmd: ${this.cmd}
1799
- # timestamp: ${time}
1800
- #
1801
-
1802
- ${buf}
1803
-
1804
- `
1805
1755
 
1806
1756
  data.cleaned = `######################################################################
1807
1757
  #
@@ -1815,9 +1765,7 @@ ${cleaned}
1815
1765
 
1816
1766
  `
1817
1767
 
1818
- this.kernel.log(data, this.group, info)
1819
-
1820
-
1768
+ this.kernel.log(data, this.group, { index: this.index })
1821
1769
  }
1822
1770
  stream(msg, callback) {
1823
1771
  if (msg === "\u0007") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.27",
3
+ "version": "7.5.29",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -2887,7 +2887,49 @@ function updateAllTooltips() {
2887
2887
  tippyInstances.forEach(updateTippyPlacement);
2888
2888
  }
2889
2889
 
2890
+ function ensureExploreNavButton() {
2891
+ const header = document.querySelector("header.navheader");
2892
+ if (!header) {
2893
+ return;
2894
+ }
2895
+
2896
+ let button = header.querySelector("[data-pinokio-explore-nav]");
2897
+ if (!button) {
2898
+ const headerRow = header.querySelector("h1") || header;
2899
+ const anchor =
2900
+ header.querySelector("#sidebar-toggle") ||
2901
+ header.querySelector(".main-sidebar-header-toggle") ||
2902
+ header.querySelector("#minimize-header");
2903
+
2904
+ if (!anchor || !headerRow.contains(anchor)) {
2905
+ return;
2906
+ }
2907
+
2908
+ button = document.createElement("a");
2909
+ button.innerHTML = '<div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>';
2910
+ anchor.insertAdjacentElement("afterend", button);
2911
+ }
2912
+
2913
+ button.classList.add("btn2", "pinokio-explore-nav-button");
2914
+ button.href = "/home?mode=explore";
2915
+ button.setAttribute("data-pinokio-explore-nav", "true");
2916
+ button.setAttribute("data-tippy-content", "Explore");
2917
+ button.setAttribute("title", "Explore");
2918
+ button.setAttribute("aria-label", "Explore");
2919
+
2920
+ const params = new URLSearchParams(window.location.search || "");
2921
+ if (window.location.pathname === "/home" && params.get("mode") === "explore") {
2922
+ button.classList.add("selected");
2923
+ button.setAttribute("aria-current", "page");
2924
+ } else {
2925
+ button.classList.remove("selected");
2926
+ button.removeAttribute("aria-current");
2927
+ }
2928
+ }
2929
+
2890
2930
  document.addEventListener("DOMContentLoaded", () => {
2931
+ ensureExploreNavButton();
2932
+
2891
2933
  if (typeof initUrlDropdown === 'function' && !window.PinokioUrlDropdown) {
2892
2934
  try {
2893
2935
  initUrlDropdown();
@@ -1297,6 +1297,34 @@ body.main-sidebar-page .navheader .sidebar-toggle {
1297
1297
  body.dark.main-sidebar-page .navheader .sidebar-toggle {
1298
1298
  color: rgba(248, 250, 252, 0.92);
1299
1299
  }
1300
+ header.navheader .pinokio-explore-nav-button {
1301
+ text-decoration: none;
1302
+ }
1303
+ body.main-sidebar-page .navheader .pinokio-explore-nav-button,
1304
+ body.app-page .navheader .pinokio-explore-nav-button {
1305
+ color: #0f172a;
1306
+ position: relative;
1307
+ width: 40px;
1308
+ height: 30px;
1309
+ padding: 0;
1310
+ border-radius: 9px;
1311
+ box-sizing: border-box;
1312
+ transition: background-color 140ms cubic-bezier(0.25, 1, 0.5, 1), box-shadow 140ms cubic-bezier(0.25, 1, 0.5, 1), color 140ms cubic-bezier(0.25, 1, 0.5, 1);
1313
+ }
1314
+ body.dark.main-sidebar-page .navheader .pinokio-explore-nav-button,
1315
+ body.dark.app-page .navheader .pinokio-explore-nav-button {
1316
+ color: rgba(248, 250, 252, 0.92);
1317
+ }
1318
+ body.main-sidebar-page .navheader .pinokio-explore-nav-button.selected,
1319
+ body.app-page .navheader .pinokio-explore-nav-button.selected {
1320
+ background: rgba(15, 23, 42, 0.08);
1321
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.06);
1322
+ }
1323
+ body.dark.main-sidebar-page .navheader .pinokio-explore-nav-button.selected,
1324
+ body.dark.app-page .navheader .pinokio-explore-nav-button.selected {
1325
+ background: rgba(255, 255, 255, 0.1);
1326
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
1327
+ }
1300
1328
  body.main-sidebar-page .navheader .sidebar-toggle[aria-expanded="true"] {
1301
1329
  background: rgba(15, 23, 42, 0.08);
1302
1330
  box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.06);
@@ -1343,20 +1371,10 @@ body.main-sidebar-page .sidebar-toggle-content {
1343
1371
  box-shadow: 0 3.8px 0 currentColor, 0 7.6px 0 currentColor;
1344
1372
  transition: opacity 150ms cubic-bezier(0.25, 1, 0.5, 1), transform 150ms cubic-bezier(0.25, 1, 0.5, 1);
1345
1373
  }
1346
- body.main-sidebar-page .navheader .sidebar-toggle[aria-expanded="false"] .sidebar-toggle-panel {
1347
- background: transparent;
1348
- outline: 1.5px solid currentColor;
1349
- outline-offset: -1px;
1350
- opacity: 0.35;
1351
- transform: translateX(-1px) scaleX(0.8);
1352
- }
1353
- body.main-sidebar-page .navheader .sidebar-toggle[aria-expanded="false"] .sidebar-toggle-content {
1354
- opacity: 0.42;
1355
- transform: translateX(-2px);
1356
- }
1357
1374
  body.main-sidebar-page main {
1358
1375
  display: flex;
1359
1376
  align-items: stretch;
1377
+ position: relative;
1360
1378
  }
1361
1379
  body.main-sidebar-page main > :not(.main-sidebar) {
1362
1380
  flex: 1 1 auto;
@@ -1890,8 +1908,83 @@ body.dark .main-sidebar-peer-dialog-surface {
1890
1908
  padding-left: 0;
1891
1909
  }
1892
1910
 
1911
+ @media (min-width: 769px) and (hover: hover) and (pointer: fine) {
1912
+ body.main-sidebar-page.main-sidebar-collapsed .main-sidebar {
1913
+ position: absolute;
1914
+ top: 0;
1915
+ bottom: 0;
1916
+ left: 0;
1917
+ z-index: 10000020;
1918
+ flex: none;
1919
+ width: 180px;
1920
+ min-width: 180px;
1921
+ max-width: 180px;
1922
+ padding: 8px 5px;
1923
+ border-right-width: 1px;
1924
+ border-right-color: var(--pinokio-sidebar-separator);
1925
+ background: var(--pinokio-sidebar-tabbar-bg);
1926
+ opacity: 0;
1927
+ pointer-events: none;
1928
+ overflow-x: hidden;
1929
+ overflow-y: auto;
1930
+ transform: translateX(-100%);
1931
+ }
1932
+
1933
+ body.main-sidebar-page.main-sidebar-collapsed .main-sidebar-peek-trigger {
1934
+ display: block;
1935
+ position: absolute;
1936
+ top: 0;
1937
+ bottom: 0;
1938
+ left: 0;
1939
+ z-index: 10000021;
1940
+ width: 14px;
1941
+ padding: 0;
1942
+ border: 0;
1943
+ border-radius: 0;
1944
+ background: transparent;
1945
+ cursor: default;
1946
+ }
1947
+
1948
+ body.main-sidebar-page.main-sidebar-collapsed .main-sidebar-peek-trigger::after {
1949
+ content: "";
1950
+ position: absolute;
1951
+ top: 50%;
1952
+ left: 4px;
1953
+ width: 3px;
1954
+ height: 36px;
1955
+ border-radius: 999px;
1956
+ background: var(--pinokio-sidebar-separator-hover);
1957
+ opacity: 0;
1958
+ transform: translateY(-50%);
1959
+ transition: opacity 140ms cubic-bezier(0.25, 1, 0.5, 1);
1960
+ }
1961
+
1962
+ body.main-sidebar-page.main-sidebar-collapsed .main-sidebar-peek-trigger:hover::after,
1963
+ body.main-sidebar-page.main-sidebar-collapsed .main-sidebar-peek-trigger:focus-visible::after,
1964
+ body.main-sidebar-page.main-sidebar-peeking .main-sidebar-peek-trigger::after {
1965
+ opacity: 1;
1966
+ }
1967
+
1968
+ body.main-sidebar-page.main-sidebar-peeking .main-sidebar-peek-trigger {
1969
+ z-index: 10000019;
1970
+ }
1971
+
1972
+ body.main-sidebar-page.main-sidebar-collapsed.main-sidebar-peeking .main-sidebar {
1973
+ box-shadow: 20px 0 42px rgba(15, 23, 42, 0.16);
1974
+ opacity: 1;
1975
+ pointer-events: auto;
1976
+ transform: translateX(0);
1977
+ }
1978
+
1979
+ body.dark.main-sidebar-page.main-sidebar-collapsed.main-sidebar-peeking .main-sidebar {
1980
+ box-shadow: 22px 0 46px rgba(0, 0, 0, 0.38);
1981
+ }
1982
+ }
1983
+
1893
1984
  @media (prefers-reduced-motion: reduce) {
1894
1985
  body.main-sidebar-page .navheader .sidebar-toggle,
1986
+ body.main-sidebar-page .navheader .pinokio-explore-nav-button,
1987
+ body.app-page .navheader .pinokio-explore-nav-button,
1895
1988
  body.main-sidebar-page .sidebar-toggle-panel,
1896
1989
  body.main-sidebar-page .sidebar-toggle-content,
1897
1990
  body.main-sidebar-page.main-sidebar-motion-ready .main-sidebar,
@@ -4307,6 +4400,7 @@ aside .qr {
4307
4400
  padding: 15px;
4308
4401
  }
4309
4402
 
4403
+ .main-sidebar-peek-trigger,
4310
4404
  .main-sidebar-mobile-toggle,
4311
4405
  .main-sidebar-mobile-backdrop {
4312
4406
  display: none;
@@ -4398,12 +4492,30 @@ aside .qr {
4398
4492
  box-shadow: 24px 0 52px rgba(0, 0, 0, 0.38);
4399
4493
  }
4400
4494
 
4401
- body.main-sidebar-page.main-sidebar-mobile-open .main-sidebar {
4495
+ body.main-sidebar-page.main-sidebar-mobile-open .main-sidebar {
4496
+ transform: translateX(0);
4497
+ }
4498
+
4499
+ body.main-sidebar-page.main-sidebar-collapsed .main-sidebar {
4500
+ flex: 0 0 auto;
4501
+ width: min(280px, calc(100vw - 40px));
4502
+ min-width: 0;
4503
+ max-width: 100%;
4504
+ padding: calc(12px + env(safe-area-inset-top)) 12px 18px;
4505
+ border-right-width: 1px;
4506
+ border-right-color: var(--pinokio-sidebar-separator);
4507
+ opacity: 1;
4508
+ pointer-events: none;
4509
+ transform: translateX(-100%);
4510
+ }
4511
+
4512
+ body.main-sidebar-page.main-sidebar-collapsed.main-sidebar-mobile-open .main-sidebar {
4513
+ pointer-events: auto;
4402
4514
  transform: translateX(0);
4403
4515
  }
4404
4516
 
4405
- body.main-sidebar-page .main-sidebar-nav,
4406
- body.main-sidebar-page .main-sidebar-section {
4517
+ body.main-sidebar-page .main-sidebar-nav,
4518
+ body.main-sidebar-page .main-sidebar-section {
4407
4519
  gap: 4px;
4408
4520
  }
4409
4521
 
@@ -399,17 +399,6 @@ body.dark #sidebar-toggle[aria-expanded="true"] {
399
399
  box-shadow: 0 3.8px 0 currentColor, 0 7.6px 0 currentColor;
400
400
  transition: opacity 150ms cubic-bezier(0.25, 1, 0.5, 1), transform 150ms cubic-bezier(0.25, 1, 0.5, 1);
401
401
  }
402
- #sidebar-toggle[aria-expanded="false"] .sidebar-toggle-panel {
403
- background: transparent;
404
- outline: 1.5px solid currentColor;
405
- outline-offset: -1px;
406
- opacity: 0.35;
407
- transform: translateX(-1px) scaleX(0.8);
408
- }
409
- #sidebar-toggle[aria-expanded="false"] .sidebar-toggle-content {
410
- opacity: 0.42;
411
- transform: translateX(-2px);
412
- }
413
402
  #devtab {
414
403
  align-items: center;
415
404
  justify-content: center;
@@ -2017,6 +2006,7 @@ body.dark .workspace-mode-link:hover {
2017
2006
  flex-grow: 1;
2018
2007
  min-height: 0;
2019
2008
  overflow: hidden;
2009
+ position: relative;
2020
2010
  /*
2021
2011
  border-top: 1px solid rgba(0,0,0,0.04);
2022
2012
  */
@@ -2036,6 +2026,9 @@ body.dark .appcanvas_filler {
2036
2026
  height: 5px;
2037
2027
  background: #F1F1F1 !important;
2038
2028
  }
2029
+ .app-sidebar-peek-trigger {
2030
+ display: none;
2031
+ }
2039
2032
  .appcanvas-resizer {
2040
2033
  display: none;
2041
2034
  }
@@ -2179,6 +2172,81 @@ body.dark .appcanvas > aside {
2179
2172
  pointer-events: none;
2180
2173
  }
2181
2174
 
2175
+ @media (min-width: 769px) and (hover: hover) and (pointer: fine) {
2176
+ .appcanvas.vertical.sidebar-collapsed > aside {
2177
+ position: absolute;
2178
+ top: 0;
2179
+ bottom: 0;
2180
+ left: 0;
2181
+ z-index: 10000020;
2182
+ display: flex;
2183
+ flex: none;
2184
+ width: var(--appcanvas-sidebar-width);
2185
+ min-width: var(--appcanvas-sidebar-width);
2186
+ max-width: var(--appcanvas-sidebar-width);
2187
+ border-right: 1px solid var(--pinokio-sidebar-separator);
2188
+ opacity: 0;
2189
+ pointer-events: none;
2190
+ overflow: hidden;
2191
+ transform: translateX(-100%);
2192
+ }
2193
+
2194
+ .appcanvas.vertical.sidebar-collapsed > aside .menu-container {
2195
+ width: var(--appcanvas-sidebar-width);
2196
+ }
2197
+
2198
+ .appcanvas.vertical.sidebar-collapsed .app-sidebar-peek-trigger {
2199
+ display: block;
2200
+ position: absolute;
2201
+ top: 0;
2202
+ bottom: 0;
2203
+ left: 0;
2204
+ z-index: 10000021;
2205
+ width: 14px;
2206
+ padding: 0;
2207
+ border: 0;
2208
+ border-radius: 0;
2209
+ background: transparent;
2210
+ cursor: default;
2211
+ }
2212
+
2213
+ .appcanvas.vertical.sidebar-collapsed .app-sidebar-peek-trigger::after {
2214
+ content: "";
2215
+ position: absolute;
2216
+ top: 50%;
2217
+ left: 4px;
2218
+ width: 3px;
2219
+ height: 36px;
2220
+ border-radius: 999px;
2221
+ background: var(--pinokio-sidebar-separator-hover);
2222
+ opacity: 0;
2223
+ transform: translateY(-50%);
2224
+ transition: opacity 140ms cubic-bezier(0.25, 1, 0.5, 1);
2225
+ }
2226
+
2227
+ .appcanvas.vertical.sidebar-collapsed .app-sidebar-peek-trigger:hover::after,
2228
+ .appcanvas.vertical.sidebar-collapsed .app-sidebar-peek-trigger:focus-visible::after,
2229
+ .appcanvas.vertical.sidebar-peeking .app-sidebar-peek-trigger::after {
2230
+ opacity: 1;
2231
+ }
2232
+
2233
+ .appcanvas.vertical.sidebar-peeking .app-sidebar-peek-trigger {
2234
+ z-index: 10000019;
2235
+ }
2236
+
2237
+ .appcanvas.vertical.sidebar-collapsed.sidebar-peeking > aside {
2238
+ box-shadow: 20px 0 42px rgba(15, 23, 42, 0.16);
2239
+ opacity: 1;
2240
+ pointer-events: auto;
2241
+ transform: translateX(0);
2242
+ }
2243
+
2244
+ body.dark .appcanvas.vertical.sidebar-collapsed.sidebar-peeking > aside {
2245
+ box-shadow: 22px 0 46px rgba(0, 0, 0, 0.38);
2246
+ }
2247
+
2248
+ }
2249
+
2182
2250
  @media (prefers-reduced-motion: reduce) {
2183
2251
  #sidebar-toggle,
2184
2252
  .sidebar-toggle-panel,
@@ -7810,6 +7878,9 @@ body.dark .pinokio-custom-terminal-header {
7810
7878
  </span>
7811
7879
  </span>
7812
7880
  </button>
7881
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
7882
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
7883
+ </a>
7813
7884
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
7814
7885
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
7815
7886
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -7906,6 +7977,7 @@ body.dark .pinokio-custom-terminal-header {
7906
7977
  </header>
7907
7978
  <div class='appcanvas vertical <%=type === "browse" ? "dev-workspace" : ""%>' data-page-error="<%= error ? '1' : '0' %>">
7908
7979
  <% if (type !== 'files') { %>
7980
+ <button type='button' class='app-sidebar-peek-trigger' data-app-sidebar-peek-trigger aria-label='Show app navigation' aria-controls='app-sidebar' aria-expanded='false'></button>
7909
7981
  <aside id='app-sidebar' class='active'>
7910
7982
  <div class='menu-container'>
7911
7983
  <div class='menu-scroller'>
@@ -16084,6 +16156,7 @@ const rerenderMenuSection = (container, html) => {
16084
16156
  const appcanvas = document.querySelector(".appcanvas")
16085
16157
  const aside = document.getElementById("app-sidebar")
16086
16158
  const toggle = document.getElementById("sidebar-toggle")
16159
+ const peekTrigger = document.querySelector("[data-app-sidebar-peek-trigger]")
16087
16160
  if (!appcanvas || !aside || !toggle) {
16088
16161
  if (toggle) {
16089
16162
  toggle.classList.add("hidden")
@@ -16109,10 +16182,49 @@ const rerenderMenuSection = (container, html) => {
16109
16182
  }
16110
16183
  }
16111
16184
 
16185
+ const desktopPeekMq = window.matchMedia("(min-width: 769px) and (hover: hover) and (pointer: fine)")
16186
+ let peekCloseTimer = 0
16187
+
16188
+ const isCollapsed = () => appcanvas.classList.contains("sidebar-collapsed")
16189
+ const isPeeking = () => appcanvas.classList.contains("sidebar-peeking")
16190
+ const syncSidebarVisibility = () => {
16191
+ const peeking = isPeeking()
16192
+ aside.setAttribute("aria-hidden", isCollapsed() && !peeking ? "true" : "false")
16193
+ if (peekTrigger) {
16194
+ peekTrigger.setAttribute("aria-expanded", peeking ? "true" : "false")
16195
+ }
16196
+ }
16197
+ const canPeek = () => desktopPeekMq.matches && isCollapsed() && !document.body.classList.contains("mobile-menu-open")
16198
+ const setPeeking = (peeking) => {
16199
+ const next = !!peeking && canPeek()
16200
+ window.clearTimeout(peekCloseTimer)
16201
+ appcanvas.classList.toggle("sidebar-peeking", next)
16202
+ syncSidebarVisibility()
16203
+ }
16204
+ const shouldKeepPeekOpen = () => {
16205
+ const active = document.activeElement
16206
+ return (
16207
+ (peekTrigger && peekTrigger.matches(":hover")) ||
16208
+ aside.matches(":hover") ||
16209
+ (peekTrigger && active === peekTrigger) ||
16210
+ (active instanceof Node && aside.contains(active))
16211
+ )
16212
+ }
16213
+ const closePeekSoon = () => {
16214
+ window.clearTimeout(peekCloseTimer)
16215
+ peekCloseTimer = window.setTimeout(() => {
16216
+ if (shouldKeepPeekOpen()) return
16217
+ setPeeking(false)
16218
+ }, 120)
16219
+ }
16220
+
16112
16221
  const setCollapsed = (collapsed, options = {}) => {
16113
16222
  const next = !!collapsed
16223
+ if (!next) {
16224
+ appcanvas.classList.remove("sidebar-peeking")
16225
+ }
16114
16226
  appcanvas.classList.toggle("sidebar-collapsed", next)
16115
- aside.setAttribute("aria-hidden", next ? "true" : "false")
16227
+ syncSidebarVisibility()
16116
16228
  updateToggle(next)
16117
16229
  if (options.persist !== false) {
16118
16230
  try {
@@ -16138,9 +16250,55 @@ const rerenderMenuSection = (container, html) => {
16138
16250
  setCollapsed(!appcanvas.classList.contains("sidebar-collapsed"))
16139
16251
  })
16140
16252
 
16253
+ if (peekTrigger) {
16254
+ peekTrigger.addEventListener("pointerenter", () => setPeeking(true))
16255
+ peekTrigger.addEventListener("focus", () => setPeeking(true))
16256
+ peekTrigger.addEventListener("click", () => setPeeking(true))
16257
+ peekTrigger.addEventListener("pointerleave", closePeekSoon)
16258
+ peekTrigger.addEventListener("blur", closePeekSoon)
16259
+ aside.addEventListener("pointerenter", () => {
16260
+ if (isPeeking()) {
16261
+ window.clearTimeout(peekCloseTimer)
16262
+ }
16263
+ })
16264
+ aside.addEventListener("focusin", () => {
16265
+ if (isPeeking()) {
16266
+ window.clearTimeout(peekCloseTimer)
16267
+ }
16268
+ })
16269
+ aside.addEventListener("pointerleave", closePeekSoon)
16270
+ aside.addEventListener("focusout", closePeekSoon)
16271
+ document.addEventListener("pointerdown", (event) => {
16272
+ if (!isPeeking()) return
16273
+ const target = event.target
16274
+ if (target instanceof Node && (aside.contains(target) || peekTrigger.contains(target))) return
16275
+ setPeeking(false)
16276
+ })
16277
+ document.addEventListener("keydown", (event) => {
16278
+ if (event.key === "Escape" && isPeeking()) {
16279
+ setPeeking(false)
16280
+ if (typeof peekTrigger.focus === "function") {
16281
+ peekTrigger.focus()
16282
+ }
16283
+ }
16284
+ })
16285
+ const handlePeekMediaChange = () => {
16286
+ if (!desktopPeekMq.matches) {
16287
+ setPeeking(false)
16288
+ }
16289
+ }
16290
+ if (typeof desktopPeekMq.addEventListener === "function") {
16291
+ desktopPeekMq.addEventListener("change", handlePeekMediaChange)
16292
+ } else if (typeof desktopPeekMq.addListener === "function") {
16293
+ desktopPeekMq.addListener(handlePeekMediaChange)
16294
+ }
16295
+ }
16296
+
16141
16297
  window.PinokioSidebar = {
16142
16298
  setCollapsed,
16143
- isCollapsed: () => appcanvas.classList.contains("sidebar-collapsed")
16299
+ setPeeking,
16300
+ isCollapsed,
16301
+ isPeeking
16144
16302
  }
16145
16303
  })()
16146
16304
  </script>
@@ -111,6 +111,9 @@ document.addEventListener('DOMContentLoaded', function() {
111
111
  <button class="btn2" id="minimize-header" data-tippy-content="fullscreen" title="fullscreen">
112
112
  <div><i class="fa-solid fa-expand"></i></div>
113
113
  </button>
114
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
115
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
116
+ </a>
114
117
  <button class="btn2" id="back" data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
115
118
  <button class="btn2" id="forward" data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
116
119
  <button class="btn2" id="refresh-page" data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -197,6 +197,9 @@ pre {
197
197
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
198
198
  <div><i class="fa-solid fa-expand"></i></div>
199
199
  </button>
200
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
201
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
202
+ </a>
200
203
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
201
204
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
202
205
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -332,6 +332,9 @@ iframe {
332
332
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
333
333
  <div><i class="fa-solid fa-expand"></i></div>
334
334
  </button>
335
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
336
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
337
+ </a>
335
338
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
336
339
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
337
340
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -125,6 +125,9 @@ body.frozen {
125
125
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
126
126
  <div><i class="fa-solid fa-expand"></i></div>
127
127
  </button>
128
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
129
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
130
+ </a>
128
131
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
129
132
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
130
133
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>