pinokiod 3.131.0 → 3.133.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.
@@ -1,3 +1,4 @@
1
+ const fs = require('fs')
1
2
  const path = require('path')
2
3
  const Common = require('./common')
3
4
  const Rewriter = require('./rewriter')
@@ -23,6 +24,19 @@ class LocalhostStaticRouter extends Processor {
23
24
  }
24
25
  }
25
26
  for(let { api_name, config } of configs) {
27
+ const apiRoot = this.router.kernel.path('api', api_name)
28
+ const indexPath = path.join(apiRoot, 'index.html')
29
+ const hasMenu = Boolean(config.menu)
30
+ if (hasMenu) {
31
+ delete this.router.rewrite_mapping[api_name]
32
+ continue
33
+ }
34
+ const hasIndex = fs.existsSync(indexPath)
35
+ const fileServerOptions = {}
36
+ if (hasIndex) {
37
+ fileServerOptions.index_names = ["index.html"]
38
+ }
39
+ const effectiveFileServerOptions = Object.keys(fileServerOptions).length ? { ...fileServerOptions } : undefined
26
40
  for(let domain in config.dns) {
27
41
  let localhost_match
28
42
  let peer_match
@@ -45,11 +59,13 @@ class LocalhostStaticRouter extends Processor {
45
59
  route: rewrite,
46
60
  match: [localhost_match],
47
61
  dial: local_dial,
62
+ fileServerOptions: effectiveFileServerOptions,
48
63
  })
49
64
  this.rewriter.handle({
50
65
  route: rewrite,
51
66
  match: [peer_match],
52
67
  dial: peer_dial,
68
+ fileServerOptions: effectiveFileServerOptions,
53
69
  })
54
70
 
55
71
  // this.router.add_rewrite({ route: new_path, match, peer, dial })
@@ -64,7 +80,7 @@ class LocalhostStaticRouter extends Processor {
64
80
 
65
81
 
66
82
 
67
- this.router.rewrite_mapping[api_name] = {
83
+ const rewriteEntry = {
68
84
  name: api_name,
69
85
  internal_router: [
70
86
  `${local_dial}${rewrite}`,
@@ -75,6 +91,10 @@ class LocalhostStaticRouter extends Processor {
75
91
  peer_match
76
92
  ]
77
93
  }
94
+ if (effectiveFileServerOptions) {
95
+ rewriteEntry.file_server_options = effectiveFileServerOptions
96
+ }
97
+ this.router.rewrite_mapping[api_name] = rewriteEntry
78
98
  // this.connector.handle({
79
99
  // match: peer_match,
80
100
  // connector: {
@@ -33,10 +33,14 @@ class PeerStaticRouter extends Processor {
33
33
  let url = new URL("http://" + rewrite_mapping.external_ip)
34
34
  let dial = url.host
35
35
  let rewrite = url.pathname
36
+ const fileServerOptions = rewrite_mapping.file_server_options
37
+ ? { ...rewrite_mapping.file_server_options }
38
+ : undefined
36
39
  this.rewriter.handle({
37
40
  route: url.pathname,
38
41
  match: rewrite_mapping.external_router,
39
42
  dial: url.host,
43
+ fileServerOptions,
40
44
  })
41
45
  }
42
46
  }
@@ -4,7 +4,7 @@ class Rewriter extends Processor {
4
4
  super()
5
5
  this.router = router
6
6
  }
7
- handle({ match, dial, route }) {
7
+ handle({ match, dial, route, fileServerOptions }) {
8
8
  //let rewrite = `${route}/{path}`
9
9
  //let rewrite = `${route}{http.request.uri}`
10
10
  /*
@@ -35,24 +35,32 @@ class Rewriter extends Processor {
35
35
 
36
36
  // stript the leading /asset/ => (/asset/api/test => /api/test)
37
37
  const asset_path = this.router.kernel.path(route.replace(/\/asset\//, ''))
38
- let handler = [{
38
+
39
+ const fileServerHandler = {
39
40
  "handler": "file_server",
40
41
  "root": asset_path,
41
- "browse": { },
42
- "index_names": ["index.html"]
43
- }]
42
+ ...fileServerOptions
43
+ }
44
+
45
+ const handlers = [fileServerHandler]
44
46
 
45
47
  // if the dial port has been overridden by router.custom_routers, use that instead
46
48
  let parsed_dial = this.parse_ip(dial)
47
49
  let override_handler = this.router.custom_routers[String(parsed_dial.port)]
48
50
  if (override_handler) {
49
- handler = override_handler
51
+ this.router.config.apps.http.servers.main.routes.push({
52
+ "match": [{
53
+ "host": match ,
54
+ }],
55
+ "handle": override_handler
56
+ })
57
+ return
50
58
  }
51
59
  this.router.config.apps.http.servers.main.routes.push({
52
60
  "match": [{
53
61
  "host": match ,
54
62
  }],
55
- "handle": handler
63
+ "handle": handlers
56
64
  })
57
65
  }
58
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.131.0",
3
+ "version": "3.133.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -449,6 +449,9 @@ class Server {
449
449
  } else {
450
450
  cfg.menu = cfg.menu(this.kernel, this.kernel.info)
451
451
  }
452
+ cfg = await this.renderIndex(name, cfg)
453
+ } else if (Array.isArray(cfg.menu)) {
454
+ cfg = await this.renderIndex(name, cfg)
452
455
  }
453
456
  } else {
454
457
  cfg = await this.renderIndex(name, cfg)
@@ -460,9 +463,16 @@ class Server {
460
463
  }
461
464
  async renderIndex(name, cfg) {
462
465
  let p = this.kernel.path("api", name)
463
- let html_path = path.resolve(p, "index.html")
464
- let html_exists = await this.kernel.exists(html_path)
465
- if (html_exists) {
466
+ let index_path = path.resolve(p, "index.html")
467
+ let index_exists = await this.kernel.exists(index_path)
468
+ let c = cfg
469
+ let menu = []
470
+ if (cfg.menu) {
471
+ ({ menu, ...c } = cfg)
472
+ }
473
+ console.log("Menu", menu)
474
+ console.log("c", c)
475
+ if (index_exists) {
466
476
  return Object.assign({
467
477
  title: name,
468
478
  menu: [{
@@ -470,8 +480,8 @@ class Server {
470
480
  icon: "fa-solid fa-link",
471
481
  text: "index.html",
472
482
  href: `/asset/api/${name}/index.html`,
473
- }]
474
- }, cfg)
483
+ }].concat(menu)
484
+ }, c)
475
485
  } else {
476
486
  return Object.assign({
477
487
  title: name,
@@ -480,8 +490,8 @@ class Server {
480
490
  icon: "fa-solid fa-link",
481
491
  text: "Project Files",
482
492
  href: `/files/api/${name}`,
483
- }]
484
- }, cfg)
493
+ }].concat(menu)
494
+ }, c)
485
495
  }
486
496
  }
487
497
  async getGit(ref, filepath) {
@@ -1,12 +1,291 @@
1
1
  document.addEventListener("DOMContentLoaded", () => {
2
- if (document.querySelector("#new-window")) {
3
- document.querySelector("#new-window").addEventListener("click", (e) => {
4
- let agent = document.body.getAttribute("data-agent")
2
+ const newWindowButton = document.querySelector("#new-window");
3
+ if (newWindowButton) {
4
+ newWindowButton.addEventListener("click", (event) => {
5
+ const agent = document.body.getAttribute("data-agent");
5
6
  if (agent === "electron") {
6
- window.open("/", "_blank", "pinokio")
7
+ window.open("/", "_blank", "pinokio");
7
8
  } else {
8
- window.open("/", "_blank")
9
+ window.open("/", "_blank");
9
10
  }
10
- })
11
+ });
11
12
  }
12
- })
13
+
14
+ const header = document.querySelector("header.navheader");
15
+ const minimizeButton = document.querySelector("#minimize-header");
16
+ const homeLink = header ? header.querySelector(".home") : null;
17
+ if (!header || !minimizeButton || !homeLink) {
18
+ return;
19
+ }
20
+
21
+ const headerTitle = header.querySelector("h1") || header;
22
+ let dragHandle = headerTitle.querySelector(".header-drag-handle");
23
+ if (!dragHandle) {
24
+ dragHandle = document.createElement("div");
25
+ dragHandle.className = "header-drag-handle";
26
+ dragHandle.setAttribute("aria-hidden", "true");
27
+ dragHandle.setAttribute("title", "Drag minimized header");
28
+ headerTitle.insertBefore(dragHandle, homeLink ? homeLink.nextSibling : headerTitle.firstChild);
29
+ }
30
+
31
+ const state = {
32
+ minimized: header.classList.contains("minimized"),
33
+ pointerId: null,
34
+ offsetX: 0,
35
+ offsetY: 0,
36
+ lastLeft: parseFloat(header.style.left) || 0,
37
+ lastTop: parseFloat(header.style.top) || 0,
38
+ hasCustomPosition: false,
39
+ originalPosition: {
40
+ top: header.style.top || "",
41
+ left: header.style.left || "",
42
+ right: header.style.right || "",
43
+ bottom: header.style.bottom || "",
44
+ },
45
+ transitionHandler: null,
46
+ };
47
+
48
+ const MIN_MARGIN = 8;
49
+
50
+ const clampPosition = (left, top) => {
51
+ const rect = header.getBoundingClientRect();
52
+ const maxLeft = Math.max(0, window.innerWidth - rect.width);
53
+ const maxTop = Math.max(0, window.innerHeight - rect.height);
54
+ return {
55
+ left: Math.min(Math.max(0, left), maxLeft),
56
+ top: Math.min(Math.max(0, top), maxTop),
57
+ };
58
+ };
59
+
60
+ const applyPosition = (left, top) => {
61
+ header.style.left = `${left}px`;
62
+ header.style.top = `${top}px`;
63
+ header.style.right = "auto";
64
+ header.style.bottom = "auto";
65
+ };
66
+
67
+ const rememberOriginalPosition = () => {
68
+ state.originalPosition = {
69
+ top: header.style.top || "",
70
+ left: header.style.left || "",
71
+ right: header.style.right || "",
72
+ bottom: header.style.bottom || "",
73
+ };
74
+ };
75
+
76
+ const measureRect = (configureClone) => {
77
+ const clone = header.cloneNode(true);
78
+ clone.querySelectorAll("[id]").forEach((node) => node.removeAttribute("id"));
79
+ Object.assign(clone.style, {
80
+ transition: "none",
81
+ transform: "none",
82
+ position: "fixed",
83
+ visibility: "hidden",
84
+ pointerEvents: "none",
85
+ margin: "0",
86
+ left: "0",
87
+ top: "0",
88
+ right: "auto",
89
+ bottom: "auto",
90
+ width: "auto",
91
+ height: "auto",
92
+ });
93
+ document.body.appendChild(clone);
94
+ if (typeof configureClone === "function") {
95
+ configureClone(clone);
96
+ }
97
+ clone.style.right = "auto";
98
+ clone.style.bottom = "auto";
99
+ const rect = clone.getBoundingClientRect();
100
+ clone.remove();
101
+ return rect;
102
+ };
103
+
104
+ const stopTransition = () => {
105
+ if (state.transitionHandler) {
106
+ header.removeEventListener("transitionend", state.transitionHandler);
107
+ state.transitionHandler = null;
108
+ }
109
+ header.classList.remove("transitioning");
110
+ header.style.transition = "";
111
+ header.style.transform = "";
112
+ header.style.transformOrigin = "";
113
+ header.style.opacity = "";
114
+ header.style.willChange = "";
115
+ };
116
+
117
+ const minimize = () => {
118
+ if (state.minimized || header.classList.contains("transitioning")) {
119
+ return;
120
+ }
121
+
122
+ rememberOriginalPosition();
123
+
124
+ const firstRect = header.getBoundingClientRect();
125
+ const minimizedSize = measureRect((clone) => {
126
+ clone.classList.add("minimized");
127
+ });
128
+
129
+ const defaultLeft = Math.max(MIN_MARGIN, window.innerWidth - minimizedSize.width - MIN_MARGIN);
130
+ const defaultTop = Math.max(MIN_MARGIN, window.innerHeight - minimizedSize.height - MIN_MARGIN);
131
+ const targetLeft = state.hasCustomPosition ? state.lastLeft : defaultLeft;
132
+ const targetTop = state.hasCustomPosition ? state.lastTop : defaultTop;
133
+
134
+ state.lastLeft = targetLeft;
135
+ state.lastTop = targetTop;
136
+
137
+ stopTransition();
138
+
139
+ header.classList.add("minimized");
140
+ applyPosition(targetLeft, targetTop);
141
+
142
+ const lastRect = header.getBoundingClientRect();
143
+ const deltaX = firstRect.left - lastRect.left;
144
+ const deltaY = firstRect.top - lastRect.top;
145
+ const scaleX = firstRect.width / lastRect.width;
146
+ const scaleY = firstRect.height / lastRect.height;
147
+
148
+ header.style.transition = "none";
149
+ header.style.transformOrigin = "top left";
150
+ header.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`;
151
+ header.style.willChange = "transform";
152
+
153
+ header.offsetWidth;
154
+
155
+ header.classList.add("transitioning");
156
+ header.style.transition = "transform 520ms cubic-bezier(0.22, 1, 0.36, 1)";
157
+ header.style.transform = "";
158
+
159
+ state.transitionHandler = (event) => {
160
+ if (event.propertyName !== "transform") {
161
+ return;
162
+ }
163
+ header.removeEventListener("transitionend", state.transitionHandler);
164
+ state.transitionHandler = null;
165
+ stopTransition();
166
+ state.minimized = true;
167
+ };
168
+
169
+ header.addEventListener("transitionend", state.transitionHandler);
170
+ };
171
+
172
+ const restore = () => {
173
+ if (!state.minimized || header.classList.contains("transitioning")) {
174
+ return;
175
+ }
176
+
177
+ const firstRect = header.getBoundingClientRect();
178
+
179
+ stopTransition();
180
+
181
+ header.classList.add("transitioning");
182
+ header.style.willChange = "transform";
183
+ header.style.transition = "none";
184
+ header.style.transformOrigin = "top left";
185
+
186
+ header.classList.remove("minimized");
187
+ header.style.left = state.originalPosition.left;
188
+ header.style.top = state.originalPosition.top;
189
+ header.style.right = state.originalPosition.right;
190
+ header.style.bottom = state.originalPosition.bottom;
191
+
192
+ const lastRect = header.getBoundingClientRect();
193
+ const deltaX = firstRect.left - lastRect.left;
194
+ const deltaY = firstRect.top - lastRect.top;
195
+ const scaleX = firstRect.width / lastRect.width;
196
+ const scaleY = firstRect.height / lastRect.height;
197
+
198
+ header.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`;
199
+
200
+ header.offsetWidth;
201
+
202
+ header.style.transition = "transform 560ms cubic-bezier(0.18, 0.85, 0.4, 1)";
203
+ header.style.transform = "";
204
+
205
+ state.transitionHandler = (event) => {
206
+ if (event.propertyName !== "transform") {
207
+ return;
208
+ }
209
+ header.removeEventListener("transitionend", state.transitionHandler);
210
+ state.transitionHandler = null;
211
+ stopTransition();
212
+ state.minimized = false;
213
+ state.hasCustomPosition = false;
214
+ state.lastLeft = parseFloat(header.style.left) || 0;
215
+ state.lastTop = parseFloat(header.style.top) || 0;
216
+ };
217
+
218
+ header.addEventListener("transitionend", state.transitionHandler);
219
+ };
220
+
221
+ minimizeButton.addEventListener("click", (event) => {
222
+ event.preventDefault();
223
+ minimize();
224
+ });
225
+
226
+ homeLink.addEventListener("click", (event) => {
227
+ if (!state.minimized) {
228
+ return;
229
+ }
230
+ event.preventDefault();
231
+ restore();
232
+ });
233
+
234
+ const onPointerDown = (event) => {
235
+ if (!state.minimized || header.classList.contains("transitioning")) {
236
+ return;
237
+ }
238
+ state.pointerId = event.pointerId;
239
+ const rect = header.getBoundingClientRect();
240
+ state.offsetX = event.clientX - rect.left;
241
+ state.offsetY = event.clientY - rect.top;
242
+ if (typeof dragHandle.setPointerCapture === "function") {
243
+ try {
244
+ dragHandle.setPointerCapture(event.pointerId);
245
+ } catch (error) {}
246
+ }
247
+ dragHandle.classList.add("dragging");
248
+ event.preventDefault();
249
+ };
250
+
251
+ const onPointerMove = (event) => {
252
+ if (!state.minimized || state.pointerId !== event.pointerId) {
253
+ return;
254
+ }
255
+ const left = event.clientX - state.offsetX;
256
+ const top = event.clientY - state.offsetY;
257
+ const clamped = clampPosition(left, top);
258
+ state.lastLeft = clamped.left;
259
+ state.lastTop = clamped.top;
260
+ state.hasCustomPosition = true;
261
+ applyPosition(clamped.left, clamped.top);
262
+ };
263
+
264
+ const onPointerEnd = (event) => {
265
+ if (state.pointerId !== event.pointerId) {
266
+ return;
267
+ }
268
+ if (typeof dragHandle.releasePointerCapture === "function") {
269
+ try {
270
+ dragHandle.releasePointerCapture(event.pointerId);
271
+ } catch (error) {}
272
+ }
273
+ dragHandle.classList.remove("dragging");
274
+ state.pointerId = null;
275
+ };
276
+
277
+ dragHandle.addEventListener("pointerdown", onPointerDown);
278
+ dragHandle.addEventListener("pointermove", onPointerMove);
279
+ dragHandle.addEventListener("pointerup", onPointerEnd);
280
+ dragHandle.addEventListener("pointercancel", onPointerEnd);
281
+
282
+ window.addEventListener("resize", () => {
283
+ if (!state.minimized || header.classList.contains("transitioning")) {
284
+ return;
285
+ }
286
+ const { left, top } = clampPosition(state.lastLeft, state.lastTop);
287
+ state.lastLeft = left;
288
+ state.lastTop = top;
289
+ applyPosition(left, top);
290
+ });
291
+ });
@@ -2482,3 +2482,85 @@ body.dark #dropdown-portal .dropdown-content .btn2 {
2482
2482
  border-radius: 10px;
2483
2483
  background: white;
2484
2484
  }
2485
+
2486
+
2487
+ header.navheader.minimized {
2488
+ position: fixed;
2489
+ right: auto;
2490
+ bottom: auto;
2491
+ width: auto;
2492
+ height: auto;
2493
+ max-height: none;
2494
+ padding: 4px 8px;
2495
+ border-radius: 12px;
2496
+ background: var(--light-nav-bg);
2497
+ box-shadow: 0 8px 18px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
2498
+ display: inline-flex;
2499
+ align-items: center;
2500
+ overflow: visible;
2501
+ z-index: 1000000;
2502
+ }
2503
+ body.dark header.navheader.minimized {
2504
+ background: var(--dark-nav-bg);
2505
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.55), 0 0 1px rgba(255, 255, 255, 0.15);
2506
+ }
2507
+ header.navheader.minimized h1 {
2508
+ display: flex;
2509
+ align-items: center;
2510
+ gap: 8px;
2511
+ margin: 0;
2512
+ padding: 0;
2513
+ height: auto;
2514
+ overflow: visible;
2515
+ }
2516
+ header.navheader.minimized h1 > *:not(.home):not(.header-drag-handle) {
2517
+ display: none !important;
2518
+ }
2519
+ header.navheader .header-drag-handle {
2520
+ display: none;
2521
+ position: relative;
2522
+ cursor: grab;
2523
+ user-select: none;
2524
+ touch-action: none;
2525
+ }
2526
+ header.navheader .header-drag-handle::before {
2527
+ content: "";
2528
+ display: block;
2529
+ width: 6px;
2530
+ height: 18px;
2531
+ border-radius: 3px;
2532
+ opacity: 0.45;
2533
+ background-image: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0 1px, transparent 1px 4px);
2534
+ transition: opacity 0.2s ease;
2535
+ }
2536
+ body.dark header.navheader .header-drag-handle::before {
2537
+ background-image: repeating-linear-gradient(to bottom, rgba(255, 255, 255, 0.55) 0 1px, transparent 1px 4px);
2538
+ opacity: 0.35;
2539
+ }
2540
+ header.navheader.minimized .header-drag-handle {
2541
+ display: block;
2542
+ }
2543
+ header.navheader.minimized:hover .header-drag-handle::before {
2544
+ opacity: 0.7;
2545
+ }
2546
+ header.navheader.minimized .header-drag-handle.dragging {
2547
+ cursor: grabbing;
2548
+ }
2549
+ header.navheader.minimized .header-drag-handle.dragging::before {
2550
+ opacity: 0.85;
2551
+ }
2552
+ header.navheader.minimized .home {
2553
+ display: flex;
2554
+ align-items: center;
2555
+ position: static;
2556
+ padding: 0;
2557
+ }
2558
+ header.navheader.minimized .home .icon {
2559
+ width: 24px;
2560
+ height: 24px;
2561
+ }
2562
+
2563
+
2564
+ header.navheader.transitioning {
2565
+ pointer-events: none;
2566
+ }
@@ -404,6 +404,9 @@ body.dark .appcanvas > aside .header-item.selected {
404
404
  border-bottom: none;
405
405
  z-index: 1;
406
406
  }
407
+ header.navheader.minimized + .appcanvas > aside .menu-container {
408
+ padding: 8px 0 0;
409
+ }
407
410
 
408
411
  main {
409
412
  flex-grow: 1;
@@ -2723,6 +2726,9 @@ body.dark {
2723
2726
  .appcanvas {
2724
2727
  margin-left: 55px;
2725
2728
  }
2729
+ header.navheader.minimized + .appcanvas {
2730
+ margin-left: 0;
2731
+ }
2726
2732
  main, iframe.mainframe {
2727
2733
  padding-left: 0;
2728
2734
  }
@@ -2822,6 +2828,9 @@ body.dark {
2822
2828
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
2823
2829
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
2824
2830
  </a>
2831
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
2832
+ <div><i class="fa-solid fa-window-maximize"></i></div>
2833
+ </button>
2825
2834
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
2826
2835
  <div><i class="fa-solid fa-plus"></i></div>
2827
2836
  </button>
@@ -205,6 +205,9 @@ pre {
205
205
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
206
206
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
207
207
  </a>
208
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
209
+ <div><i class="fa-solid fa-window-maximize"></i></div>
210
+ </button>
208
211
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
209
212
  <div><i class="fa-solid fa-plus"></i></div>
210
213
  </button>
@@ -830,6 +830,9 @@ document.addEventListener('DOMContentLoaded', function() {
830
830
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
831
831
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
832
832
  </a>
833
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
834
+ <div><i class="fa-solid fa-window-maximize"></i></div>
835
+ </button>
833
836
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
834
837
  <div><i class="fa-solid fa-plus"></i></div>
835
838
  </button>
@@ -344,6 +344,9 @@ iframe {
344
344
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
345
345
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
346
346
  </a>
347
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
348
+ <div><i class="fa-solid fa-window-maximize"></i></div>
349
+ </button>
347
350
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
348
351
  <div><i class="fa-solid fa-plus"></i></div>
349
352
  </button>
@@ -133,6 +133,9 @@ body.frozen {
133
133
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
134
134
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
135
135
  </a>
136
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
137
+ <div><i class="fa-solid fa-window-maximize"></i></div>
138
+ </button>
136
139
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
137
140
  <div><i class="fa-solid fa-plus"></i></div>
138
141
  </button>
@@ -134,6 +134,9 @@ body main iframe {
134
134
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
135
135
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
136
136
  </a>
137
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
138
+ <div><i class="fa-solid fa-window-maximize"></i></div>
139
+ </button>
137
140
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
138
141
  <div><i class="fa-solid fa-plus"></i></div>
139
142
  </button>
@@ -167,6 +167,9 @@ document.addEventListener("DOMContentLoaded", async () => {
167
167
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
168
168
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
169
169
  </a>
170
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
171
+ <div><i class="fa-solid fa-window-maximize"></i></div>
172
+ </button>
170
173
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
171
174
  <div><i class="fa-solid fa-plus"></i></div>
172
175
  </button>
@@ -63,6 +63,9 @@ main iframe {
63
63
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
64
64
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
65
65
  </a>
66
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
67
+ <div><i class="fa-solid fa-window-maximize"></i></div>
68
+ </button>
66
69
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
67
70
  <div><i class="fa-solid fa-plus"></i></div>
68
71
  </button>
@@ -246,6 +246,9 @@ ol {
246
246
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
247
247
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
248
248
  </a>
249
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
250
+ <div><i class="fa-solid fa-window-maximize"></i></div>
251
+ </button>
249
252
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
250
253
  <div><i class="fa-solid fa-plus"></i></div>
251
254
  </button>
@@ -267,6 +267,9 @@ body.dark .item .tile .badge {
267
267
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
268
268
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
269
269
  </a>
270
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
271
+ <div><i class="fa-solid fa-window-maximize"></i></div>
272
+ </button>
270
273
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
271
274
  <div><i class="fa-solid fa-plus"></i></div>
272
275
  </button>
@@ -435,6 +435,9 @@ body.dark aside .current.selected {
435
435
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
436
436
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
437
437
  </a>
438
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
439
+ <div><i class="fa-solid fa-window-maximize"></i></div>
440
+ </button>
438
441
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
439
442
  <div><i class="fa-solid fa-plus"></i></div>
440
443
  </button>
@@ -272,6 +272,9 @@ body.dark .open-menu, body.dark .browse {
272
272
  <button class='btn2' id='genlog'><div><i class="fa-solid fa-laptop-code"></i></div><div>Logs</div></button>
273
273
  <a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><div><i class="fa-solid fa-download"></i></div><div>Download logs</div></a>
274
274
  <a class='btn2' href="/home?mode=settings"><div><i class="fa-solid fa-gear"></i></div><div>Settings</div></a>
275
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
276
+ <div><i class="fa-solid fa-window-maximize"></i></div>
277
+ </button>
275
278
  <button id='new-window' title='open a new window' class='btn2' data-agent="<%=agent%>"><div><i class="fa-solid fa-plus"></i></div><div>Window</div></button>
276
279
  </div>
277
280
  <% } %>
@@ -1532,6 +1532,9 @@ body.dark .ace-editor {
1532
1532
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
1533
1533
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
1534
1534
  </a>
1535
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
1536
+ <div><i class="fa-solid fa-window-maximize"></i></div>
1537
+ </button>
1535
1538
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
1536
1539
  <div><i class="fa-solid fa-plus"></i></div>
1537
1540
  </button>
@@ -767,6 +767,9 @@ body.dark .appcanvas {
767
767
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
768
768
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
769
769
  </a>
770
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
771
+ <div><i class="fa-solid fa-window-maximize"></i></div>
772
+ </button>
770
773
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
771
774
  <div><i class="fa-solid fa-plus"></i></div>
772
775
  </button>
@@ -553,6 +553,9 @@ document.addEventListener('DOMContentLoaded', function() {
553
553
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
554
554
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
555
555
  </a>
556
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
557
+ <div><i class="fa-solid fa-window-maximize"></i></div>
558
+ </button>
556
559
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
557
560
  <div><i class="fa-solid fa-plus"></i></div>
558
561
  </button>
@@ -1066,6 +1066,9 @@ document.addEventListener('DOMContentLoaded', function() {
1066
1066
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
1067
1067
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
1068
1068
  </a>
1069
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
1070
+ <div><i class="fa-solid fa-window-maximize"></i></div>
1071
+ </button>
1069
1072
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
1070
1073
  <div><i class="fa-solid fa-plus"></i></div>
1071
1074
  </button>
@@ -428,6 +428,9 @@ table h3 {
428
428
  <button class='btn2' id='genlog'><div><i class="fa-solid fa-laptop-code"></i></div><div>Logs</div></button>
429
429
  <a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><div><i class="fa-solid fa-download"></i></div><div>Download logs</div></a>
430
430
  <a class='btn2' href="/home?mode=settings"><div><i class="fa-solid fa-gear"></i></div><div>Settings</div></a>
431
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
432
+ <div><i class="fa-solid fa-window-maximize"></i></div>
433
+ </button>
431
434
  <button id='new-window' title='open a new window' class='btn2' data-agent="<%=agent%>"><div><i class="fa-solid fa-plus"></i></div><div>Window</div></button>
432
435
  </div>
433
436
  </h1>
@@ -414,6 +414,9 @@ input:checked + .slider:before {
414
414
  <button class='btn2' id='genlog'><div><i class="fa-solid fa-laptop-code"></i></div><div>Logs</div></button>
415
415
  <a id='downloadlogs' download class='hidden btn2' href="/pinokio/logs.zip"><div><i class="fa-solid fa-download"></i></div><div>Download logs</div></a>
416
416
  <a class='btn2' href="/home?mode=settings"><div><i class="fa-solid fa-gear"></i></div><div>Settings</div></a>
417
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
418
+ <div><i class="fa-solid fa-window-maximize"></i></div>
419
+ </button>
417
420
  <button id='new-window' title='open a new window' class='btn2' data-agent="<%=agent%>"><div><i class="fa-solid fa-plus"></i></div><div>Window</div></button>
418
421
  </div>
419
422
  </h1>
@@ -1014,6 +1014,9 @@ body.dark .appcanvas {
1014
1014
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
1015
1015
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
1016
1016
  </a>
1017
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
1018
+ <div><i class="fa-solid fa-window-maximize"></i></div>
1019
+ </button>
1017
1020
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
1018
1021
  <div><i class="fa-solid fa-plus"></i></div>
1019
1022
  </button>
@@ -954,6 +954,9 @@ body.dark .top-menu .btn2.selected {
954
954
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
955
955
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
956
956
  </a>
957
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
958
+ <div><i class="fa-solid fa-window-maximize"></i></div>
959
+ </button>
957
960
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
958
961
  <div><i class="fa-solid fa-plus"></i></div>
959
962
  </button>
@@ -726,6 +726,9 @@ document.addEventListener('DOMContentLoaded', function() {
726
726
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
727
727
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
728
728
  </a>
729
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
730
+ <div><i class="fa-solid fa-window-maximize"></i></div>
731
+ </button>
729
732
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
730
733
  <div><i class="fa-solid fa-plus"></i></div>
731
734
  </button>
@@ -398,6 +398,9 @@ document.addEventListener('DOMContentLoaded', function() {
398
398
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
399
399
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
400
400
  </a>
401
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
402
+ <div><i class="fa-solid fa-window-maximize"></i></div>
403
+ </button>
401
404
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
402
405
  <div><i class="fa-solid fa-plus"></i></div>
403
406
  </button>
@@ -144,6 +144,9 @@ body {
144
144
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
145
145
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
146
146
  </a>
147
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
148
+ <div><i class="fa-solid fa-window-maximize"></i></div>
149
+ </button>
147
150
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
148
151
  <div><i class="fa-solid fa-plus"></i></div>
149
152
  </button>
@@ -160,6 +160,9 @@ body.dark .card {
160
160
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
161
161
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
162
162
  </a>
163
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
164
+ <div><i class="fa-solid fa-window-maximize"></i></div>
165
+ </button>
163
166
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
164
167
  <div><i class="fa-solid fa-plus"></i></div>
165
168
  </button>
@@ -381,6 +381,9 @@ body.dark .plugin-option:hover {
381
381
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
382
382
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
383
383
  </a>
384
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
385
+ <div><i class="fa-solid fa-window-maximize"></i></div>
386
+ </button>
384
387
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
385
388
  <div><i class="fa-solid fa-plus"></i></div>
386
389
  </button>
@@ -1116,6 +1116,9 @@ document.addEventListener('DOMContentLoaded', function() {
1116
1116
  <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
1117
1117
  <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
1118
1118
  </a>
1119
+ <button class='btn2' id='minimize-header' data-tippy-content="minimize header" title='minimize header'>
1120
+ <div><i class="fa-solid fa-window-maximize"></i></div>
1121
+ </button>
1119
1122
  <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
1120
1123
  <div><i class="fa-solid fa-plus"></i></div>
1121
1124
  </button>