mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Allow hiding environments from the feature overview screen (#2727)
This commit is contained in:
parent
c2a3872386
commit
88d649d239
@ -17,14 +17,14 @@ import { featuresPlaceholder } from 'component/feature/FeatureToggleList/Feature
|
|||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import { useSearch } from 'hooks/useSearch';
|
import { useSearch } from 'hooks/useSearch';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { TimeAgoCell } from '../../../common/Table/cells/TimeAgoCell/TimeAgoCell';
|
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
|
||||||
import { TextCell } from '../../../common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell';
|
import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell';
|
||||||
import { AvatarCell } from './AvatarCell/AvatarCell';
|
import { AvatarCell } from './AvatarCell/AvatarCell';
|
||||||
import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell';
|
import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell';
|
||||||
import { TableBody, TableRow } from '../../../common/Table';
|
import { TableBody, TableRow } from 'component/common/Table';
|
||||||
import { useStyles } from './ChangeRequestsTabs.styles';
|
import { useStyles } from './ChangeRequestsTabs.styles';
|
||||||
import { createLocalStorage } from '../../../../utils/createLocalStorage';
|
import { createLocalStorage } from 'utils/createLocalStorage';
|
||||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||||
|
|
||||||
export interface IChangeRequestTableProps {
|
export interface IChangeRequestTableProps {
|
||||||
|
@ -14,6 +14,7 @@ import { usePageTitle } from 'hooks/usePageTitle';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
|
import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
|
||||||
|
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
|
||||||
|
|
||||||
const FeatureOverview = () => {
|
const FeatureOverview = () => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
@ -22,6 +23,8 @@ const FeatureOverview = () => {
|
|||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const featurePath = formatFeaturePath(projectId, featureId);
|
const featurePath = formatFeaturePath(projectId, featureId);
|
||||||
|
const { hiddenEnvironments, setHiddenEnvironments } =
|
||||||
|
useHiddenEnvironments();
|
||||||
const onSidebarClose = () => navigate(featurePath);
|
const onSidebarClose = () => navigate(featurePath);
|
||||||
usePageTitle(featureId);
|
usePageTitle(featureId);
|
||||||
|
|
||||||
@ -31,7 +34,12 @@ const FeatureOverview = () => {
|
|||||||
<FeatureOverviewMetaData />
|
<FeatureOverviewMetaData />
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(uiConfig.flags.variantsPerEnvironment)}
|
condition={Boolean(uiConfig.flags.variantsPerEnvironment)}
|
||||||
show={<FeatureOverviewSidePanel />}
|
show={
|
||||||
|
<FeatureOverviewSidePanel
|
||||||
|
hiddenEnvironments={hiddenEnvironments}
|
||||||
|
setHiddenEnvironments={setHiddenEnvironments}
|
||||||
|
/>
|
||||||
|
}
|
||||||
elseShow={<FeatureOverviewEnvSwitches />}
|
elseShow={<FeatureOverviewEnvSwitches />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,6 +21,7 @@ import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureSt
|
|||||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
|
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
|
||||||
|
import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage';
|
||||||
|
|
||||||
interface IFeatureOverviewEnvironmentProps {
|
interface IFeatureOverviewEnvironmentProps {
|
||||||
env: IFeatureEnvironment;
|
env: IFeatureEnvironment;
|
||||||
@ -123,6 +124,7 @@ const FeatureOverviewEnvironment = ({
|
|||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { metrics } = useFeatureMetrics(projectId, featureId);
|
const { metrics } = useFeatureMetrics(projectId, featureId);
|
||||||
const { feature } = useFeature(projectId, featureId);
|
const { feature } = useFeature(projectId, featureId);
|
||||||
|
const { value: globalStore } = useGlobalLocalStorage();
|
||||||
|
|
||||||
const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
|
const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
|
||||||
const environmentMetric = featureMetrics.find(
|
const environmentMetric = featureMetrics.find(
|
||||||
@ -133,92 +135,106 @@ const FeatureOverviewEnvironment = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledFeatureOverviewEnvironment enabled={env.enabled}>
|
<ConditionallyRender
|
||||||
<StyledAccordion
|
condition={
|
||||||
data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${env.name}`}
|
!Boolean(new Set(globalStore.hiddenEnvironments).has(env.name))
|
||||||
>
|
}
|
||||||
<StyledAccordionSummary
|
show={
|
||||||
expandIcon={<ExpandMore titleAccess="Toggle" />}
|
<StyledFeatureOverviewEnvironment enabled={env.enabled}>
|
||||||
>
|
<StyledAccordion
|
||||||
<StyledHeader data-loading enabled={env.enabled}>
|
data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${env.name}`}
|
||||||
<StyledHeaderTitle>
|
>
|
||||||
<StyledEnvironmentIcon enabled={env.enabled} />
|
<StyledAccordionSummary
|
||||||
<div>
|
expandIcon={<ExpandMore titleAccess="Toggle" />}
|
||||||
<StyledStringTruncator
|
>
|
||||||
text={env.name}
|
<StyledHeader data-loading enabled={env.enabled}>
|
||||||
maxWidth="100"
|
<StyledHeaderTitle>
|
||||||
maxLength={15}
|
<StyledEnvironmentIcon
|
||||||
/>
|
enabled={env.enabled}
|
||||||
</div>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={!env.enabled}
|
|
||||||
show={
|
|
||||||
<Chip
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
label="Disabled"
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
/>
|
/>
|
||||||
}
|
<div>
|
||||||
/>
|
<StyledStringTruncator
|
||||||
</StyledHeaderTitle>
|
text={env.name}
|
||||||
<StyledContainer>
|
maxWidth="100"
|
||||||
<FeatureStrategyMenu
|
maxLength={15}
|
||||||
label="Add strategy"
|
/>
|
||||||
projectId={projectId}
|
</div>
|
||||||
featureId={featureId}
|
<ConditionallyRender
|
||||||
environmentId={env.name}
|
condition={!env.enabled}
|
||||||
variant="text"
|
show={
|
||||||
/>
|
<Chip
|
||||||
<FeatureStrategyIcons
|
size="small"
|
||||||
strategies={featureEnvironment?.strategies}
|
variant="outlined"
|
||||||
/>
|
label="Disabled"
|
||||||
</StyledContainer>
|
sx={{ ml: 1 }}
|
||||||
</StyledHeader>
|
/>
|
||||||
|
}
|
||||||
<FeatureOverviewEnvironmentMetrics
|
/>
|
||||||
environmentMetric={environmentMetric}
|
</StyledHeaderTitle>
|
||||||
disabled={!env.enabled}
|
<StyledContainer>
|
||||||
/>
|
|
||||||
</StyledAccordionSummary>
|
|
||||||
|
|
||||||
<StyledAccordionDetails enabled={env.enabled}>
|
|
||||||
<StyledEnvironmentAccordionBody
|
|
||||||
featureEnvironment={featureEnvironment}
|
|
||||||
isDisabled={!env.enabled}
|
|
||||||
otherEnvironments={feature?.environments
|
|
||||||
.map(({ name }) => name)
|
|
||||||
.filter(name => name !== env.name)}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={
|
|
||||||
(featureEnvironment?.strategies?.length || 0) > 0
|
|
||||||
}
|
|
||||||
show={
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
py: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FeatureStrategyMenu
|
<FeatureStrategyMenu
|
||||||
label="Add strategy"
|
label="Add strategy"
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
featureId={featureId}
|
featureId={featureId}
|
||||||
environmentId={env.name}
|
environmentId={env.name}
|
||||||
|
variant="text"
|
||||||
/>
|
/>
|
||||||
</Box>
|
<FeatureStrategyIcons
|
||||||
<EnvironmentFooter
|
strategies={
|
||||||
environmentMetric={environmentMetric}
|
featureEnvironment?.strategies
|
||||||
/>
|
}
|
||||||
</>
|
/>
|
||||||
}
|
</StyledContainer>
|
||||||
/>
|
</StyledHeader>
|
||||||
</StyledAccordionDetails>
|
|
||||||
</StyledAccordion>
|
<FeatureOverviewEnvironmentMetrics
|
||||||
</StyledFeatureOverviewEnvironment>
|
environmentMetric={environmentMetric}
|
||||||
|
disabled={!env.enabled}
|
||||||
|
/>
|
||||||
|
</StyledAccordionSummary>
|
||||||
|
|
||||||
|
<StyledAccordionDetails enabled={env.enabled}>
|
||||||
|
<StyledEnvironmentAccordionBody
|
||||||
|
featureEnvironment={featureEnvironment}
|
||||||
|
isDisabled={!env.enabled}
|
||||||
|
otherEnvironments={feature?.environments
|
||||||
|
.map(({ name }) => name)
|
||||||
|
.filter(name => name !== env.name)}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
(featureEnvironment?.strategies?.length ||
|
||||||
|
0) > 0
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
py: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FeatureStrategyMenu
|
||||||
|
label="Add strategy"
|
||||||
|
projectId={projectId}
|
||||||
|
featureId={featureId}
|
||||||
|
environmentId={env.name}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<EnvironmentFooter
|
||||||
|
environmentMetric={
|
||||||
|
environmentMetric
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledAccordionDetails>
|
||||||
|
</StyledAccordion>
|
||||||
|
</StyledFeatureOverviewEnvironment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,15 @@ const StyledHeader = styled('h3')(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const FeatureOverviewSidePanel = () => {
|
interface IFeatureOverviewSidePanelProps {
|
||||||
|
hiddenEnvironments: Set<String>;
|
||||||
|
setHiddenEnvironments: (environment: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureOverviewSidePanel = ({
|
||||||
|
hiddenEnvironments,
|
||||||
|
setHiddenEnvironments,
|
||||||
|
}: IFeatureOverviewSidePanelProps) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { feature } = useFeature(projectId, featureId);
|
const { feature } = useFeature(projectId, featureId);
|
||||||
@ -64,6 +72,8 @@ export const FeatureOverviewSidePanel = () => {
|
|||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
}
|
}
|
||||||
feature={feature}
|
feature={feature}
|
||||||
|
hiddenEnvironments={hiddenEnvironments}
|
||||||
|
setHiddenEnvironments={setHiddenEnvironments}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<FeatureOverviewSidePanelDetails
|
<FeatureOverviewSidePanelDetails
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { Visibility, VisibilityOff } from '@mui/icons-material';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
const Visible = styled(Visibility)(({ theme }) => ({
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
color: theme.palette.grey[700],
|
||||||
|
'&:hover': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
opacity: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const VisibleOff = styled(VisibilityOff)(({ theme }) => ({
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
color: theme.palette.grey[700],
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IFeatureOverviewSidePanelEnvironmentHiderProps {
|
||||||
|
environment: IFeatureEnvironment;
|
||||||
|
hiddenEnvironments: Set<String>;
|
||||||
|
setHiddenEnvironments: (environment: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureOverviewSidePanelEnvironmentHider = ({
|
||||||
|
environment,
|
||||||
|
hiddenEnvironments,
|
||||||
|
setHiddenEnvironments,
|
||||||
|
}: IFeatureOverviewSidePanelEnvironmentHiderProps) => {
|
||||||
|
const toggleHiddenEnvironments = () => {
|
||||||
|
setHiddenEnvironments(environment.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hiddenEnvironments.has(environment.name)}
|
||||||
|
show={<VisibleOff onClick={toggleHiddenEnvironments} />}
|
||||||
|
elseShow={<Visible onClick={toggleHiddenEnvironments} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -13,12 +13,15 @@ import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfi
|
|||||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { RemoveRedEye, Star } from '@mui/icons-material';
|
||||||
|
import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider';
|
||||||
|
|
||||||
const StyledContainer = styled('div')(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
marginLeft: theme.spacing(-1.5),
|
marginLeft: theme.spacing(-1.5),
|
||||||
'&:not(:last-of-type)': {
|
'&:not(:last-of-type)': {
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
},
|
},
|
||||||
|
display: 'flex',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledLabel = styled('label')(() => ({
|
const StyledLabel = styled('label')(() => ({
|
||||||
@ -27,11 +30,19 @@ const StyledLabel = styled('label')(() => ({
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const HideButton = styled(RemoveRedEye)(({ theme }) => ({
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
color: theme.palette.grey[700],
|
||||||
|
}));
|
||||||
|
|
||||||
interface IFeatureOverviewSidePanelEnvironmentSwitchProps {
|
interface IFeatureOverviewSidePanelEnvironmentSwitchProps {
|
||||||
environment: IFeatureEnvironment;
|
environment: IFeatureEnvironment;
|
||||||
callback?: () => void;
|
callback?: () => void;
|
||||||
showInfoBox: () => void;
|
showInfoBox: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
hiddenEnvironments: Set<String>;
|
||||||
|
setHiddenEnvironments: (environment: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureOverviewSidePanelEnvironmentSwitch = ({
|
export const FeatureOverviewSidePanelEnvironmentSwitch = ({
|
||||||
@ -39,6 +50,8 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
|
|||||||
callback,
|
callback,
|
||||||
showInfoBox,
|
showInfoBox,
|
||||||
children,
|
children,
|
||||||
|
hiddenEnvironments,
|
||||||
|
setHiddenEnvironments,
|
||||||
}: IFeatureOverviewSidePanelEnvironmentSwitchProps) => {
|
}: IFeatureOverviewSidePanelEnvironmentSwitchProps) => {
|
||||||
const { name, enabled } = environment;
|
const { name, enabled } = environment;
|
||||||
|
|
||||||
@ -136,6 +149,11 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
|
|||||||
/>
|
/>
|
||||||
{children ?? defaultContent}
|
{children ?? defaultContent}
|
||||||
</StyledLabel>
|
</StyledLabel>
|
||||||
|
<FeatureOverviewSidePanelEnvironmentHider
|
||||||
|
environment={environment}
|
||||||
|
hiddenEnvironments={hiddenEnvironments}
|
||||||
|
setHiddenEnvironments={setHiddenEnvironments}
|
||||||
|
/>
|
||||||
<ChangeRequestDialogue
|
<ChangeRequestDialogue
|
||||||
isOpen={changeRequestDialogDetails.isOpen}
|
isOpen={changeRequestDialogDetails.isOpen}
|
||||||
onClose={onChangeRequestToggleClose}
|
onClose={onChangeRequestToggleClose}
|
||||||
|
@ -32,11 +32,15 @@ const StyledLink = styled(Link<typeof RouterLink | 'a'>)(() => ({
|
|||||||
interface IFeatureOverviewSidePanelEnvironmentSwitchesProps {
|
interface IFeatureOverviewSidePanelEnvironmentSwitchesProps {
|
||||||
feature: IFeatureToggle;
|
feature: IFeatureToggle;
|
||||||
header: React.ReactNode;
|
header: React.ReactNode;
|
||||||
|
hiddenEnvironments: Set<String>;
|
||||||
|
setHiddenEnvironments: (environment: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureOverviewSidePanelEnvironmentSwitches = ({
|
export const FeatureOverviewSidePanelEnvironmentSwitches = ({
|
||||||
feature,
|
feature,
|
||||||
header,
|
header,
|
||||||
|
hiddenEnvironments,
|
||||||
|
setHiddenEnvironments,
|
||||||
}: IFeatureOverviewSidePanelEnvironmentSwitchesProps) => {
|
}: IFeatureOverviewSidePanelEnvironmentSwitchesProps) => {
|
||||||
const [showInfoBox, setShowInfoBox] = useState(false);
|
const [showInfoBox, setShowInfoBox] = useState(false);
|
||||||
const [environmentName, setEnvironmentName] = useState('');
|
const [environmentName, setEnvironmentName] = useState('');
|
||||||
@ -73,6 +77,8 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({
|
|||||||
<FeatureOverviewSidePanelEnvironmentSwitch
|
<FeatureOverviewSidePanelEnvironmentSwitch
|
||||||
key={environment.name}
|
key={environment.name}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
|
hiddenEnvironments={hiddenEnvironments}
|
||||||
|
setHiddenEnvironments={setHiddenEnvironments}
|
||||||
showInfoBox={() => {
|
showInfoBox={() => {
|
||||||
setEnvironmentName(environment.name);
|
setEnvironmentName(environment.name);
|
||||||
setShowInfoBox(true);
|
setShowInfoBox(true);
|
||||||
|
@ -2,6 +2,7 @@ import { createLocalStorage } from 'utils/createLocalStorage';
|
|||||||
|
|
||||||
interface IGlobalStore {
|
interface IGlobalStore {
|
||||||
favorites?: boolean;
|
favorites?: boolean;
|
||||||
|
hiddenEnvironments?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGlobalLocalStorage = () => {
|
export const useGlobalLocalStorage = () => {
|
||||||
|
37
frontend/src/hooks/useHiddenEnvironments.ts
Normal file
37
frontend/src/hooks/useHiddenEnvironments.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { createLocalStorage } from 'utils/createLocalStorage';
|
||||||
|
import { useGlobalLocalStorage } from './useGlobalLocalStorage';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface IGlobalStore {
|
||||||
|
favorites?: boolean;
|
||||||
|
hiddenEnvironments?: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHiddenEnvironments = () => {
|
||||||
|
const { value: globalStore, setValue: setGlobalStore } =
|
||||||
|
useGlobalLocalStorage();
|
||||||
|
const [hiddenEnvironments, setStoredHiddenEnvironments] = useState<
|
||||||
|
Set<string>
|
||||||
|
>(new Set(globalStore.hiddenEnvironments));
|
||||||
|
|
||||||
|
const setHiddenEnvironments = (environment: string) => {
|
||||||
|
setGlobalStore(params => {
|
||||||
|
const hiddenEnvironments = new Set(params.hiddenEnvironments);
|
||||||
|
if (hiddenEnvironments.has(environment)) {
|
||||||
|
hiddenEnvironments.delete(environment);
|
||||||
|
} else {
|
||||||
|
hiddenEnvironments.add(environment);
|
||||||
|
}
|
||||||
|
setStoredHiddenEnvironments(hiddenEnvironments);
|
||||||
|
return {
|
||||||
|
...globalStore,
|
||||||
|
hiddenEnvironments: hiddenEnvironments,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
hiddenEnvironments,
|
||||||
|
setHiddenEnvironments,
|
||||||
|
};
|
||||||
|
};
|
@ -12,7 +12,12 @@ export function getLocalStorageItem<T>(key: string): T | undefined {
|
|||||||
// Does nothing if the browser denies access.
|
// Does nothing if the browser denies access.
|
||||||
export function setLocalStorageItem(key: string, value: unknown) {
|
export function setLocalStorageItem(key: string, value: unknown) {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(key, JSON.stringify(value));
|
window.localStorage.setItem(
|
||||||
|
key,
|
||||||
|
JSON.stringify(value, (_key, value) =>
|
||||||
|
value instanceof Set ? [...value] : value
|
||||||
|
)
|
||||||
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user