Skip to content

Commit 8a89600

Browse files
authored
Merge pull request #1209 from rust-lang/reset-after-a-while
2 parents e07bc9d + 42159bf commit 8a89600

15 files changed

Lines changed: 287 additions & 24 deletions
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
require 'json'
2+
3+
require 'spec_helper'
4+
require 'support/editor'
5+
require 'support/playground_actions'
6+
7+
RSpec.feature "Resetting the configuration to defaults", type: :feature, js: true do
8+
include PlaygroundActions
9+
10+
describe "after not visiting in a while" do
11+
before do
12+
visit "/"
13+
editor.set(code)
14+
15+
sleep(0.002)
16+
visit "/?#{config_overrides}"
17+
end
18+
19+
scenario "the default values are restored" do
20+
within(:notification) { click_on 'Reset all code and configuration' }
21+
22+
expect(editor).to_not have_line(code)
23+
expect(editor).to have_line(some_default_code)
24+
end
25+
26+
scenario "the current values are kept" do
27+
within(:notification) { click_on 'Keep the current code and configuration' }
28+
29+
expect(editor).to have_line(code)
30+
expect(editor).to_not have_line(some_default_code)
31+
end
32+
33+
def config_overrides
34+
config = {
35+
oldConfigurationThresholdS: 0.001,
36+
}
37+
38+
"whte_rbt.obj=#{config.to_json}"
39+
end
40+
end
41+
42+
describe "manually" do
43+
before do
44+
visit "/"
45+
editor.set(code)
46+
end
47+
48+
scenario "the default values are restored" do
49+
in_config_menu { click_on 'Reset all code and configuration to default values' }
50+
within(:notification) { click_on 'Reset all code and configuration' }
51+
52+
expect(editor).to_not have_line(code)
53+
expect(editor).to have_line(some_default_code)
54+
end
55+
56+
scenario "the current values are kept" do
57+
in_config_menu { click_on 'Reset all code and configuration to default values' }
58+
within(:notification) { click_on 'Keep the current code and configuration' }
59+
60+
expect(editor).to have_line(code)
61+
expect(editor).to_not have_line(some_default_code)
62+
end
63+
end
64+
65+
def editor
66+
Editor.new(page)
67+
end
68+
69+
def code
70+
'This is my old code'
71+
end
72+
73+
def some_default_code
74+
'Hello, world!'
75+
end
76+
end

ui/frontend/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ node_modules
2828
!Output/WarnAboutNoSymbols.tsx
2929
!PopButton.tsx
3030
!Prism.tsx
31+
!SimpleButtonMenuItem.tsx
3132
!Stdin.tsx
3233
!actions.ts
3334
!api.ts

ui/frontend/ButtonMenuItem.module.css

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
.container {
2-
composes: -menuItemFullButton from './shared.module.css';
3-
4-
&:hover {
5-
color: var(--header-tint);
6-
}
7-
}
8-
91
.name {
102
composes: -menuItemTitle from './shared.module.css';
113
margin: 0;

ui/frontend/ButtonMenuItem.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { type JSX } from 'react';
22

3-
import MenuItem from './MenuItem';
3+
import SimpleButtonMenuItem from './SimpleButtonMenuItem';
44

55
import * as styles from './ButtonMenuItem.module.css';
66

@@ -12,12 +12,12 @@ interface ButtonMenuItemProps extends Button {
1212
}
1313

1414
const ButtonMenuItem: React.FC<ButtonMenuItemProps> = ({ name, children, ...props }) => (
15-
<MenuItem>
16-
<button className={styles.container} {...props}>
17-
<div className={styles.name} data-test-id="button-menu-item__name">{name}</div>
18-
<div className={styles.description}>{children}</div>
19-
</button>
20-
</MenuItem>
15+
<SimpleButtonMenuItem {...props}>
16+
<div className={styles.name} data-test-id="button-menu-item__name">
17+
{name}
18+
</div>
19+
<div className={styles.description}>{children}</div>
20+
</SimpleButtonMenuItem>
2121
);
2222

2323
export default ButtonMenuItem;

ui/frontend/ConfigMenu.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import React, { Fragment, useCallback } from 'react';
44

55
import { Either as EitherConfig, Select as SelectConfig } from './ConfigElement';
66
import MenuGroup from './MenuGroup';
7+
import SimpleButtonMenuItem from './SimpleButtonMenuItem';
78
import { useAppDispatch, useAppSelector } from './hooks';
8-
9+
import * as client from './reducers/client';
910
import * as config from './reducers/configuration';
1011
import {
1112
AssemblyFlavor,
@@ -48,6 +49,7 @@ const ConfigMenu: React.FC = () => {
4849
useCallback((p: ProcessAssembly) => dispatch(config.changeProcessAssembly(p)), [dispatch]);
4950
const changeDemangleAssembly =
5051
useCallback((d: DemangleAssembly) => dispatch(config.changeDemangleAssembly(d)), [dispatch]);
52+
const showConfigReset = useCallback(() => dispatch(client.showConfigReset()), [dispatch]);
5153

5254
return (
5355
<Fragment>
@@ -151,6 +153,12 @@ const ConfigMenu: React.FC = () => {
151153
onChange={changeProcessAssembly}
152154
/>
153155
</MenuGroup>
156+
157+
<MenuGroup title="Reset">
158+
<SimpleButtonMenuItem onClick={showConfigReset}>
159+
Reset all code and configuration to default values
160+
</SimpleButtonMenuItem>
161+
</MenuGroup>
154162
</Fragment>
155163
);
156164
};

ui/frontend/Notifications.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Portal } from 'react-portal';
33

44
import { Close } from './Icon';
55
import { useAppDispatch, useAppSelector } from './hooks';
6+
import * as client from './reducers/client';
67
import { seenRustSurvey2025 } from './reducers/notifications';
78
import { allowLongRun, wsExecuteKillCurrent } from './reducers/output/execute';
89
import * as selectors from './selectors';
@@ -17,6 +18,8 @@ const Notifications: React.FC = () => {
1718
<div className={styles.container}>
1819
<RustSurvey2025Notification />
1920
<ExcessiveExecutionNotification />
21+
<ResetConfigurationNotification />
22+
<ResetOldConfigurationNotification />
2023
</div>
2124
</Portal>
2225
);
@@ -61,6 +64,54 @@ const ExcessiveExecutionNotification: React.FC = () => {
6164
) : null;
6265
};
6366

67+
interface ResetNotificationCommonProps {
68+
preamble?: string;
69+
onReset: () => void;
70+
onCancel: () => void;
71+
}
72+
73+
const ResetNotificationCommon: React.FC<ResetNotificationCommonProps> = ({
74+
preamble,
75+
onReset,
76+
onCancel,
77+
}) => (
78+
<Notification onClose={onReset}>
79+
{preamble}
80+
Would you like to reset all code and configuration back to the default values to get a fresh
81+
start?
82+
<div className={styles.action}>
83+
<button onClick={onReset}>Reset all code and configuration</button>
84+
<button onClick={onCancel}>Keep the current code and configuration</button>
85+
</div>
86+
</Notification>
87+
);
88+
89+
const ResetConfigurationNotification: React.FC = () => {
90+
const showResetConfiguration = useAppSelector(selectors.resetConfigurationSelector);
91+
92+
const dispatch = useAppDispatch();
93+
const reset = useCallback(() => dispatch(client.resetEverything()), [dispatch]);
94+
const keep = useCallback(() => dispatch(client.hideConfigReset()), [dispatch]);
95+
96+
return showResetConfiguration ? (
97+
<ResetNotificationCommon onReset={reset} onCancel={keep} />
98+
) : null;
99+
};
100+
101+
const ResetOldConfigurationNotification: React.FC = () => {
102+
const showResetOldConfiguration = useAppSelector(selectors.resetOldConfigurationSelector);
103+
104+
const dispatch = useAppDispatch();
105+
const reset = useCallback(() => dispatch(client.resetEverything()), [dispatch]);
106+
const keep = useCallback(() => dispatch(client.updateLastVisitedAt()), [dispatch]);
107+
108+
const preamble = "It's been a while since you've used the Playground. ";
109+
110+
return showResetOldConfiguration ? (
111+
<ResetNotificationCommon preamble={preamble} onReset={reset} onCancel={keep} />
112+
) : null;
113+
};
114+
64115
interface NotificationProps {
65116
children: React.ReactNode;
66117
onClose: () => void;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.container {
2+
composes: -menuItemFullButton from './shared.module.css';
3+
4+
&:hover {
5+
color: var(--header-tint);
6+
}
7+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, { type JSX } from 'react';
2+
3+
import MenuItem from './MenuItem';
4+
5+
import * as styles from './SimpleButtonMenuItem.module.css';
6+
7+
type Button = JSX.IntrinsicElements['button'];
8+
9+
interface SimpleButtonMenuItemProps extends Button {
10+
children: React.ReactNode;
11+
}
12+
13+
const SimpleButtonMenuItem: React.FC<SimpleButtonMenuItemProps> = ({
14+
name,
15+
children,
16+
...props
17+
}) => (
18+
<MenuItem>
19+
<button className={styles.container} {...props}>
20+
{children}
21+
</button>
22+
</MenuItem>
23+
);
24+
25+
export default SimpleButtonMenuItem;

ui/frontend/configureStore.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ export default function configureStore(window: Window) {
6767
localStorage.saveChanges(state);
6868
sessionStorage.saveChanges(state);
6969
}
70+
71+
if (state.client.resetEverything) {
72+
localStorage.clear();
73+
sessionStorage.clear();
74+
75+
// This removes any query parameters and triggers all the
76+
// initialization
77+
window.location = state.globalConfiguration.baseUrl;
78+
}
7079
});
7180

7281
return store;

ui/frontend/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import { configureRustErrors } from './highlighting';
1515
import PageSwitcher from './PageSwitcher';
1616
import playgroundApp from './reducers';
17-
import { clientSetIdentifiers } from './reducers/client';
17+
import * as client from './reducers/client';
1818
import { featureFlagsForceDisableAll, featureFlagsForceEnableAll } from './reducers/featureFlags';
1919
import { disableSyncChangesToStorage, override } from './reducers/globalConfiguration';
2020
import Router from './Router';
@@ -31,6 +31,8 @@ import { Theme } from './types';
3131

3232
const store = configureStore(window);
3333

34+
store.dispatch(client.updateLastVisitedAt());
35+
3436
if (store.getState().client.id === '') {
3537
const { crypto } = window;
3638

@@ -40,7 +42,7 @@ if (store.getState().client.id === '') {
4042
crypto.getRandomValues(rawValue);
4143
const featureFlagThreshold = rawValue[0] / 0xFFFF_FFFF;
4244

43-
store.dispatch(clientSetIdentifiers({ id, featureFlagThreshold }));
45+
store.dispatch(client.setIdentifiers({ id, featureFlagThreshold }));
4446
}
4547

4648
const params = new URLSearchParams(window.location.search);

0 commit comments

Comments
 (0)