Skip to content

Commit 8c8d084

Browse files
authored
fix: Legacy script in JavaScript console not imported to modern console (#2963)
1 parent a552d1f commit 8c8d084

File tree

3 files changed

+229
-55
lines changed

3 files changed

+229
-55
lines changed

src/dashboard/Data/Playground/Playground.react.js

Lines changed: 161 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState, useRef, useEffect, useContext, useCallback, useMemo } from 'react';
22
import ReactJson from 'react-json-view';
33
import Parse from 'parse';
4+
import { useBeforeUnload } from 'react-router-dom';
45

56
import CodeEditor from 'components/CodeEditor/CodeEditor.react';
67
import Toolbar from 'components/Toolbar/Toolbar.react';
@@ -176,11 +177,11 @@ export default function Playground() {
176177
const containerRef = useRef(null);
177178

178179
// Tab management state
180+
const initialTabId = useMemo(() => crypto.randomUUID(), []);
179181
const [tabs, setTabs] = useState([
180-
{ id: 1, name: 'Tab 1', code: DEFAULT_CODE_EDITOR_VALUE }
182+
{ id: initialTabId, name: 'Tab 1', code: DEFAULT_CODE_EDITOR_VALUE }
181183
]);
182-
const [activeTabId, setActiveTabId] = useState(1);
183-
const [nextTabId, setNextTabId] = useState(2);
184+
const [activeTabId, setActiveTabId] = useState(initialTabId);
184185
const [renamingTabId, setRenamingTabId] = useState(null);
185186
const [renamingValue, setRenamingValue] = useState('');
186187
const [savedTabs, setSavedTabs] = useState([]); // All saved tabs including closed ones
@@ -235,8 +236,6 @@ export default function Playground() {
235236

236237
if (tabsToOpen.length > 0) {
237238
setTabs(tabsToOpen);
238-
const maxId = Math.max(...allScripts.map(tab => tab.id));
239-
setNextTabId(maxId + 1);
240239

241240
// Set active tab to the first one
242241
setActiveTabId(tabsToOpen[0].id);
@@ -249,26 +248,24 @@ export default function Playground() {
249248
const firstScript = { ...allScripts[0], order: 0 };
250249
setTabs([firstScript]);
251250
setActiveTabId(firstScript.id);
252-
const maxId = Math.max(...allScripts.map(tab => tab.id));
253-
setNextTabId(maxId + 1);
254251

255252
// Save it as open
256253
await scriptManagerRef.current.openScript(context.applicationId, firstScript.id, 0);
257254

258255
setSavedTabs(allScripts.filter(script => script.saved !== false));
259256
} else {
260257
// Fallback to default tab if no scripts exist
261-
setTabs([{ id: 1, name: 'Tab 1', code: DEFAULT_CODE_EDITOR_VALUE, order: 0 }]);
262-
setActiveTabId(1);
263-
setNextTabId(2);
258+
const defaultTabId = crypto.randomUUID();
259+
setTabs([{ id: defaultTabId, name: 'Tab 1', code: DEFAULT_CODE_EDITOR_VALUE, order: 0 }]);
260+
setActiveTabId(defaultTabId);
264261
}
265262
}
266263
} catch (error) {
267264
console.warn('Failed to load scripts via ScriptManager:', error);
268265
// Fallback to default tab if loading fails
269-
setTabs([{ id: 1, name: 'Tab 1', code: DEFAULT_CODE_EDITOR_VALUE, order: 0 }]);
270-
setActiveTabId(1);
271-
setNextTabId(2);
266+
const defaultTabId = crypto.randomUUID();
267+
setTabs([{ id: defaultTabId, name: 'Tab 1', code: DEFAULT_CODE_EDITOR_VALUE, order: 0 }]);
268+
setActiveTabId(defaultTabId);
272269
}
273270

274271
// Load other data from localStorage
@@ -317,18 +314,19 @@ export default function Playground() {
317314

318315
// Tab management functions
319316
const createNewTab = useCallback(() => {
317+
const newTabId = crypto.randomUUID();
318+
const tabCount = tabs.length + 1;
320319
const newTab = {
321-
id: nextTabId,
322-
name: `Tab ${nextTabId}`,
320+
id: newTabId,
321+
name: `Tab ${tabCount}`,
323322
code: '', // Start with empty code instead of default value
324323
saved: false, // Mark as unsaved initially
325324
order: tabs.length // Assign order as the last position
326325
};
327326
const updatedTabs = [...tabs, newTab];
328327
setTabs(updatedTabs);
329-
setActiveTabId(nextTabId);
330-
setNextTabId(nextTabId + 1);
331-
}, [tabs, nextTabId]);
328+
setActiveTabId(newTabId);
329+
}, [tabs]);
332330

333331
const closeTab = useCallback(async (tabId) => {
334332
if (tabs.length <= 1) {
@@ -591,11 +589,6 @@ export default function Playground() {
591589
setTabs(updatedTabs);
592590
setActiveTabId(savedTab.id);
593591

594-
// Update nextTabId if necessary
595-
if (savedTab.id >= nextTabId) {
596-
setNextTabId(savedTab.id + 1);
597-
}
598-
599592
// Save the open state through ScriptManager
600593
if (scriptManagerRef.current && context?.applicationId) {
601594
try {
@@ -604,7 +597,151 @@ export default function Playground() {
604597
console.error('Failed to open script:', error);
605598
}
606599
}
607-
}, [tabs, nextTabId, switchTab, context?.applicationId]);
600+
}, [tabs, switchTab, context?.applicationId]);
601+
602+
// Navigation confirmation for unsaved changes
603+
useBeforeUnload(
604+
useCallback(
605+
(event) => {
606+
// Check for unsaved changes across all tabs
607+
let hasChanges = false;
608+
609+
for (const tab of tabs) {
610+
// Check if tab is marked as unsaved (like legacy scripts)
611+
if (tab.saved === false) {
612+
hasChanges = true;
613+
break;
614+
}
615+
616+
// Get current content for the tab
617+
let currentContent = '';
618+
if (tab.id === activeTabId && editorRef.current) {
619+
// For active tab, get content from editor
620+
currentContent = editorRef.current.value;
621+
} else {
622+
// For inactive tabs, use stored code
623+
currentContent = tab.code;
624+
}
625+
626+
// Find the saved version of this tab
627+
const savedTab = savedTabs.find(saved => saved.id === tab.id);
628+
629+
if (!savedTab) {
630+
// If tab was never saved, it has unsaved changes if it has any content
631+
if (currentContent.trim() !== '') {
632+
hasChanges = true;
633+
break;
634+
}
635+
} else {
636+
// Compare current content with saved content
637+
if (currentContent !== savedTab.code) {
638+
hasChanges = true;
639+
break;
640+
}
641+
}
642+
}
643+
644+
if (hasChanges) {
645+
const message = 'You have unsaved changes in your playground tabs. Are you sure you want to leave?';
646+
event.preventDefault();
647+
event.returnValue = message;
648+
return message;
649+
}
650+
},
651+
[tabs, activeTabId, savedTabs]
652+
)
653+
);
654+
655+
// Handle navigation confirmation for internal route changes
656+
useEffect(() => {
657+
const checkForUnsavedChanges = () => {
658+
// Check for unsaved changes across all tabs
659+
for (const tab of tabs) {
660+
// Check if tab is marked as unsaved (like legacy scripts)
661+
if (tab.saved === false) {
662+
return true;
663+
}
664+
665+
// Get current content for the tab
666+
let currentContent = '';
667+
if (tab.id === activeTabId && editorRef.current) {
668+
// For active tab, get content from editor
669+
currentContent = editorRef.current.value;
670+
} else {
671+
// For inactive tabs, use stored code
672+
currentContent = tab.code;
673+
}
674+
675+
// Find the saved version of this tab
676+
const savedTab = savedTabs.find(saved => saved.id === tab.id);
677+
678+
if (!savedTab) {
679+
// If tab was never saved, it has unsaved changes if it has any content
680+
if (currentContent.trim() !== '') {
681+
return true;
682+
}
683+
} else {
684+
// Compare current content with saved content
685+
if (currentContent !== savedTab.code) {
686+
return true;
687+
}
688+
}
689+
}
690+
return false;
691+
};
692+
693+
const handleLinkClick = (event) => {
694+
if (event.defaultPrevented) {
695+
return;
696+
}
697+
if (event.button !== 0) {
698+
return;
699+
}
700+
if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
701+
return;
702+
}
703+
704+
const anchor = event.target.closest('a[href]');
705+
if (!anchor || anchor.target === '_blank') {
706+
return;
707+
}
708+
709+
const href = anchor.getAttribute('href');
710+
if (!href || href === '#') {
711+
return;
712+
}
713+
714+
// Check if it's an internal navigation (starts with / or #)
715+
if (href.startsWith('/') || href.startsWith('#')) {
716+
if (checkForUnsavedChanges()) {
717+
const message = 'You have unsaved changes in your playground tabs. Are you sure you want to leave?';
718+
if (!window.confirm(message)) {
719+
event.preventDefault();
720+
event.stopPropagation();
721+
}
722+
}
723+
}
724+
};
725+
726+
const handlePopState = () => {
727+
if (checkForUnsavedChanges()) {
728+
const message = 'You have unsaved changes in your playground tabs. Are you sure you want to leave?';
729+
if (!window.confirm(message)) {
730+
window.history.go(1);
731+
}
732+
}
733+
};
734+
735+
// Add event listeners
736+
document.addEventListener('click', handleLinkClick, true);
737+
window.addEventListener('popstate', handlePopState);
738+
739+
// Cleanup event listeners
740+
return () => {
741+
document.removeEventListener('click', handleLinkClick, true);
742+
window.removeEventListener('popstate', handlePopState);
743+
};
744+
}, [tabs, activeTabId, savedTabs]);
608745

609746
// Focus input when starting to rename
610747
useEffect(() => {

src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Notification from 'dashboard/Data/Browser/Notification.react';
1818
import * as ColumnPreferences from 'lib/ColumnPreferences';
1919
import * as ClassPreferences from 'lib/ClassPreferences';
2020
import ViewPreferencesManager from 'lib/ViewPreferencesManager';
21+
import ScriptManager from 'lib/ScriptManager';
2122
import bcrypt from 'bcryptjs';
2223
import * as OTPAuth from 'otpauth';
2324
import QRCode from 'qrcode';
@@ -28,6 +29,7 @@ export default class DashboardSettings extends DashboardView {
2829
this.section = 'App Settings';
2930
this.subsection = 'Dashboard Configuration';
3031
this.viewPreferencesManager = null;
32+
this.scriptManager = null;
3133

3234
this.state = {
3335
createUserInput: false,
@@ -57,12 +59,13 @@ export default class DashboardSettings extends DashboardView {
5759
}
5860

5961
componentDidMount() {
60-
this.initializeViewPreferencesManager();
62+
this.initializeManagers();
6163
}
6264

63-
initializeViewPreferencesManager() {
65+
initializeManagers() {
6466
if (this.context) {
6567
this.viewPreferencesManager = new ViewPreferencesManager(this.context);
68+
this.scriptManager = new ScriptManager(this.context);
6669
this.loadStoragePreference();
6770
}
6871
}
@@ -123,11 +126,18 @@ export default class DashboardSettings extends DashboardView {
123126
return;
124127
}
125128

126-
const success = this.viewPreferencesManager.deleteFromBrowser(this.context.applicationId);
127-
if (success) {
128-
this.showNote('Successfully deleted views from browser storage.');
129+
if (!this.scriptManager) {
130+
this.showNote('ScriptManager not initialized');
131+
return;
132+
}
133+
134+
const viewsSuccess = this.viewPreferencesManager.deleteFromBrowser(this.context.applicationId);
135+
const scriptsSuccess = this.scriptManager.deleteFromBrowser(this.context.applicationId);
136+
137+
if (viewsSuccess && scriptsSuccess) {
138+
this.showNote('Successfully deleted dashboard settings from browser storage.');
129139
} else {
130-
this.showNote('Failed to delete views from browser storage.');
140+
this.showNote('Failed to delete all dashboard settings from browser storage.');
131141
}
132142
}
133143

@@ -461,13 +471,16 @@ export default class DashboardSettings extends DashboardView {
461471
}
462472
/>
463473
</Fieldset>
464-
{this.viewPreferencesManager && this.viewPreferencesManager.isServerConfigEnabled() && (
474+
{this.viewPreferencesManager && this.scriptManager && this.viewPreferencesManager.isServerConfigEnabled() && (
465475
<Fieldset legend="Settings Storage">
476+
<div style={{ marginBottom: '20px', color: '#666', fontSize: '14px', textAlign: 'center' }}>
477+
Storing dashboard settings on the server rather than locally in the browser storage makes the settings available across devices and browsers. It also prevents them from getting lost when resetting the browser website data. Settings that can be stored on the server are currently Views and JS Console scripts.
478+
</div>
466479
<Field
467480
label={
468481
<Label
469482
text="Storage Location"
470-
description="Choose where your dashboard settings are stored and loaded from. Server storage allows sharing settings across devices and users, while Browser storage is local to this device."
483+
description="Choose where your dashboard settings are stored and loaded from."
471484
/>
472485
}
473486
input={
@@ -487,7 +500,7 @@ export default class DashboardSettings extends DashboardView {
487500
label={
488501
<Label
489502
text="Migrate Settings to Server"
490-
description="Migrates your current browser-stored dashboard settings to the server. This does not change your storage preference - use the switch above to select the server as storage location after migration. ⚠️ This overwrites existing server settings."
503+
description="Migrates browser-stored settings to the server. ⚠️ This overwrites existing dashboard settings on the server."
491504
/>
492505
}
493506
input={
@@ -503,7 +516,7 @@ export default class DashboardSettings extends DashboardView {
503516
label={
504517
<Label
505518
text="Delete Settings from Browser"
506-
description="Removes your dashboard settings from the browser's local storage. This action is irreversible. Make sure to migrate your settings to server and test them first."
519+
description="Removes settings from browser storage. ⚠️ Migrate your settings to the server and test them first."
507520
/>
508521
}
509522
input={

0 commit comments

Comments
 (0)