pinokiod 3.47.0 → 3.49.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.
@@ -0,0 +1,396 @@
1
+ /**
2
+ * URL Dropdown functionality for process selection
3
+ * Fetches running processes from /info/procs API and displays them in a dropdown
4
+ */
5
+
6
+ function initUrlDropdown(config = {}) {
7
+ const urlInput = document.querySelector('.urlbar input[type="url"]');
8
+ const dropdown = document.getElementById('url-dropdown');
9
+ const mobileButton = document.getElementById('mobile-link-button');
10
+
11
+ if (!urlInput || !dropdown) {
12
+ console.warn('URL dropdown elements not found');
13
+ return;
14
+ }
15
+
16
+ // Configuration options
17
+ const options = {
18
+ clearBehavior: config.clearBehavior || 'empty', // 'empty' or 'restore'
19
+ defaultValue: config.defaultValue || '',
20
+ apiEndpoint: config.apiEndpoint || '/info/procs',
21
+ ...config
22
+ };
23
+
24
+ let isDropdownVisible = false;
25
+ let allProcesses = []; // Store all processes for filtering
26
+ let filteredProcesses = []; // Store currently filtered processes
27
+
28
+ // Initialize input field state based on clear behavior
29
+ initializeInputValue();
30
+
31
+ // Handle page navigation events
32
+ window.addEventListener('pageshow', function(event) {
33
+ if (event.persisted || window.performance?.navigation?.type === 2) {
34
+ initializeInputValue();
35
+ }
36
+ });
37
+
38
+ // Event listeners
39
+ urlInput.addEventListener('focus', function() {
40
+ // Auto-select text for restore behavior to make filtering easier
41
+ if (options.clearBehavior === 'restore' && urlInput.value) {
42
+ // Use setTimeout to ensure the focus event completes first
43
+ setTimeout(() => {
44
+ urlInput.select();
45
+ }, 0);
46
+ }
47
+ showDropdown();
48
+ });
49
+ urlInput.addEventListener('input', handleInputChange);
50
+
51
+ // Hide dropdown when clicking outside
52
+ document.addEventListener('click', function(e) {
53
+ if (!e.target.closest('.url-input-container')) {
54
+ hideDropdown();
55
+ }
56
+ });
57
+
58
+ function initializeInputValue() {
59
+ if (options.clearBehavior === 'empty') {
60
+ urlInput.value = '';
61
+ } else if (options.clearBehavior === 'restore') {
62
+ const originalValue = urlInput.getAttribute('value') || options.defaultValue;
63
+ if (urlInput.value !== originalValue) {
64
+ urlInput.value = originalValue;
65
+ }
66
+ }
67
+ }
68
+
69
+ function showDropdown() {
70
+ if (isDropdownVisible && allProcesses.length > 0) {
71
+ // If dropdown is already visible and we have data, show all initially
72
+ showAllProcesses();
73
+ return;
74
+ }
75
+
76
+ isDropdownVisible = true;
77
+ dropdown.style.display = 'block';
78
+
79
+ // If we already have processes data, show all initially
80
+ if (allProcesses.length > 0) {
81
+ showAllProcesses();
82
+ return;
83
+ }
84
+
85
+ // Otherwise, show loading and fetch data
86
+ dropdown.innerHTML = '<div class="url-dropdown-loading">Loading running processes...</div>';
87
+
88
+ // Fetch processes from API
89
+ fetch(options.apiEndpoint)
90
+ .then(response => {
91
+ if (!response.ok) {
92
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
93
+ }
94
+ return response.json();
95
+ })
96
+ .then(data => {
97
+ allProcesses = data.info || [];
98
+ showAllProcesses(); // Show all processes when dropdown first opens
99
+ })
100
+ .catch(error => {
101
+ console.error('Failed to fetch processes:', error);
102
+ dropdown.innerHTML = '<div class="url-dropdown-empty">Failed to load processes</div>';
103
+ allProcesses = [];
104
+ });
105
+ }
106
+
107
+ function showAllProcesses() {
108
+ filteredProcesses = allProcesses;
109
+ populateDropdown(filteredProcesses);
110
+ }
111
+
112
+ function handleInputChange() {
113
+ if (!isDropdownVisible) return;
114
+
115
+ const query = urlInput.value.toLowerCase().trim();
116
+
117
+ // Special case: if text is selected (user just focused), don't filter yet
118
+ if (urlInput.selectionStart === 0 && urlInput.selectionEnd === urlInput.value.length) {
119
+ // Text is fully selected, show all processes until user starts typing
120
+ filteredProcesses = allProcesses;
121
+ } else if (!query) {
122
+ // No query, show all processes
123
+ filteredProcesses = allProcesses;
124
+ } else {
125
+ // Filter processes based on name and URL
126
+ filteredProcesses = allProcesses.filter(process => {
127
+ const url = `http://${process.ip}`;
128
+ const name = process.name.toLowerCase();
129
+ const urlLower = url.toLowerCase();
130
+
131
+ return name.includes(query) || urlLower.includes(query);
132
+ });
133
+ }
134
+
135
+ populateDropdown(filteredProcesses);
136
+ }
137
+
138
+ function hideDropdown() {
139
+ isDropdownVisible = false;
140
+ dropdown.style.display = 'none';
141
+ }
142
+
143
+ function populateDropdown(processes) {
144
+ if (processes.length === 0) {
145
+ const query = urlInput.value.toLowerCase().trim();
146
+ const message = query
147
+ ? `No processes match "${query}"`
148
+ : 'No running processes found';
149
+ dropdown.innerHTML = `<div class="url-dropdown-empty">${message}</div>`;
150
+ return;
151
+ }
152
+
153
+ const items = processes.map(process => {
154
+ const url = `http://${process.ip}`;
155
+ return `
156
+ <div class="url-dropdown-item" data-url="${url}">
157
+ <div class="url-dropdown-name">${escapeHtml(process.name)}</div>
158
+ <div class="url-dropdown-url">${escapeHtml(url)}</div>
159
+ </div>
160
+ `;
161
+ }).join('');
162
+
163
+ dropdown.innerHTML = items;
164
+
165
+ // Add click handlers to dropdown items
166
+ dropdown.querySelectorAll('.url-dropdown-item').forEach(item => {
167
+ item.addEventListener('click', function() {
168
+ const url = this.getAttribute('data-url');
169
+ urlInput.value = url;
170
+ hideDropdown();
171
+ // Submit the form
172
+ urlInput.closest('form').dispatchEvent(new Event('submit'));
173
+ });
174
+ });
175
+ }
176
+
177
+ // Utility function to escape HTML
178
+ function escapeHtml(text) {
179
+ const div = document.createElement('div');
180
+ div.textContent = text;
181
+ return div.innerHTML;
182
+ }
183
+
184
+ // Mobile modal functionality
185
+ function createMobileModal() {
186
+ const overlay = document.createElement('div');
187
+ overlay.className = 'url-modal-overlay';
188
+ overlay.id = 'url-modal-overlay';
189
+
190
+ const content = document.createElement('div');
191
+ content.className = 'url-modal-content';
192
+
193
+ const closeButton = document.createElement('span');
194
+ closeButton.className = 'url-modal-close';
195
+ closeButton.innerHTML = '&times;';
196
+ closeButton.onclick = closeMobileModal;
197
+
198
+ const modalInput = document.createElement('input');
199
+ modalInput.type = 'url';
200
+ modalInput.className = 'url-modal-input';
201
+ modalInput.placeholder = 'enter a local url';
202
+
203
+ const modalDropdown = document.createElement('div');
204
+ modalDropdown.className = 'url-dropdown';
205
+ modalDropdown.id = 'url-modal-dropdown';
206
+ modalDropdown.style.position = 'relative';
207
+ modalDropdown.style.top = '0';
208
+ modalDropdown.style.left = '0';
209
+ modalDropdown.style.right = '0';
210
+ modalDropdown.style.marginTop = '10px';
211
+
212
+ content.appendChild(closeButton);
213
+ content.appendChild(modalInput);
214
+ content.appendChild(modalDropdown);
215
+ overlay.appendChild(content);
216
+
217
+ return { overlay, input: modalInput, dropdown: modalDropdown };
218
+ }
219
+
220
+ function showMobileModal() {
221
+ let modal = document.getElementById('url-modal-overlay');
222
+ if (!modal) {
223
+ const { overlay, input: modalInput, dropdown: modalDropdown } = createMobileModal();
224
+ modal = overlay;
225
+ document.body.appendChild(modal);
226
+
227
+ // Initialize dropdown functionality for modal
228
+ modalInput.addEventListener('focus', function() {
229
+ if (options.clearBehavior === 'restore' && modalInput.value) {
230
+ setTimeout(() => modalInput.select(), 0);
231
+ }
232
+ showModalDropdown(modalDropdown);
233
+ });
234
+ modalInput.addEventListener('input', function() {
235
+ handleModalInputChange(modalInput, modalDropdown);
236
+ });
237
+
238
+ // Close modal when clicking outside content
239
+ modal.addEventListener('click', function(e) {
240
+ if (e.target === modal) {
241
+ closeMobileModal();
242
+ }
243
+ });
244
+
245
+ // Handle form submission
246
+ modalInput.addEventListener('keypress', function(e) {
247
+ if (e.key === 'Enter') {
248
+ e.preventDefault();
249
+ if (modalInput.value) {
250
+ urlInput.value = modalInput.value;
251
+ urlInput.closest('form').dispatchEvent(new Event('submit'));
252
+ closeMobileModal();
253
+ }
254
+ }
255
+ });
256
+ }
257
+
258
+ modal.style.display = 'flex';
259
+ const modalInput = modal.querySelector('.url-modal-input');
260
+
261
+ // Set initial value based on config
262
+ if (options.clearBehavior === 'restore') {
263
+ modalInput.value = urlInput.value || options.defaultValue || '';
264
+ } else {
265
+ modalInput.value = '';
266
+ }
267
+
268
+ setTimeout(() => modalInput.focus(), 100);
269
+ }
270
+
271
+ function closeMobileModal() {
272
+ const modal = document.getElementById('url-modal-overlay');
273
+ if (modal) {
274
+ modal.style.display = 'none';
275
+ }
276
+ }
277
+
278
+ function showModalDropdown(modalDropdown) {
279
+ modalDropdown.style.display = 'block';
280
+
281
+ if (allProcesses.length > 0) {
282
+ populateModalDropdown(allProcesses, modalDropdown);
283
+ return;
284
+ }
285
+
286
+ modalDropdown.innerHTML = '<div class="url-dropdown-loading">Loading running processes...</div>';
287
+
288
+ fetch(options.apiEndpoint)
289
+ .then(response => {
290
+ if (!response.ok) {
291
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
292
+ }
293
+ return response.json();
294
+ })
295
+ .then(data => {
296
+ allProcesses = data.info || [];
297
+ populateModalDropdown(allProcesses, modalDropdown);
298
+ })
299
+ .catch(error => {
300
+ console.error('Failed to fetch processes:', error);
301
+ modalDropdown.innerHTML = '<div class="url-dropdown-empty">Failed to load processes</div>';
302
+ });
303
+ }
304
+
305
+ function handleModalInputChange(modalInput, modalDropdown) {
306
+ const query = modalInput.value.toLowerCase().trim();
307
+ let filtered = allProcesses;
308
+
309
+ if (modalInput.selectionStart === 0 && modalInput.selectionEnd === modalInput.value.length) {
310
+ filtered = allProcesses;
311
+ } else if (query) {
312
+ filtered = allProcesses.filter(process => {
313
+ const url = `http://${process.ip}`;
314
+ const name = process.name.toLowerCase();
315
+ const urlLower = url.toLowerCase();
316
+ return name.includes(query) || urlLower.includes(query);
317
+ });
318
+ }
319
+
320
+ populateModalDropdown(filtered, modalDropdown);
321
+ }
322
+
323
+ function populateModalDropdown(processes, modalDropdown) {
324
+ const modalInput = modalDropdown.parentElement.querySelector('.url-modal-input');
325
+
326
+ if (processes.length === 0) {
327
+ const query = modalInput.value.toLowerCase().trim();
328
+ const message = query ? `No processes match "${query}"` : 'No running processes found';
329
+ modalDropdown.innerHTML = `<div class="url-dropdown-empty">${message}</div>`;
330
+ return;
331
+ }
332
+
333
+ const items = processes.map(process => {
334
+ const url = `http://${process.ip}`;
335
+ return `
336
+ <div class="url-dropdown-item" data-url="${url}">
337
+ <div class="url-dropdown-name">${escapeHtml(process.name)}</div>
338
+ <div class="url-dropdown-url">${escapeHtml(url)}</div>
339
+ </div>
340
+ `;
341
+ }).join('');
342
+
343
+ modalDropdown.innerHTML = items;
344
+
345
+ modalDropdown.querySelectorAll('.url-dropdown-item').forEach(item => {
346
+ item.addEventListener('click', function() {
347
+ const url = this.getAttribute('data-url');
348
+ modalInput.value = url;
349
+ urlInput.value = url;
350
+ urlInput.closest('form').dispatchEvent(new Event('submit'));
351
+ closeMobileModal();
352
+ });
353
+ });
354
+ }
355
+
356
+ // Set up mobile button click handler
357
+ if (mobileButton) {
358
+ mobileButton.addEventListener('click', showMobileModal);
359
+ }
360
+
361
+ // Public API
362
+ return {
363
+ show: showDropdown,
364
+ hide: hideDropdown,
365
+ showAll: showAllProcesses,
366
+ showMobileModal: showMobileModal,
367
+ closeMobileModal: closeMobileModal,
368
+ refresh: function() {
369
+ allProcesses = []; // Clear cache to force refetch
370
+ if (isDropdownVisible) {
371
+ showDropdown();
372
+ }
373
+ },
374
+ filter: handleInputChange,
375
+ destroy: function() {
376
+ // Remove the focus event listener (need to store reference)
377
+ urlInput.removeEventListener('input', handleInputChange);
378
+ if (mobileButton) {
379
+ mobileButton.removeEventListener('click', showMobileModal);
380
+ }
381
+ hideDropdown();
382
+ closeMobileModal();
383
+ allProcesses = [];
384
+ filteredProcesses = [];
385
+ }
386
+ };
387
+ }
388
+
389
+ // Auto-initialize if DOM is already loaded, otherwise wait for it
390
+ if (document.readyState === 'loading') {
391
+ document.addEventListener('DOMContentLoaded', function() {
392
+ // Will be initialized by individual templates with their specific config
393
+ });
394
+ } else {
395
+ // DOM is already loaded, templates can initialize immediately
396
+ }
@@ -0,0 +1,30 @@
1
+ const WINDOW_ID = (() => {
2
+ // Try to get existing window ID or create a new one
3
+ let id = sessionStorage.getItem('__window_id');
4
+ if (!id) {
5
+ id = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
6
+ try { sessionStorage.setItem('__window_id', id); } catch (_) {}
7
+ }
8
+ return id;
9
+ })();
10
+
11
+ // Window-specific storage wrapper
12
+ window.windowStorage = {
13
+ setItem: (key, value) => {
14
+ try {
15
+ sessionStorage.setItem(`${WINDOW_ID}:${key}`, value);
16
+ } catch (_) {}
17
+ },
18
+ removeItem: (key, value) => {
19
+ try {
20
+ sessionStorage.removeItem(`${WINDOW_ID}:${key}`)
21
+ } catch (_) {}
22
+ },
23
+ getItem: (key) => {
24
+ try {
25
+ return sessionStorage.getItem(`${WINDOW_ID}:${key}`);
26
+ } catch (_) {
27
+ return null;
28
+ }
29
+ }
30
+ };
@@ -1663,13 +1663,30 @@ body.dark .pinokio-git-commit-hash:hover {
1663
1663
  body.minimized {
1664
1664
  flex-direction: row !important;
1665
1665
  }
1666
+ body.minimized aside {
1667
+ display: none;
1668
+ }
1669
+ @media only screen and (max-width: 800px) {
1670
+ .mode-selector .btn2 {
1671
+ width: unset;
1672
+ }
1673
+ .mode-selector .btn2 .caption {
1674
+ display: none;
1675
+ }
1676
+ }
1677
+ /*
1666
1678
  @media only screen and (max-width: 800px) {
1667
1679
  body {
1668
1680
  flex-direction: row !important;
1669
1681
  }
1682
+ aside {
1683
+ display: none;
1684
+ }
1670
1685
  }
1686
+ */
1671
1687
  </style>
1672
1688
  <link href="/app.css" rel="stylesheet"/>
1689
+ <script src="/window_storage.js"></script>
1673
1690
  <script src="/timeago.min.js"></script>
1674
1691
  <script src="/hotkeys.min.js"></script>
1675
1692
  <script src="/sweetalert2.js"></script>
@@ -2003,24 +2020,6 @@ body.minimized {
2003
2020
  document.querySelector("main").appendChild(frame)
2004
2021
  loaded[name] = true
2005
2022
  }
2006
- /*
2007
- if (document.body.classList.contains("minimized")) {
2008
- document.querySelector("#collapse i").className = "fa-solid fa-compress"
2009
- } else {
2010
- document.querySelector("#collapse i").className = "fa-solid fa-expand"
2011
- }
2012
- */
2013
- document.querySelector("#collapse").addEventListener("click", (e) => {
2014
- document.body.classList.toggle("minimized")
2015
- let frame_key = window.frameElement?.name || "";
2016
- if (document.body.classList.contains("minimized")) {
2017
- // document.querySelector("#collapse i").className = "fa-solid fa-compress"
2018
- sessionStorage.setItem(frame_key + ":window_mode", "minimized")
2019
- } else {
2020
- // document.querySelector("#collapse i").className = "fa-solid fa-expand"
2021
- sessionStorage.setItem(frame_key + ":window_mode", "full")
2022
- }
2023
- })
2024
2023
  document.addEventListener("click", (e) => {
2025
2024
  interacted = true
2026
2025
  })
@@ -2256,7 +2255,7 @@ body.minimized {
2256
2255
  <% if (type !== "run") { %>
2257
2256
  let _url = new URL(target.href)
2258
2257
  let frame_key = window.frameElement?.name || "";
2259
- sessionStorage.setItem(frame_key + ":url", _url.pathname + _url.search + _url.hash)
2258
+ windowStorage.setItem(frame_key + ":url", _url.pathname + _url.search + _url.hash)
2260
2259
  <% } %>
2261
2260
 
2262
2261
  // hide all frames
@@ -3223,7 +3222,7 @@ body.minimized {
3223
3222
  refresh_du("logs")
3224
3223
  let frame_key = window.frameElement?.name || "";
3225
3224
 
3226
- let selection_url = sessionStorage.getItem(frame_key + ":url")
3225
+ let selection_url = windowStorage.getItem(frame_key + ":url")
3227
3226
  console.log({ frame_key, selection_url })
3228
3227
  let selection
3229
3228
  if (selection_url) {
@@ -3237,13 +3236,6 @@ body.minimized {
3237
3236
  selection.click()
3238
3237
  // }, 100)
3239
3238
  }
3240
- let window_mode = sessionStorage.getItem(frame_key + ":window_mode")
3241
- console.log({ window_mode })
3242
- if (window_mode) {
3243
- if (window_mode === "minimized") {
3244
- document.body.classList.add("minimized")
3245
- }
3246
- }
3247
3239
  <% if (type !== 'run') { %>
3248
3240
  fetch("<%=repos%>").then((res) => {
3249
3241
  return res.text()
@@ -54,6 +54,7 @@ body.resizing {
54
54
  .gutter:hover::before, body.resizing .gutter::before { background: #9e9e9e; }
55
55
  .gutter:focus { outline: none; box-shadow: inset 0 0 0 2px #90caf9; }
56
56
  </style>
57
+ <script src="/window_storage.js"></script>
57
58
  </head>
58
59
  <body class='<%=theme%>'>
59
60
  <iframe id='col0' data-src="<%=src%>"></iframe>
@@ -103,7 +104,7 @@ body.resizing {
103
104
  const total = computeTotal();
104
105
  if (total > 0) {
105
106
  const ratio = clamp(leftPx / total, 0, 1);
106
- try { sessionStorage.setItem(splitKey, String(ratio)); } catch (_) {}
107
+ windowStorage.setItem(splitKey, String(ratio));
107
108
  }
108
109
  }
109
110
 
@@ -118,7 +119,7 @@ body.resizing {
118
119
  // --- Per-window URL persistence for each pane ---
119
120
  function restorePaneURL(pane, key) {
120
121
  try {
121
- const saved = sessionStorage.getItem(key);
122
+ const saved = windowStorage.getItem(key);
122
123
  const fallback = pane.getAttribute('data-src') || pane.getAttribute('src') || '';
123
124
  const target = (saved && typeof saved === 'string') ? saved : fallback;
124
125
  if (target && pane.src !== target) pane.src = target;
@@ -130,7 +131,7 @@ body.resizing {
130
131
  const cw = pane.contentWindow;
131
132
  if (!cw) return;
132
133
  const notify = () => {
133
- try { sessionStorage.setItem(key, cw.location.href); } catch (_) {}
134
+ windowStorage.setItem(key, cw.location.href);
134
135
  };
135
136
  // Hook SPA navigations
136
137
  const _ps = cw.history.pushState;
@@ -143,7 +144,7 @@ body.resizing {
143
144
  else notify();
144
145
  } catch (err) {
145
146
  // Cross-origin: fall back to saving src only
146
- try { sessionStorage.setItem(key, pane.src); } catch (_) {}
147
+ windowStorage.setItem(key, pane.src);
147
148
  }
148
149
  }
149
150
  function onPaneLoadFactory(pane, key) {
@@ -170,8 +171,10 @@ body.resizing {
170
171
  let overlay = null;
171
172
 
172
173
  function refreshLayout (splitKey) {
173
- let val = sessionStorage.getItem(splitKey)
174
+ console.log("refreshLayout", splitKey)
175
+ let val = windowStorage.getItem(splitKey)
174
176
  let id = splitKey.replace("splitRatio:", "")
177
+ console.log({ id, val })
175
178
  if (val === "1" || val === "0") {
176
179
  if (val === "1") {
177
180
  id_to_hide = id + ".1"
@@ -180,8 +183,28 @@ body.resizing {
180
183
  }
181
184
  const el = document.querySelector(`iframe[name='${id_to_hide}']`)
182
185
  el.remove()
183
- document.body.className = "single"
184
- document.querySelector("#gutter").remove()
186
+ if (document.querySelector("#gutter")) {
187
+ document.querySelector("#gutter").remove()
188
+ }
189
+ let existing_iframe = document.querySelector("iframe")
190
+ console.log("1")
191
+ if (existing_iframe) {
192
+ console.log("2")
193
+ document.body.className = "single"
194
+ } else {
195
+ console.log("3")
196
+ if (window.parent) {
197
+ console.log("4")
198
+ // if all child iframes have been removed, remove self
199
+ window.parent.postMessage({
200
+ e: "close"
201
+ }, "*")
202
+ } else {
203
+ console.log("5")
204
+ // if this is the top window, everything has been removed, so just redirect to home
205
+ location.href = "/"
206
+ }
207
+ }
185
208
  }
186
209
  }
187
210
 
@@ -262,7 +285,7 @@ body.resizing {
262
285
 
263
286
  // Initialize from saved ratio if available and set ARIA
264
287
  try {
265
- const saved = parseFloat(sessionStorage.getItem(splitKey) || '');
288
+ const saved = parseFloat(windowStorage.getItem(splitKey) || '');
266
289
  console.log({ saved })
267
290
  if (!Number.isNaN(saved) && saved > 0 && saved < 1) {
268
291
  console.log("> 1")
@@ -280,7 +303,7 @@ body.resizing {
280
303
  // Re-apply on window resize to keep ratio
281
304
  window.addEventListener('resize', () => {
282
305
  try {
283
- const saved = parseFloat(sessionStorage.getItem(splitKey) || '');
306
+ const saved = parseFloat(windowStorage.getItem(splitKey) || '');
284
307
  if (!Number.isNaN(saved) && saved > 0 && saved < 1) {
285
308
  applyFromRatio(saved);
286
309
  } else {
@@ -299,11 +322,17 @@ body.resizing {
299
322
 
300
323
  let sourceFrameId = null;
301
324
 
302
- if (event.source === col0.contentWindow) {
325
+ if (col0 && event.source === col0.contentWindow) {
303
326
  sourceFrameId = 'col0';
304
- } else if (event.source === col1.contentWindow) {
327
+ } else if (col1 && event.source === col1.contentWindow) {
305
328
  sourceFrameId = 'col1';
306
329
  }
330
+
331
+ if (!sourceFrameId) {
332
+ windowStorage.removeItem(splitKey)
333
+ location.href = "/"
334
+ return
335
+ }
307
336
 
308
337
  console.log('Message received from iframe:', sourceFrameId);
309
338
 
@@ -312,19 +341,11 @@ body.resizing {
312
341
  console.log({ splitKey })
313
342
  for (let iframe of iframes) {
314
343
  if (event.source === iframe.contentWindow) {
315
- // const splitKey = `splitRatio:${iframe.name}`
316
- // console.log({ splitKey })
317
344
  if (iframe.id === "col0") {
318
- // hide col0 => ratio: 0
319
- // col0.src = "about:blank"
320
- // col0.style.display = "none"
321
- try { sessionStorage.setItem(splitKey, "0"); } catch (_) {}
345
+ windowStorage.setItem(splitKey, "0");
322
346
  refreshLayout(splitKey)
323
347
  } else if (iframe.id === "col1") {
324
- // hide col1 => ratio: 1
325
- // col1.src = "about:blank"
326
- // col1.style.display = "none"
327
- try { sessionStorage.setItem(splitKey, "1"); } catch (_) { console.log("<<< ", _ )}
348
+ windowStorage.setItem(splitKey, "1");
328
349
  refreshLayout(splitKey)
329
350
  }
330
351
  break;
@@ -333,6 +354,17 @@ body.resizing {
333
354
  }
334
355
  })
335
356
  })();
357
+ if (document.querySelector("#collapse") && window.windowStorage) {
358
+ document.querySelector("#collapse").addEventListener("click", (e) => {
359
+ document.body.classList.toggle("minimized")
360
+ let frame_key = window.frameElement?.name || "";
361
+ if (document.body.classList.contains("minimized")) {
362
+ windowStorage.setItem(frame_key + ":window_mode", "minimized")
363
+ } else {
364
+ windowStorage.setItem(frame_key + ":window_mode", "full")
365
+ }
366
+ })
367
+ }
336
368
  </script>
337
369
  </body>
338
370
  </html>