Skip to content

Commit

Permalink
injected component
Browse files Browse the repository at this point in the history
  • Loading branch information
salazarm committed Mar 22, 2024
1 parent fadf49c commit c5d7100
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {AppTopNavRightOfLogo} from '@dagster-io/ui-core/app/AppTopNav/AppTopNavRightOfLogo.oss';
import {InjectedComponentContext} from '@dagster-io/ui-core/app/InjectedComponentContext';
import {UserPreferences} from '@dagster-io/ui-core/app/UserSettingsDialog/UserPreferences.oss';

export const InjectedComponents = ({children}: {children: React.ReactNode}) => {
return (
<InjectedComponentContext.Provider
value={{
AppTopNavRightOfLogo,
UserPreferences,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import styled from 'styled-components';

import {Colors} from './Color';
import {IconWrapper} from './Icon';

export const RoundedButton = styled.button`
background-color: ${Colors.navButton()};
border-radius: 24px;
border: 0;
color: ${Colors.navTextSelected()};
cursor: pointer;
font-size: 14px;
line-height: 20px;
height: 32px;
text-align: left;
display: block;
padding: 0 8px 0 4px;
user-select: none;
&:focus,
&:active {
outline: none;
}
${IconWrapper} {
background: ${Colors.navText()};
}
&:hover {
background-color: ${Colors.navButtonHover()};
${IconWrapper} {
background: ${Colors.navTextSelected()};
}
}
${IconWrapper} {
transition: linear 100ms background;
}
`;
1 change: 1 addition & 0 deletions js_modules/dagster-ui/packages/ui-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * from './components/Popover';
export * from './components/ProductTour';
export * from './components/Radio';
export * from './components/RefreshableCountdown';
export * from './components/RoundedButton';
export * from './components/Select';
export * from './components/Slider';
export * from './components/Spinner';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,34 @@ import React, {useContext} from 'react';

// import using type so that the actual file doesn't get bundled into Cloud if it's not imported directly by cloud.
import type {AppTopNavRightOfLogo} from './AppTopNav/AppTopNavRightOfLogo.oss';
import type {UserPreferences} from './UserSettingsDialog/UserPreferences.oss';

type AComponentOrNull<Props> =
type ComponentType = keyof React.JSX.IntrinsicElements | React.JSXElementConstructor<any>;
type AComponentFromComponent<TComponent extends ComponentType> = AComponentWithProps<
React.ComponentProps<TComponent>
>;

type AComponentWithProps<Props = Record<string, never>> =
| ((props: Props) => React.ReactNode)
| React.MemoExoticComponent<(props: Props) => React.ReactNode>
| null
| undefined;
| React.MemoExoticComponent<(props: Props) => React.ReactNode>;

export const InjectedComponentContext = React.createContext<{
AppTopNavRightOfLogo: AComponentOrNull<React.ComponentProps<typeof AppTopNavRightOfLogo>>;
OverviewPageAlerts?: AComponentOrNull<Record<string, never>>;
}>({
type InjectedComponentContextType = {
AppTopNavRightOfLogo: AComponentFromComponent<typeof AppTopNavRightOfLogo> | null;
OverviewPageAlerts?: AComponentWithProps | null;
UserPreferences?: AComponentFromComponent<typeof UserPreferences> | null;
};
export const InjectedComponentContext = React.createContext<InjectedComponentContextType>({
AppTopNavRightOfLogo: null,
OverviewPageAlerts: null,
});

export function componentStub(component: keyof React.ContextType<typeof InjectedComponentContext>) {
return () => {
export function componentStub<TComponentKey extends keyof InjectedComponentContextType>(
component: TComponentKey,
): NonNullable<InjectedComponentContextType[TComponentKey]> {
return (props: any) => {
const {[component]: Component} = useContext(InjectedComponentContext);
if (Component) {
return <Component />;
return <Component {...props} />;
}
return null;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Colors, Icon, IconWrapper} from '@dagster-io/ui-components';
import {useState} from 'react';
import styled from 'styled-components';

import {UserSettingsDialog} from './UserSettingsDialog';
import {UserSettingsDialog} from './UserSettingsDialog/UserSettingsDialog';
import {getVisibleFeatureFlagRows} from './getVisibleFeatureFlagRows';

const SettingsButton = styled.button`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
Box,
Button,
Checkbox,
DAGSTER_THEME_KEY,
DagsterTheme,
Icon,
Subheading,
} from '@dagster-io/ui-components';
import React from 'react';

import {useStateWithStorage} from '../../hooks/useStateWithStorage';
import {SHORTCUTS_STORAGE_KEY} from '../ShortcutHandler';
import {HourCycleSelect} from '../time/HourCycleSelect';
import {ThemeSelect} from '../time/ThemeSelect';
import {TimezoneSelect} from '../time/TimezoneSelect';
import {automaticLabel} from '../time/browserTimezone';

export const UserPreferences = ({
onChangeRequiresReload,
}: {
onChangeRequiresReload: (requiresReload: boolean) => void;
}) => {
const [shortcutsEnabled, setShortcutsEnabled] = useStateWithStorage(
SHORTCUTS_STORAGE_KEY,
(value: any) => (typeof value === 'boolean' ? value : true),
);

const [theme, setTheme] = useStateWithStorage(DAGSTER_THEME_KEY, (value: any) => {
if (value === DagsterTheme.Light || value === DagsterTheme.Dark) {
return value;
}
return DagsterTheme.System;
});

const initialShortcutsEnabled = React.useRef(shortcutsEnabled);
const initialTheme = React.useRef(theme);

const lastChangeValue = React.useRef(false);
React.useEffect(() => {
const didChange =
initialTheme.current !== theme || initialShortcutsEnabled.current !== shortcutsEnabled;
if (lastChangeValue.current !== didChange) {
onChangeRequiresReload(didChange);
lastChangeValue.current = didChange;
}
}, [shortcutsEnabled, theme, onChangeRequiresReload]);

const trigger = React.useCallback(
(timezone: string) => (
<Button
rightIcon={<Icon name="arrow_drop_down" />}
style={{minWidth: '200px', display: 'flex', justifyContent: 'space-between'}}
>
{timezone === 'Automatic' ? automaticLabel() : timezone}
</Button>
),
[],
);

const toggleKeyboardShortcuts = (e: React.ChangeEvent<HTMLInputElement>) => {
const {checked} = e.target;
setShortcutsEnabled(checked);
};

return (
<>
<Box padding={{bottom: 4}}>
<Subheading>Preferences</Subheading>
</Box>
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Timezone</div>
<TimezoneSelect trigger={trigger} />
</Box>
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Hour format</div>
<HourCycleSelect />
</Box>
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Theme</div>
<ThemeSelect theme={theme} onChange={setTheme} />
</Box>
<Box padding={{vertical: 8}} flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Enable keyboard shortcuts</div>
<Checkbox checked={shortcutsEnabled} format="switch" onChange={toggleKeyboardShortcuts} />
</Box>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {componentStub} from '../InjectedComponentContext';

export const UserPreferences = componentStub('UserPreferences');
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@ import {
Dialog,
DialogBody,
DialogFooter,
Icon,
Subheading,
} from '@dagster-io/ui-components';
import {DAGSTER_THEME_KEY, DagsterTheme} from '@dagster-io/ui-components/src/theme/theme';
import * as React from 'react';

import {FeatureFlagType, getFeatureFlags, setFeatureFlags} from './Flags';
import {SHORTCUTS_STORAGE_KEY} from './ShortcutHandler';
import {HourCycleSelect} from './time/HourCycleSelect';
import {ThemeSelect} from './time/ThemeSelect';
import {TimezoneSelect} from './time/TimezoneSelect';
import {automaticLabel} from './time/browserTimezone';
import {useStateWithStorage} from '../hooks/useStateWithStorage';
import {UserPreferences} from './UserPreferences';
import {FeatureFlagType, getFeatureFlags, setFeatureFlags} from '../Flags';

type OnCloseFn = (event: React.SyntheticEvent<HTMLElement>) => void;
type VisibleFlag = {key: string; label?: React.ReactNode; flagType: FeatureFlagType};
Expand Down Expand Up @@ -54,21 +47,7 @@ const UserSettingsDialogContent = ({onClose, visibleFlags}: DialogContentProps)
const [flags, setFlags] = React.useState<FeatureFlagType[]>(() => getFeatureFlags());
const [reloading, setReloading] = React.useState(false);

const [shortcutsEnabled, setShortcutsEnabled] = useStateWithStorage(
SHORTCUTS_STORAGE_KEY,
(value: any) => (typeof value === 'boolean' ? value : true),
);

const [theme, setTheme] = useStateWithStorage(DAGSTER_THEME_KEY, (value: any) => {
if (value === DagsterTheme.Light || value === DagsterTheme.Dark) {
return value;
}
return DagsterTheme.System;
});

const initialFlagState = React.useRef(JSON.stringify([...getFeatureFlags().sort()]));
const initialShortcutsEnabled = React.useRef(shortcutsEnabled);
const initialTheme = React.useRef(theme);

React.useEffect(() => {
setFeatureFlags(flags);
Expand All @@ -78,27 +57,10 @@ const UserSettingsDialogContent = ({onClose, visibleFlags}: DialogContentProps)
setFlags(flags.includes(flag) ? flags.filter((f) => f !== flag) : [...flags, flag]);
};

const trigger = React.useCallback(
(timezone: string) => (
<Button
rightIcon={<Icon name="arrow_drop_down" />}
style={{minWidth: '200px', display: 'flex', justifyContent: 'space-between'}}
>
{timezone === 'Automatic' ? automaticLabel() : timezone}
</Button>
),
[],
);

const toggleKeyboardShortcuts = (e: React.ChangeEvent<HTMLInputElement>) => {
const {checked} = e.target;
setShortcutsEnabled(checked);
};
const [arePreferencesChanged, setAreaPreferencesChanged] = React.useState(false);

const anyChange =
initialFlagState.current !== JSON.stringify([...flags.sort()]) ||
initialShortcutsEnabled.current !== shortcutsEnabled ||
initialTheme.current !== theme;
initialFlagState.current !== JSON.stringify([...flags.sort()]) || arePreferencesChanged;

const handleClose = (event: React.SyntheticEvent<HTMLElement>) => {
if (anyChange) {
Expand All @@ -113,32 +75,7 @@ const UserSettingsDialogContent = ({onClose, visibleFlags}: DialogContentProps)
<>
<DialogBody>
<Box padding={{bottom: 8}} flex={{direction: 'column', gap: 4}}>
<Box padding={{bottom: 4}}>
<Subheading>Preferences</Subheading>
</Box>
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Timezone</div>
<TimezoneSelect trigger={trigger} />
</Box>
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Hour format</div>
<HourCycleSelect />
</Box>
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>Theme</div>
<ThemeSelect theme={theme} onChange={setTheme} />
</Box>
<Box
padding={{vertical: 8}}
flex={{justifyContent: 'space-between', alignItems: 'center'}}
>
<div>Enable keyboard shortcuts</div>
<Checkbox
checked={shortcutsEnabled}
format="switch"
onChange={toggleKeyboardShortcuts}
/>
</Box>
<UserPreferences onChange={setAreaPreferencesChanged} />
</Box>
<Box padding={{top: 16}} border="top">
<Box padding={{bottom: 8}}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Box, Icon} from '@dagster-io/ui-components';
import {useState} from 'react';
import {useLocation} from 'react-router-dom';

import {UserSettingsDialog} from '../app/UserSettingsDialog';
import {UserSettingsDialog} from '../app/UserSettingsDialog/UserSettingsDialog';
import {getVisibleFeatureFlagRows} from '../app/getVisibleFeatureFlagRows';
import {SideNavItem, SideNavItemConfig} from '../ui/SideNavItem';

Expand Down

0 comments on commit c5d7100

Please sign in to comment.