Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions tests/spec/features/reset_configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'json'

require 'spec_helper'
require 'support/editor'
require 'support/playground_actions'

RSpec.feature "Resetting the configuration to defaults", type: :feature, js: true do
include PlaygroundActions

describe "after not visiting in a while" do
before do
visit "/"
editor.set(code)

sleep(0.002)
visit "/?#{config_overrides}"
end

scenario "the default values are restored" do
within(:notification) { click_on 'Reset all code and configuration' }

expect(editor).to_not have_line(code)
expect(editor).to have_line(some_default_code)
end

scenario "the current values are kept" do
within(:notification) { click_on 'Keep the current code and configuration' }

expect(editor).to have_line(code)
expect(editor).to_not have_line(some_default_code)
end

def config_overrides
config = {
oldConfigurationThresholdS: 0.001,
}

"whte_rbt.obj=#{config.to_json}"
end
end

describe "manually" do
before do
visit "/"
editor.set(code)
end

scenario "the default values are restored" do
in_config_menu { click_on 'Reset all code and configuration to default values' }
within(:notification) { click_on 'Reset all code and configuration' }

expect(editor).to_not have_line(code)
expect(editor).to have_line(some_default_code)
end

scenario "the current values are kept" do
in_config_menu { click_on 'Reset all code and configuration to default values' }
within(:notification) { click_on 'Keep the current code and configuration' }

expect(editor).to have_line(code)
expect(editor).to_not have_line(some_default_code)
end
end

def editor
Editor.new(page)
end

def code
'This is my old code'
end

def some_default_code
'Hello, world!'
end
end
1 change: 1 addition & 0 deletions ui/frontend/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ node_modules
!Output/WarnAboutNoSymbols.tsx
!PopButton.tsx
!Prism.tsx
!SimpleButtonMenuItem.tsx
!Stdin.tsx
!actions.ts
!api.ts
Expand Down
8 changes: 0 additions & 8 deletions ui/frontend/ButtonMenuItem.module.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
.container {
composes: -menuItemFullButton from './shared.module.css';

&:hover {
color: var(--header-tint);
}
}

.name {
composes: -menuItemTitle from './shared.module.css';
margin: 0;
Expand Down
14 changes: 7 additions & 7 deletions ui/frontend/ButtonMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { type JSX } from 'react';

import MenuItem from './MenuItem';
import SimpleButtonMenuItem from './SimpleButtonMenuItem';

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

Expand All @@ -12,12 +12,12 @@ interface ButtonMenuItemProps extends Button {
}

const ButtonMenuItem: React.FC<ButtonMenuItemProps> = ({ name, children, ...props }) => (
<MenuItem>
<button className={styles.container} {...props}>
<div className={styles.name} data-test-id="button-menu-item__name">{name}</div>
<div className={styles.description}>{children}</div>
</button>
</MenuItem>
<SimpleButtonMenuItem {...props}>
<div className={styles.name} data-test-id="button-menu-item__name">
{name}
</div>
<div className={styles.description}>{children}</div>
</SimpleButtonMenuItem>
);

export default ButtonMenuItem;
10 changes: 9 additions & 1 deletion ui/frontend/ConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import React, { Fragment, useCallback } from 'react';

import { Either as EitherConfig, Select as SelectConfig } from './ConfigElement';
import MenuGroup from './MenuGroup';
import SimpleButtonMenuItem from './SimpleButtonMenuItem';
import { useAppDispatch, useAppSelector } from './hooks';

import * as client from './reducers/client';
import * as config from './reducers/configuration';
import {
AssemblyFlavor,
Expand Down Expand Up @@ -48,6 +49,7 @@ const ConfigMenu: React.FC = () => {
useCallback((p: ProcessAssembly) => dispatch(config.changeProcessAssembly(p)), [dispatch]);
const changeDemangleAssembly =
useCallback((d: DemangleAssembly) => dispatch(config.changeDemangleAssembly(d)), [dispatch]);
const showConfigReset = useCallback(() => dispatch(client.showConfigReset()), [dispatch]);

return (
<Fragment>
Expand Down Expand Up @@ -151,6 +153,12 @@ const ConfigMenu: React.FC = () => {
onChange={changeProcessAssembly}
/>
</MenuGroup>

<MenuGroup title="Reset">
<SimpleButtonMenuItem onClick={showConfigReset}>
Reset all code and configuration to default values
</SimpleButtonMenuItem>
</MenuGroup>
</Fragment>
);
};
Expand Down
51 changes: 51 additions & 0 deletions ui/frontend/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Portal } from 'react-portal';

import { Close } from './Icon';
import { useAppDispatch, useAppSelector } from './hooks';
import * as client from './reducers/client';
import { seenRustSurvey2025 } from './reducers/notifications';
import { allowLongRun, wsExecuteKillCurrent } from './reducers/output/execute';
import * as selectors from './selectors';
Expand All @@ -17,6 +18,8 @@ const Notifications: React.FC = () => {
<div className={styles.container}>
<RustSurvey2025Notification />
<ExcessiveExecutionNotification />
<ResetConfigurationNotification />
<ResetOldConfigurationNotification />
</div>
</Portal>
);
Expand Down Expand Up @@ -61,6 +64,54 @@ const ExcessiveExecutionNotification: React.FC = () => {
) : null;
};

interface ResetNotificationCommonProps {
preamble?: string;
onReset: () => void;
onCancel: () => void;
}

const ResetNotificationCommon: React.FC<ResetNotificationCommonProps> = ({
preamble,
onReset,
onCancel,
}) => (
<Notification onClose={onReset}>
{preamble}
Would you like to reset all code and configuration back to the default values to get a fresh
start?
<div className={styles.action}>
<button onClick={onReset}>Reset all code and configuration</button>
<button onClick={onCancel}>Keep the current code and configuration</button>
</div>
</Notification>
);

const ResetConfigurationNotification: React.FC = () => {
const showResetConfiguration = useAppSelector(selectors.resetConfigurationSelector);

const dispatch = useAppDispatch();
const reset = useCallback(() => dispatch(client.resetEverything()), [dispatch]);
const keep = useCallback(() => dispatch(client.hideConfigReset()), [dispatch]);

return showResetConfiguration ? (
<ResetNotificationCommon onReset={reset} onCancel={keep} />
) : null;
};

const ResetOldConfigurationNotification: React.FC = () => {
const showResetOldConfiguration = useAppSelector(selectors.resetOldConfigurationSelector);

const dispatch = useAppDispatch();
const reset = useCallback(() => dispatch(client.resetEverything()), [dispatch]);
const keep = useCallback(() => dispatch(client.updateLastVisitedAt()), [dispatch]);

const preamble = "It's been a while since you've used the Playground. ";

return showResetOldConfiguration ? (
<ResetNotificationCommon preamble={preamble} onReset={reset} onCancel={keep} />
) : null;
};

interface NotificationProps {
children: React.ReactNode;
onClose: () => void;
Expand Down
7 changes: 7 additions & 0 deletions ui/frontend/SimpleButtonMenuItem.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.container {
composes: -menuItemFullButton from './shared.module.css';

&:hover {
color: var(--header-tint);
}
}
25 changes: 25 additions & 0 deletions ui/frontend/SimpleButtonMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { type JSX } from 'react';

import MenuItem from './MenuItem';

import * as styles from './SimpleButtonMenuItem.module.css';

type Button = JSX.IntrinsicElements['button'];

interface SimpleButtonMenuItemProps extends Button {
children: React.ReactNode;
}

const SimpleButtonMenuItem: React.FC<SimpleButtonMenuItemProps> = ({
name,
children,
...props
}) => (
<MenuItem>
<button className={styles.container} {...props}>
{children}
</button>
</MenuItem>
);

export default SimpleButtonMenuItem;
9 changes: 9 additions & 0 deletions ui/frontend/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ export default function configureStore(window: Window) {
localStorage.saveChanges(state);
sessionStorage.saveChanges(state);
}

if (state.client.resetEverything) {
localStorage.clear();
sessionStorage.clear();

// This removes any query parameters and triggers all the
// initialization
window.location = state.globalConfiguration.baseUrl;
}
});

return store;
Expand Down
6 changes: 4 additions & 2 deletions ui/frontend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { configureRustErrors } from './highlighting';
import PageSwitcher from './PageSwitcher';
import playgroundApp from './reducers';
import { clientSetIdentifiers } from './reducers/client';
import * as client from './reducers/client';
import { featureFlagsForceDisableAll, featureFlagsForceEnableAll } from './reducers/featureFlags';
import { disableSyncChangesToStorage, override } from './reducers/globalConfiguration';
import Router from './Router';
Expand All @@ -31,6 +31,8 @@ import { Theme } from './types';

const store = configureStore(window);

store.dispatch(client.updateLastVisitedAt());

if (store.getState().client.id === '') {
const { crypto } = window;

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

store.dispatch(clientSetIdentifiers({ id, featureFlagThreshold }));
store.dispatch(client.setIdentifiers({ id, featureFlagThreshold }));
}

const params = new URLSearchParams(window.location.search);
Expand Down
7 changes: 6 additions & 1 deletion ui/frontend/local_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ interface V2Configuration {
client: {
id: string;
featureFlagThreshold: number;
},
visitedAt?: string;
};
configuration: {
editor: Editor;
ace: {
Expand Down Expand Up @@ -60,6 +61,7 @@ export function serialize(state: State): string {
client: {
id: state.client.id,
featureFlagThreshold: state.client.featureFlagThreshold,
visitedAt: state.client.visitedAt,
},
configuration: {
editor: state.configuration.editor,
Expand All @@ -83,13 +85,16 @@ export function serialize(state: State): string {
return JSON.stringify(conf);
}

const MIGRATION_TIMESTAMP = '2021-08-21T21:51:05.000Z';

function migrateV1(state: V1Configuration): CurrentConfiguration {
const { editor, theme, keybinding, pairCharacters, ...configuration } = state.configuration;
const step: V2Configuration = {
...state,
client: {
id: '',
featureFlagThreshold: 0,
visitedAt: MIGRATION_TIMESTAMP,
},
configuration: {
...configuration,
Expand Down
33 changes: 32 additions & 1 deletion ui/frontend/reducers/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';

const NOW_TIMESTAMP = new Date().toISOString();

interface State {
id: string;
featureFlagThreshold: number;
lastVisitedAt?: string;
visitedAt?: string;
showConfigReset: boolean;
resetEverything: boolean;
}

const initialState: State = {
id: '',
featureFlagThreshold: 1.0,
showConfigReset: false,
resetEverything: false,
};

const slice = createSlice({
Expand All @@ -21,9 +29,32 @@ const slice = createSlice({
state.id = action.payload.id;
state.featureFlagThreshold = action.payload.featureFlagThreshold;
},

updateLastVisitedAt: (state) => {
state.lastVisitedAt = state.visitedAt;
state.visitedAt = NOW_TIMESTAMP;
},

showConfigReset: (state) => {
state.showConfigReset = true;
},

hideConfigReset: (state) => {
state.showConfigReset = false;
},

resetEverything: (state) => {
state.resetEverything = true;
},
},
});

export const { setIdentifiers: clientSetIdentifiers } = slice.actions;
export const {
setIdentifiers,
updateLastVisitedAt,
showConfigReset,
hideConfigReset,
resetEverything,
} = slice.actions;

export default slice.reducer;
Loading
Loading