forked from actualbudget/actual
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
01659d5
commit 790b18d
Showing
29 changed files
with
838 additions
and
510 deletions.
There are no files selected for viewing
143 changes: 143 additions & 0 deletions
143
packages/desktop-client/src/components/PrivacyFilter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import React, { | ||
useState, | ||
useCallback, | ||
Children, | ||
type ComponentPropsWithRef, | ||
type ReactNode, | ||
} from 'react'; | ||
|
||
import usePrivacyMode from 'loot-core/src/client/privacy'; | ||
|
||
import useFeatureFlag from '../hooks/useFeatureFlag'; | ||
import { useResponsive } from '../ResponsiveProvider'; | ||
|
||
import { View } from './common'; | ||
|
||
export type ConditionalPrivacyFilterProps = { | ||
children: ReactNode; | ||
privacyFilter?: boolean | PrivacyFilterProps; | ||
defaultPrivacyFilterProps?: PrivacyFilterProps; | ||
}; | ||
export function ConditionalPrivacyFilter({ | ||
children, | ||
privacyFilter, | ||
defaultPrivacyFilterProps, | ||
}: ConditionalPrivacyFilterProps) { | ||
let renderPrivacyFilter = (children, mergedProps) => ( | ||
<PrivacyFilter {...mergedProps}>{children}</PrivacyFilter> | ||
); | ||
return privacyFilter ? ( | ||
typeof privacyFilter === 'boolean' ? ( | ||
<PrivacyFilter {...defaultPrivacyFilterProps}>{children}</PrivacyFilter> | ||
) : ( | ||
renderPrivacyFilter( | ||
children, | ||
mergeConditionalPrivacyFilterProps( | ||
defaultPrivacyFilterProps, | ||
privacyFilter, | ||
), | ||
) | ||
) | ||
) : ( | ||
<>{Children.toArray(children)}</> | ||
); | ||
} | ||
|
||
type PrivacyFilterProps = ComponentPropsWithRef<typeof View> & { | ||
activationFilters?: (boolean | (() => boolean))[]; | ||
blurIntensity?: number; | ||
}; | ||
export default function PrivacyFilter({ | ||
activationFilters, | ||
blurIntensity, | ||
children, | ||
...props | ||
}: PrivacyFilterProps) { | ||
let privacyModeFeatureFlag = useFeatureFlag('privacyMode'); | ||
let privacyMode = usePrivacyMode(); | ||
// Limit mobile support for now. | ||
let { isNarrowWidth } = useResponsive(); | ||
let activate = | ||
privacyMode && | ||
!isNarrowWidth && | ||
(!activationFilters || | ||
activationFilters.every(value => | ||
typeof value === 'boolean' ? value : value(), | ||
)); | ||
|
||
let blurAmount = blurIntensity != null ? `${blurIntensity}px` : '3px'; | ||
|
||
return !privacyModeFeatureFlag || !activate ? ( | ||
<>{Children.toArray(children)}</> | ||
) : ( | ||
<BlurredOverlay blurIntensity={blurAmount} {...props}> | ||
{children} | ||
</BlurredOverlay> | ||
); | ||
} | ||
|
||
function BlurredOverlay({ blurIntensity, children, ...props }) { | ||
let [hovered, setHovered] = useState(false); | ||
let onHover = useCallback(() => setHovered(true), [setHovered]); | ||
let onHoverEnd = useCallback(() => setHovered(false), [setHovered]); | ||
|
||
let blurStyle = { | ||
...(!hovered && { | ||
filter: `blur(${blurIntensity})`, | ||
WebkitFilter: `blur(${blurIntensity})`, | ||
}), | ||
}; | ||
|
||
let { style, ...restProps } = props; | ||
|
||
return ( | ||
<View | ||
style={{ | ||
display: style?.display ? style.display : 'inline-flex', | ||
...blurStyle, | ||
...style, | ||
}} | ||
onPointerEnter={onHover} | ||
onPointerLeave={onHoverEnd} | ||
{...restProps} | ||
> | ||
{children} | ||
</View> | ||
); | ||
} | ||
export function mergeConditionalPrivacyFilterProps( | ||
defaultPrivacyFilterProps: PrivacyFilterProps = {}, | ||
privacyFilter: ConditionalPrivacyFilterProps['privacyFilter'], | ||
): ConditionalPrivacyFilterProps['privacyFilter'] { | ||
if (privacyFilter == null || privacyFilter === false) { | ||
return privacyFilter; | ||
} | ||
|
||
if (privacyFilter === true) { | ||
return defaultPrivacyFilterProps; | ||
} | ||
|
||
return merge(defaultPrivacyFilterProps, privacyFilter); | ||
} | ||
|
||
function merge(initialValue, ...objects) { | ||
return objects.reduce((prev, current) => { | ||
Object.keys(current).forEach(key => { | ||
const pValue = prev[key]; | ||
const cValue = current[key]; | ||
|
||
if (Array.isArray(pValue) && Array.isArray(cValue)) { | ||
prev[key] = pValue.concat(...cValue); | ||
} else if (isObject(pValue) && isObject(cValue)) { | ||
prev[key] = merge(pValue, cValue); | ||
} else { | ||
prev[key] = cValue; | ||
} | ||
}); | ||
return prev; | ||
}, initialValue); | ||
} | ||
|
||
function isObject(obj) { | ||
return obj && typeof obj === 'object'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.