Merge pull request #2420 from spotify/eide/hack-user-settings-dialog
sidebar: User settings dialog
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user