Merge pull request #2420 from spotify/eide/hack-user-settings-dialog

sidebar: User settings dialog
This commit is contained in:
Marcus Eide
2020-09-18 10:17:27 +02:00
committed by GitHub
21 changed files with 632 additions and 274 deletions
@@ -35,8 +35,6 @@ import {
SidebarSearchField,
SidebarSpace,
SidebarUserSettings,
SidebarThemeToggle,
SidebarPinButton,
DefaultProviderSettings,
} from '@backstage/core';
import { NavLink } from 'react-router-dom';
@@ -103,9 +101,7 @@ const Root: FC<{}> = ({ children }) => (
/>
<SidebarSpace />
<SidebarDivider />
<SidebarThemeToggle />
<SidebarUserSettings providerSettings={<DefaultProviderSettings />} />
<SidebarPinButton />
</Sidebar>
{children}
</SidebarPage>
@@ -80,6 +80,15 @@ export class UserFlags extends Map<FeatureFlagName, FeatureFlagState> {
return output;
}
toggle(name: FeatureFlagName): FeatureFlagState {
if (super.get(name) === FeatureFlagState.On) {
super.set(name, FeatureFlagState.Off);
} else {
super.set(name, FeatureFlagState.On);
}
return super.get(name) || FeatureFlagState.Off;
}
delete(name: FeatureFlagName): boolean {
const output = super.delete(name);
this.save();
@@ -1,75 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { FC, useContext } from 'react';
import { makeStyles } from '@material-ui/core';
import DoubleArrowIcon from '@material-ui/icons/DoubleArrow';
import { SidebarContext } from './config';
import { BackstageTheme } from '@backstage/theme';
import { SidebarPinStateContext } from './Page';
const ARROW_BUTTON_SIZE = 20;
const useStyles = makeStyles<BackstageTheme, { isPinned: boolean }>(theme => {
return {
root: {
position: 'relative',
alignSelf: 'stretch',
},
arrowButtonWrapper: {
position: 'absolute',
right: 0,
width: ARROW_BUTTON_SIZE,
height: ARROW_BUTTON_SIZE,
top: -(theme.spacing(6) + ARROW_BUTTON_SIZE) / 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '2px 0px 0px 2px',
background: theme.palette.pinSidebarButton.background,
color: theme.palette.pinSidebarButton.icon,
border: 'none',
outline: 'none',
cursor: 'pointer',
},
arrowButtonIcon: {
transform: ({ isPinned }) => (isPinned ? 'rotate(180deg)' : 'none'),
},
};
});
export const SidebarPinButton: FC<{}> = () => {
const { isOpen } = useContext(SidebarContext);
const { isPinned, toggleSidebarPinState } = useContext(
SidebarPinStateContext,
);
const classes = useStyles({ isPinned });
return (
<div className={classes.root}>
{isOpen && (
<button
className={classes.arrowButtonWrapper}
onClick={toggleSidebarPinState}
>
<DoubleArrowIcon
className={classes.arrowButtonIcon}
style={{ fontSize: 14 }}
/>
</button>
)}
</div>
);
};
@@ -0,0 +1,26 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { List, ListSubheader } from '@material-ui/core';
import { SidebarThemeToggle } from './ThemeToggle';
import { SidebarPinButton } from './PinButton';
export const AppSettingsList = () => (
<List dense subheader={<ListSubheader>App Settings</ListSubheader>}>
<SidebarThemeToggle />
<SidebarPinButton />
</List>
);
@@ -0,0 +1,29 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import List from '@material-ui/core/List';
import ListSubheader from '@material-ui/core/ListSubheader';
type Props = {
providerSettings: React.ReactNode;
};
export const AuthProvidersList = ({ providerSettings }: Props) => (
<List subheader={<ListSubheader>Available Auth Providers</ListSubheader>}>
{providerSettings}
</List>
);
@@ -0,0 +1,73 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import {
FeatureFlagName,
useApi,
featureFlagsApiRef,
} from '@backstage/core-api';
import {
ListItem,
ListItemSecondaryAction,
ListItemText,
Tooltip,
} from '@material-ui/core';
import CheckIcon from '@material-ui/icons/CheckCircle';
import { ToggleButton } from '@material-ui/lab';
export type Item = {
name: FeatureFlagName;
pluginId: string;
};
type Props = {
featureFlag: Item;
};
export const FlagItem = ({ featureFlag }: Props) => {
const api = useApi(featureFlagsApiRef);
const [enabled, setEnabled] = React.useState(
Boolean(api.getFlags().get(featureFlag.name)),
);
const toggleFlag = () => {
const newState = api.getFlags().toggle(featureFlag.name);
setEnabled(Boolean(newState));
};
return (
<ListItem>
<ListItemText
primary={featureFlag.name}
secondary={`Registered in ${featureFlag.pluginId} plugin`}
/>
<ListItemSecondaryAction>
<ToggleButton
size="small"
value="flag"
selected={enabled}
onChange={toggleFlag}
>
<Tooltip placement="top" arrow title={enabled ? 'Disable' : 'Enable'}>
<CheckIcon />
</Tooltip>
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -0,0 +1,32 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import List from '@material-ui/core/List';
import ListSubheader from '@material-ui/core/ListSubheader';
import { FlagItem, Item } from './FeatureFlagsItem';
type Props = {
featureFlags: Item[];
};
export const FeatureFlagsList = ({ featureFlags }: Props) => (
<List dense subheader={<ListSubheader>Feature Flags</ListSubheader>}>
{featureFlags.map(featureFlag => (
<FlagItem key={featureFlag.name} featureFlag={featureFlag} />
))}
</List>
);
@@ -0,0 +1,64 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext } from 'react';
import {
ListItem,
ListItemSecondaryAction,
ListItemText,
Tooltip,
} from '@material-ui/core';
import LockIcon from '@material-ui/icons/Lock';
import LockOpenIcon from '@material-ui/icons/LockOpen';
import { ToggleButton } from '@material-ui/lab';
import { SidebarPinStateContext } from '../Page';
export const SidebarPinButton = () => {
const { isPinned, toggleSidebarPinState } = useContext(
SidebarPinStateContext,
);
const PinIcon = () => (
<Tooltip
placement="top"
arrow
title={`${isPinned ? 'Unpin' : 'Pin'} Sidebar`}
>
{isPinned ? <LockIcon /> : <LockOpenIcon />}
</Tooltip>
);
return (
<ListItem>
<ListItemText
primary="Pin Sidebar"
secondary="Prevent the sidebar from collapsing"
/>
<ListItemSecondaryAction>
<ToggleButton
size="small"
value="pin"
selected={isPinned}
onChange={() => {
toggleSidebarPinState();
}}
>
<PinIcon />
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -14,31 +14,53 @@
* limitations under the License.
*/
import React, { FC } from 'react';
import { OAuthApi, OpenIdConnectApi, IconComponent } from '@backstage/core-api';
import { SidebarItem } from '../Items';
import { IconButton, Tooltip } from '@material-ui/core';
import StarBorder from '@material-ui/icons/StarBorder';
import React from 'react';
import { IconComponent, OAuthApi, OpenIdConnectApi } from '@backstage/core-api';
import {
ListItem,
ListItemIcon,
ListItemSecondaryAction,
ListItemText,
Tooltip,
} from '@material-ui/core';
import PowerButton from '@material-ui/icons/PowerSettingsNew';
import { ToggleButton } from '@material-ui/lab';
export const ProviderSettingsItem: FC<{
type Props = {
title: string;
icon: IconComponent;
signedIn: boolean;
api: OAuthApi | OpenIdConnectApi;
signInHandler: Function;
}> = ({ title, icon, signedIn, api, signInHandler }) => {
return (
<SidebarItem key={title} text={title} icon={icon ?? StarBorder}>
<IconButton onClick={() => (signedIn ? api.logout() : signInHandler())}>
};
export const ProviderSettingsItem = ({
title,
icon: Icon,
signedIn,
api,
signInHandler,
}: Props) => (
<ListItem>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={title} />
<ListItemSecondaryAction>
<ToggleButton
size="small"
value={title}
selected={signedIn}
onChange={() => (signedIn ? api.logout() : signInHandler())}
>
<Tooltip
placement="top"
arrow
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
>
<PowerButton color={signedIn ? 'secondary' : 'primary'} />
<PowerButton />
</Tooltip>
</IconButton>
</SidebarItem>
);
};
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
@@ -0,0 +1,74 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import {
Card,
CardContent,
CardHeader,
makeStyles,
Divider,
} from '@material-ui/core';
import { AppSettingsList } from './AppSettingsList';
import { AuthProvidersList } from './AuthProviderList';
import { FeatureFlagsList } from './FeatureFlagsList';
import { SignInAvatar } from './SignInAvatar';
import { UserSettingsMenu } from './UserSettingsMenu';
import { useUserProfile } from './useUserProfileInfo';
import { useApi, featureFlagsApiRef } from '@backstage/core-api';
const useStyles = makeStyles({
root: {
minWidth: 400,
},
});
type Props = {
providerSettings?: React.ReactNode;
};
export const SettingsDialog = ({ providerSettings }: Props) => {
const classes = useStyles();
const { profile, displayName } = useUserProfile();
const featureFlagsApi = useApi(featureFlagsApiRef);
const featureFlags = featureFlagsApi.getRegisteredFlags();
return (
<Card className={classes.root}>
<CardHeader
avatar={<SignInAvatar size={48} />}
action={<UserSettingsMenu />}
title={displayName}
subheader={profile.email}
/>
<CardContent>
<AppSettingsList />
{providerSettings && (
<>
<Divider />
<AuthProvidersList providerSettings={providerSettings} />
</>
)}
{featureFlags.length > 0 && (
<>
<Divider />
<FeatureFlagsList featureFlags={featureFlags} />
</>
)}
</CardContent>
</Card>
);
};
@@ -0,0 +1,42 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { BackstageTheme } from '@backstage/theme';
import { makeStyles, Avatar } from '@material-ui/core';
import { useUserProfile } from './useUserProfileInfo';
import { sidebarConfig } from '../config';
const useStyles = makeStyles<BackstageTheme, { size: number }>({
avatar: {
width: ({ size }) => size,
height: ({ size }) => size,
},
});
type Props = { size?: number };
export const SignInAvatar = ({ size }: Props) => {
const { iconSize } = sidebarConfig;
const classes = useStyles(size ? { size } : { size: iconSize });
const { profile, displayName } = useUserProfile();
return (
<Avatar src={profile.picture} className={classes.avatar}>
{displayName[0]}
</Avatar>
);
};
@@ -0,0 +1,87 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { useObservable } from 'react-use';
import LightIcon from '@material-ui/icons/WbSunny';
import DarkIcon from '@material-ui/icons/Brightness2';
import AutoIcon from '@material-ui/icons/BrightnessAuto';
import { appThemeApiRef, useApi } from '@backstage/core-api';
import ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import {
ListItem,
ListItemText,
ListItemSecondaryAction,
Tooltip,
} from '@material-ui/core';
export const SidebarThemeToggle = () => {
const appThemeApi = useApi(appThemeApiRef);
const themeId = useObservable(
appThemeApi.activeThemeId$(),
appThemeApi.getActiveThemeId(),
);
const themeIds = appThemeApi.getInstalledThemes();
// TODO(marcuseide): can these be put on the theme itself?
const themeIcons = {
dark: <DarkIcon />,
light: <LightIcon />,
};
const handleSetTheme = (
_event: React.MouseEvent<HTMLElement>,
newThemeId: string | undefined,
) => {
if (themeIds.some(t => t.id === newThemeId)) {
appThemeApi.setActiveThemeId(newThemeId);
} else {
appThemeApi.setActiveThemeId(undefined);
}
};
return (
<ListItem>
<ListItemText primary="Theme" secondary="Change the theme mode" />
<ListItemSecondaryAction>
<ToggleButtonGroup
exclusive
size="small"
value={themeId ?? 'auto'}
onChange={handleSetTheme}
>
{themeIds.map(theme => (
<ToggleButton key={theme.id} value={theme.variant}>
<Tooltip
placement="top"
arrow
title={`Select ${theme.variant} theme`}
>
{themeIcons[theme.variant]}
</Tooltip>
</ToggleButton>
))}
<ToggleButton value="auto">
<Tooltip placement="top" arrow title="Select auto theme">
<AutoIcon />
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -1,61 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { FC, useRef } from 'react';
import { makeStyles, Avatar, Divider } from '@material-ui/core';
import { useApi, identityApiRef } from '@backstage/core-api';
import { SidebarItem } from '../Items';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
const useStyles = makeStyles({
avatar: {
width: 24,
height: 24,
},
});
export const UserProfile: FC<{ open: boolean; setOpen: Function }> = ({
open,
setOpen,
}) => {
const ref = useRef<Element>(); // for scrolling down when collapse item opens
const classes = useStyles();
const identityApi = useApi(identityApiRef);
const handleClick = () => {
setOpen(!open);
setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300);
};
const userId = identityApi.getUserId();
const profile = identityApi.getProfile();
const displayName = profile.displayName ?? userId;
const SignInAvatar = () => (
<Avatar src={profile.picture} className={classes.avatar}>
{displayName[0]}
</Avatar>
);
return (
<>
<Divider innerRef={ref} />
<SidebarItem text={displayName} onClick={handleClick} icon={SignInAvatar}>
{open ? <ExpandMore /> : <ExpandLess />}
</SidebarItem>
</>
);
};
@@ -0,0 +1,77 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect, useContext } from 'react';
import { Popover } from '@material-ui/core';
import { SignInAvatar } from './SignInAvatar';
import { SettingsDialog } from './SettingsDialog';
import { SidebarItem } from '../Items';
import { useUserProfile } from './useUserProfileInfo';
import { SidebarContext } from '../config';
type Props = {
providerSettings?: React.ReactNode;
};
export const SidebarUserSettings = ({ providerSettings }: Props) => {
const { isOpen: sidebarOpen } = useContext(SidebarContext);
const { displayName } = useUserProfile();
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | undefined>(
undefined,
);
const handleOpen = (event?: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event?.currentTarget ?? undefined);
setOpen(true);
};
const handleClose = () => {
setAnchorEl(undefined);
setOpen(false);
};
useEffect(() => {
if (!sidebarOpen && open) setOpen(false);
}, [open, sidebarOpen]);
const SidebarAvatar = () => <SignInAvatar />;
return (
<>
<SidebarItem
text={displayName}
onClick={handleOpen}
icon={SidebarAvatar}
/>
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'center',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<SettingsDialog providerSettings={providerSettings} />
</Popover>
</>
);
};
@@ -0,0 +1,55 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { identityApiRef, useApi } from '@backstage/core-api';
import { IconButton, ListItemIcon, Menu, MenuItem } from '@material-ui/core';
import SignOutIcon from '@material-ui/icons/MeetingRoom';
import MoreVertIcon from '@material-ui/icons/MoreVert';
export const UserSettingsMenu = () => {
const identityApi = useApi(identityApiRef);
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState<undefined | HTMLElement>(
undefined,
);
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
setOpen(true);
};
const handleClose = () => {
setAnchorEl(undefined);
setOpen(false);
};
return (
<>
<IconButton onClick={handleOpen}>
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={() => identityApi.logout()}>
<ListItemIcon>
<SignOutIcon />
</ListItemIcon>
Sign Out
</MenuItem>
</Menu>
</>
);
};
@@ -17,4 +17,4 @@
export { ProviderSettingsItem } from './ProviderSettingsItem';
export { OAuthProviderSettings } from './OAuthProviderSettings';
export { OIDCProviderSettings } from './OIDCProviderSettings';
export { UserProfile } from './UserProfile';
export { SidebarUserSettings } from './UserSettings';
@@ -0,0 +1,26 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useApi, identityApiRef } from '@backstage/core-api';
export const useUserProfile = () => {
const identityApi = useApi(identityApiRef);
const userId = identityApi.getUserId();
const profile = identityApi.getProfile();
const displayName = profile.displayName ?? userId;
return { profile, displayName };
};
@@ -1,58 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { FC } from 'react';
import { useObservable } from 'react-use';
import LightIcon from '@material-ui/icons/WbSunny';
import DarkIcon from '@material-ui/icons/Brightness2';
import AutoIcon from '@material-ui/icons/BrightnessAuto';
import { appThemeApiRef, useApi } from '@backstage/core-api';
import { SidebarItem } from './Items';
export const SidebarThemeToggle: FC<{}> = () => {
const appThemeApi = useApi(appThemeApiRef);
const themeId = useObservable(
appThemeApi.activeThemeId$(),
appThemeApi.getActiveThemeId(),
);
let text = 'Auto';
let icon = AutoIcon;
switch (themeId) {
case 'dark':
text = 'Dark mode';
icon = DarkIcon;
break;
case 'light':
text = 'Light mode';
icon = LightIcon;
break;
default:
break;
}
const handleToggle = () => {
if (!themeId) {
appThemeApi.setActiveThemeId('light');
} else if (themeId === 'light') {
appThemeApi.setActiveThemeId('dark');
} else {
appThemeApi.setActiveThemeId(undefined);
}
};
return <SidebarItem text={text} onClick={handleToggle} icon={icon} />;
};
@@ -1,53 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { identityApiRef, useApi } from '@backstage/core-api';
import Collapse from '@material-ui/core/Collapse';
import SignOutIcon from '@material-ui/icons/MeetingRoom';
import React, { useContext, useEffect } from 'react';
import { SidebarContext } from './config';
import { SidebarItem } from './Items';
import { UserProfile as SidebarUserProfile } from './Settings';
type SidebarUserSettingsProps = { providerSettings?: React.ReactNode };
export function SidebarUserSettings({
providerSettings,
}: SidebarUserSettingsProps) {
const { isOpen: sidebarOpen } = useContext(SidebarContext);
const [open, setOpen] = React.useState(false);
const identityApi = useApi(identityApiRef);
// Close the provider list when sidebar collapse
useEffect(() => {
if (!sidebarOpen && open) setOpen(false);
}, [open, sidebarOpen]);
return (
<>
<SidebarUserProfile open={open} setOpen={setOpen} />
<Collapse in={open} timeout="auto">
{providerSettings}
<SidebarItem
icon={SignOutIcon}
text="Sign Out"
onClick={() => identityApi.logout()}
/>
</Collapse>
</>
);
}
@@ -25,14 +25,11 @@ export {
SidebarSpacer,
} from './Items';
export { IntroCard, SidebarIntro } from './Intro';
export { SidebarPinButton } from './PinButton';
export {
SIDEBAR_INTRO_LOCAL_STORAGE,
SidebarContext,
sidebarConfig,
} from './config';
export type { SidebarContextType } from './config';
export { SidebarThemeToggle } from './SidebarThemeToggle';
export { SidebarUserSettings } from './UserSettings';
export { DefaultProviderSettings } from './DefaultProviderSettings';
export * from './Settings';
@@ -18,8 +18,6 @@ import {
SidebarContext,
SidebarSpace,
SidebarUserSettings,
SidebarThemeToggle,
SidebarPinButton,
DefaultProviderSettings,
} from '@backstage/core';
@@ -39,9 +37,7 @@ export const AppSidebar = () => (
<SidebarDivider />
<SidebarSpace />
<SidebarDivider />
<SidebarThemeToggle />
<SidebarUserSettings providerSettings={<DefaultProviderSettings />} />
<SidebarPinButton />
</Sidebar>
);