Skip to content

Commit

Permalink
Merge pull request #1091 from rust-lang/dark-mode
Browse files Browse the repository at this point in the history
Enable dark mode
  • Loading branch information
shepmaster authored Sep 26, 2024
2 parents 748fd36 + ed15731 commit fb55dc1
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 28 deletions.
6 changes: 5 additions & 1 deletion tests/spec/features/assistance_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
require 'spec_helper'
require 'support/editor'
require 'support/notifications'
require 'support/playground_actions'

RSpec.feature "Editor assistance for common code modifications", type: :feature, js: true do
include PlaygroundActions

before { visit '/' }
before do
visit '/'
Notifications.new(page).close_all
end

scenario "building code without a main method offers adding one" do
editor.set <<~EOF
Expand Down
14 changes: 14 additions & 0 deletions tests/spec/support/notifications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Notifications
attr_reader :page
def initialize(page)
@page = page
end

def close_all
page.all(:notification).each do |notification|
page.within(notification) do
page.click_on('dismiss notification')
end
end
end
end
16 changes: 6 additions & 10 deletions ui/frontend/ConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
ProcessAssembly,
Theme,
} from './types';
import { showThemeSelector } from './selectors';

const MONACO_THEMES = [
'vs', 'vs-dark', 'vscode-dark-plus',
Expand All @@ -34,8 +33,6 @@ const ConfigMenu: React.FC = () => {
const demangleAssembly = useAppSelector((state) => state.configuration.demangleAssembly);
const processAssembly = useAppSelector((state) => state.configuration.processAssembly);

const showTheme = useAppSelector(showThemeSelector);

const dispatch = useAppDispatch();
const changeAceTheme = useCallback((t: string) => dispatch(config.changeAceTheme(t)), [dispatch]);
const changeMonacoTheme = useCallback((t: string) => dispatch(config.changeMonacoTheme(t)), [dispatch]);
Expand Down Expand Up @@ -104,13 +101,12 @@ const ConfigMenu: React.FC = () => {
</MenuGroup>

<MenuGroup title="UI">
{showTheme && (
<SelectConfig name="Theme" value={theme} onChange={changeTheme}>
{ /* <option value={Theme.System}>System</option> */ }
<option value={Theme.Light}>Light</option>
<option value={Theme.Dark}>Dark</option>
</SelectConfig>
)}
<SelectConfig name="Theme" value={theme} onChange={changeTheme}>
<option value={Theme.System}>System</option>
<option value={Theme.Light}>Light</option>
<option value={Theme.Dark}>Dark</option>
</SelectConfig>

<SelectConfig
name="Orientation"
value={orientation}
Expand Down
5 changes: 5 additions & 0 deletions ui/frontend/Notifications.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ $space: 0.25em;

.notificationContent {
padding: $space 0 $space $space;
width: 100%;
}

.close {
Expand All @@ -34,3 +35,7 @@ $space: 0.25em;
padding-top: 0.5em;
gap: 0.5em;
}

.swapTheme {
width: 100%;
}
56 changes: 54 additions & 2 deletions ui/frontend/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { Portal } from 'react-portal';

import { Close } from './Icon';
import { useAppDispatch, useAppSelector } from './hooks';
import { seenRustSurvey2023 } from './reducers/notifications';
import { swapTheme } from './reducers/configuration';
import { seenDarkMode, seenRustSurvey2023 } from './reducers/notifications';
import { allowLongRun, wsExecuteKillCurrent } from './reducers/output/execute';
import * as selectors from './selectors';
import { Theme } from './types';

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

Expand All @@ -15,13 +17,63 @@ const Notifications: React.FC = () => {
return (
<Portal>
<div className={styles.container}>
<DarkModeNotification />
<RustSurvey2023Notification />
<ExcessiveExecutionNotification />
</div>
</Portal>
);
};

const DarkModeNotification: React.FC = () => {
const showIt = useAppSelector(selectors.showDarkModeSelector);

const dispatch = useAppDispatch();
const seenIt = useCallback(() => dispatch(seenDarkMode()), [dispatch]);
const swapToLight = useCallback(() => dispatch(swapTheme(Theme.Light)), [dispatch]);
const swapToDark = useCallback(() => dispatch(swapTheme(Theme.Dark)), [dispatch]);
const swapToSystem = useCallback(() => dispatch(swapTheme(Theme.System)), [dispatch]);

return showIt ? (
<Notification onClose={seenIt}>
<p>The playground now has a dark mode! Sample the themes here:</p>

<table>
<tr>
<th>
<button className={styles.swapTheme} onClick={swapToSystem}>
System
</button>
</th>
<td>Use your system&apos;s preference</td>
</tr>

<tr>
<th>
<button className={styles.swapTheme} onClick={swapToLight}>
Light
</button>
</th>
<td>The classic playground style</td>
</tr>

<tr>
<th>
<button className={styles.swapTheme} onClick={swapToDark}>
Dark
</button>
</th>
<td>Reduce the number of photons hitting your eyeballs</td>
</tr>
</table>

<p>
You can change the current UI theme (and the editor&apos;s theme) in the configuration menu.
</p>
</Notification>
) : null;
};

const RustSurvey2023Notification: React.FC = () => {
const showIt = useAppSelector(selectors.showRustSurvey2023Selector);

Expand Down Expand Up @@ -69,7 +121,7 @@ interface NotificationProps {
const Notification: React.FC<NotificationProps> = ({ onClose, children }) => (
<div className={styles.notification} data-test-id="notification">
<div className={styles.notificationContent}>{children}</div>
<button className={styles.close} onClick={onClose}>
<button className={styles.close} onClick={onClose} title="dismiss notification">
<Close />
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion ui/frontend/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function configureStore(window: Window) {
const baseUrl = new URL('/', window.location.href).href;
const websocket = websocketMiddleware(window);

const prefersDarkTheme = false && window.matchMedia('(prefers-color-scheme: dark)').matches;
const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
const initialThemes = prefersDarkTheme ? editorDarkThemes : {};

const initialGlobalState = {
Expand Down
2 changes: 0 additions & 2 deletions ui/frontend/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,11 @@
@mixin light-theme-vars;
}

/*
@media (prefers-color-scheme: dark) {
:root {
@mixin dark-theme-vars;
}
}
*/

[data-theme='dark']:root {
@mixin dark-theme-vars;
Expand Down
17 changes: 17 additions & 0 deletions ui/frontend/reducers/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,22 @@ const slice = createSlice({
changeProcessAssembly: (state, action: PayloadAction<ProcessAssembly>) => {
state.processAssembly = action.payload;
},

swapTheme: (state, action: PayloadAction<Theme>) => {
state.theme = action.payload;
switch (action.payload) {
case Theme.Light: {
state.ace.theme = 'github';
state.monaco.theme = 'vs';
break;
}
case Theme.Dark: {
state.ace.theme = 'github_dark';
state.monaco.theme = 'vscode-dark-plus';
break;
}
}
},
},
});

Expand All @@ -143,6 +159,7 @@ export const {
changePairCharacters,
changePrimaryAction,
changeProcessAssembly,
swapTheme,
} = slice.actions;

export const changeEdition =
Expand Down
11 changes: 1 addition & 10 deletions ui/frontend/reducers/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { createWebsocketResponse } from '../websocketActions';
interface State {
forced: boolean;
showGemThreshold: number;
showThemeThreshold: number;
}

const ENABLED = 1.0;
Expand All @@ -15,14 +14,12 @@ const DISABLED = -1.0;
const initialState: State = {
forced: false,
showGemThreshold: DISABLED,
showThemeThreshold: DISABLED,
};

const { action: wsFeatureFlags, schema: wsFeatureFlagsSchema } = createWebsocketResponse(
'featureFlags',
z.object({
showGemThreshold: z.number().nullish(),
showThemeThreshold: z.number().nullish(),
}),
);

Expand All @@ -33,12 +30,10 @@ const slice = createSlice({
forceEnableAll: (state) => {
state.forced = true;
state.showGemThreshold = ENABLED;
state.showThemeThreshold = ENABLED;
},
forceDisableAll: (state) => {
state.forced = true;
state.showGemThreshold = DISABLED;
state.showThemeThreshold = DISABLED;
},
},
extraReducers: (builder) => {
Expand All @@ -47,15 +42,11 @@ const slice = createSlice({
return;
}

const { showGemThreshold, showThemeThreshold } = action.payload;
const { showGemThreshold } = action.payload;

if (showGemThreshold) {
state.showGemThreshold = showGemThreshold;
}

if (showThemeThreshold) {
state.showThemeThreshold = showThemeThreshold;
}
});
},
});
Expand Down
6 changes: 6 additions & 0 deletions ui/frontend/reducers/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface State {
seenMonacoEditorAvailable: boolean; // expired
seenRustSurvey2022: boolean; // expired
seenRustSurvey2023: boolean;
seenDarkMode: boolean;
}

const initialState: State = {
Expand All @@ -22,6 +23,7 @@ const initialState: State = {
seenMonacoEditorAvailable: true,
seenRustSurvey2022: true,
seenRustSurvey2023: false,
seenDarkMode: false,
};

const slice = createSlice({
Expand All @@ -30,6 +32,9 @@ const slice = createSlice({
reducers: {
notificationSeen: (state, action: PayloadAction<Notification>) => {
switch (action.payload) {
case Notification.DarkMode: {
state.seenDarkMode = true;
}
case Notification.RustSurvey2023: {
state.seenRustSurvey2023 = true;
}
Expand All @@ -40,6 +45,7 @@ const slice = createSlice({

const { notificationSeen } = slice.actions;

export const seenDarkMode = () => notificationSeen(Notification.DarkMode);
export const seenRustSurvey2023 = () => notificationSeen(Notification.RustSurvey2023);

export default slice.reducer;
10 changes: 8 additions & 2 deletions ui/frontend/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,13 @@ const notificationsSelector = (state: State) => state.notifications;

const NOW = new Date();

const DARK_MODE_END = new Date('2024-10-15T00:00:00Z');
const DARK_MODE_OPEN = NOW <= DARK_MODE_END;
export const showDarkModeSelector = createSelector(
notificationsSelector,
notifications => DARK_MODE_OPEN && !notifications.seenDarkMode,
);

const RUST_SURVEY_2023_END = new Date('2024-01-15T00:00:00Z');
const RUST_SURVEY_2023_OPEN = NOW <= RUST_SURVEY_2023_END;
export const showRustSurvey2023Selector = createSelector(
Expand All @@ -365,6 +372,7 @@ export const showRustSurvey2023Selector = createSelector(
);

export const anyNotificationsToShowSelector = createSelector(
showDarkModeSelector,
showRustSurvey2023Selector,
excessiveExecutionSelector,
(...allNotifications) => allNotifications.some(n => n),
Expand Down Expand Up @@ -444,13 +452,11 @@ const websocket = (state: State) => state.websocket;
const clientFeatureFlagThreshold = createSelector(client, (c) => c.featureFlagThreshold);

const showGemThreshold = createSelector(featureFlags, ff => ff.showGemThreshold);
const showThemeThreshold = createSelector(featureFlags, ff => ff.showThemeThreshold);

const createFeatureFlagSelector = (ff: (state: State) => number) =>
createSelector(clientFeatureFlagThreshold, ff, (c, ff) => c <= ff);

export const showGemSelector = createFeatureFlagSelector(showGemThreshold);
export const showThemeSelector = createFeatureFlagSelector(showThemeThreshold);

export const executeViaWebsocketSelector = createSelector(websocket, (ws) => ws.connected);

Expand Down
1 change: 1 addition & 0 deletions ui/frontend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,6 @@ export enum Focus {
}

export enum Notification {
DarkMode = 'dark-mode',
RustSurvey2023 = 'rust-survey-2023',
}

0 comments on commit fb55dc1

Please sign in to comment.