[desktop] Move out of labs (#4088)

Prep for next release.
This commit is contained in:
Manav Rathi
2024-11-19 08:18:23 +05:30
committed by GitHub
10 changed files with 179 additions and 140 deletions

View File

@@ -78,7 +78,6 @@ type CollectionsProps = Omit<
*/
export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
shouldHide,
showPeopleSectionButton,
mode,
onChangeMode,
collectionSummaries,
@@ -193,7 +192,6 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
<>
<GalleryBarImpl
{...{
showPeopleSectionButton,
mode,
onChangeMode,
activeCollectionID,

View File

@@ -14,6 +14,7 @@ const DropDiv = styled("div")`
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
`;
const Overlay = styled("div")`
border-width: 8px;

View File

@@ -26,8 +26,7 @@ import {
import { useAppContext } from "@/new/photos/types/context";
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
import ChevronRight from "@mui/icons-material/ChevronRight";
import ScienceIcon from "@mui/icons-material/Science";
import { Box, Stack } from "@mui/material";
import { Stack } from "@mui/material";
import DropdownInput from "components/DropdownInput";
import { t } from "i18next";
import React, { useCallback, useEffect } from "react";
@@ -65,6 +64,15 @@ export const Preferences: React.FC<NestedSidebarDrawerVisibilityProps> = ({
/>
<Stack sx={{ px: "16px", py: "20px", gap: "24px" }}>
<LanguageSelector />
{isMLSupported && (
<MenuItemGroup>
<EnteMenuItem
endIcon={<ChevronRight />}
onClick={showMLSettings}
label={t("ml_search")}
/>
</MenuItemGroup>
)}
<EnteMenuItem
onClick={showMapSettings}
endIcon={<ChevronRight />}
@@ -75,21 +83,6 @@ export const Preferences: React.FC<NestedSidebarDrawerVisibilityProps> = ({
endIcon={<ChevronRight />}
label={t("advanced")}
/>
{isMLSupported && (
<Box>
<MenuSectionTitle
title={t("labs")}
icon={<ScienceIcon />}
/>
<MenuItemGroup>
<EnteMenuItem
endIcon={<ChevronRight />}
onClick={showMLSettings}
label={t("ml_search")}
/>
</MenuItemGroup>
</Box>
)}
</Stack>
</Stack>
<MapSettings

View File

@@ -844,10 +844,6 @@ export default function Gallery() {
return <div></div>;
}
// `peopleState` will be undefined only when ML is disabled, otherwise it'll
// be present, with empty arrays, even if people data is still syncing.
const showPeopleSectionButton = peopleState !== undefined;
return (
<GalleryContext.Provider
value={{
@@ -965,7 +961,6 @@ export default function Gallery() {
setActiveCollectionID: handleSetActiveCollectionID,
hiddenCollectionSummaries:
state.hiddenCollectionSummaries,
showPeopleSectionButton,
people:
(state.view.type == "people"
? state.view.visiblePeople

View File

@@ -49,6 +49,7 @@ body {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
}
.pswp__button--custom {

View File

@@ -18,7 +18,7 @@ export const SpaceBetweenFlex = styled("div")`
* A flex child that fills the entire flex direction, and shows its children
* after centering them both vertically and horizontally.
*/
export const CenteredBox = styled("div")`
export const CenteredFill = styled("div")`
flex: 1;
display: flex;
justify-content: center;

View File

@@ -41,13 +41,10 @@ import {
type ListChildComponentProps,
areEqual,
} from "react-window";
import { isMLSupported } from "../../services/ml";
import type { GalleryBarMode } from "./reducer";
export interface GalleryBarImplProps {
/**
* When `true`, the bar shows a button to switch to the people section.
*/
showPeopleSectionButton: boolean;
/**
* What are we displaying currently.
*/
@@ -100,7 +97,6 @@ export interface GalleryBarImplProps {
}
export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
showPeopleSectionButton,
mode,
onChangeMode,
collectionSummaries,
@@ -255,9 +251,7 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
sx={people.length ? {} : { borderBlockEndColor: "transparent" }}
>
<Row1>
<ModeIndicator
{...{ showPeopleSectionButton, mode, onChangeMode }}
/>
<ModeIndicator {...{ mode, onChangeMode }} />
{controls1}
</Row1>
<Row2>
@@ -314,20 +308,17 @@ export const Row2 = styled(Box)`
`;
const ModeIndicator: React.FC<
Pick<
GalleryBarImplProps,
"showPeopleSectionButton" | "mode" | "onChangeMode"
>
> = ({ showPeopleSectionButton, mode, onChangeMode }) => {
Pick<GalleryBarImplProps, "mode" | "onChangeMode">
> = ({ mode, onChangeMode }) => {
// Mode switcher is not shown in the hidden albums section.
if (mode == "hidden-albums") {
return <Typography>{t("hidden_albums")}</Typography>;
}
// Show the static mode indicator with only the "Albums" title if we have
// not been asked to show the people button (there are no other sections to
// switch to in such a case).
if (!showPeopleSectionButton) {
// Show the static mode indicator with only the "Albums" title if ML is not
// supported on this client (web), since there are no other sections to
// switch to in such a case.
if (!isMLSupported) {
return <Typography>{t("albums")}</Typography>;
}

View File

@@ -1,6 +1,9 @@
import { ActivityErrorIndicator } from "@/base/components/ErrorIndicator";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { CenteredBox, SpaceBetweenFlex } from "@/base/components/mui/Container";
import {
CenteredFill,
SpaceBetweenFlex,
} from "@/base/components/mui/Container";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { LoadingButton } from "@/base/components/mui/LoadingButton";
import {
@@ -691,15 +694,15 @@ const SuggestionsDialog: React.FC<SuggestionsDialogProps> = ({
sx={{ display: "flex", "&&&": { pt: 0 } }}
>
{state.activity == "fetching" ? (
<CenteredBox>
<CenteredFill>
<ActivityIndicator>
{t("people_suggestions_finding")}
</ActivityIndicator>
</CenteredBox>
</CenteredFill>
) : state.fetchFailed ? (
<CenteredBox>
<CenteredFill>
<ActivityErrorIndicator />
</CenteredBox>
</CenteredFill>
) : state.showChoices ? (
<SuggestionOrChoiceList
items={state.choices}
@@ -707,14 +710,14 @@ const SuggestionsDialog: React.FC<SuggestionsDialogProps> = ({
onUpdateItem={handleUpdateItem}
/>
) : state.suggestions.length == 0 ? (
<CenteredBox>
<CenteredFill>
<Typography
color="text.muted"
sx={{ textAlign: "center" }}
>
t{"people_suggestions_empty"}
</Typography>
</CenteredBox>
</CenteredFill>
) : (
<SuggestionOrChoiceList
items={state.suggestions}

View File

@@ -7,12 +7,15 @@
* there.
*/
import { CenteredFill } from "@/base/components/mui/Container";
import type { SearchOption } from "@/new/photos/services/search/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import { Typography } from "@mui/material";
import { Paper, Stack, Typography } from "@mui/material";
import { t } from "i18next";
import React from "react";
import React, { useState } from "react";
import { enableML } from "../../services/ml";
import { EnableML, FaceConsent } from "../sidebar/MLSettings";
import { useMLStatusSnapshot } from "../utils/use-snapshot";
import { useWrapAsyncOperation } from "../utils/use-wrap-async";
import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader";
/**
@@ -46,25 +49,62 @@ export const SearchResultsHeader: React.FC<SearchResultsHeaderProps> = ({
export const PeopleEmptyState: React.FC = () => {
const mlStatus = useMLStatusSnapshot();
const message =
mlStatus?.phase == "done"
? t("people_empty_too_few")
: t("syncing_wait");
switch (mlStatus?.phase) {
case "disabled":
return <PeopleEmptyStateDisabled />;
case "done":
return (
<PeopleEmptyStateMessage>
{t("people_empty_too_few")}
</PeopleEmptyStateMessage>
);
default:
return (
<PeopleEmptyStateMessage>
{t("syncing_wait")}
</PeopleEmptyStateMessage>
);
}
};
export const PeopleEmptyStateMessage: React.FC<React.PropsWithChildren> = ({
children,
}) => (
<CenteredFill>
<Typography
color="text.muted"
sx={{
mx: 1,
// Approximately compensate for the hidden section bar (86px),
// and then add a bit extra padding so that the message appears
// visually off the center, towards the top.
paddingBlockEnd: "126px",
}}
>
{children}
</Typography>
</CenteredFill>
);
export const PeopleEmptyStateDisabled: React.FC = () => {
const [showConsent, setShowConsent] = useState(false);
const handleConsent = useWrapAsyncOperation(async () => {
await enableML();
});
return (
<VerticallyCentered>
<Typography
color="text.muted"
sx={{
mx: 1,
// Approximately compensate for the hidden section bar (86px),
// and then add a bit extra padding so that the message appears
// visually off the center, towards the top.
paddingBlockEnd: "126px",
}}
>
{message}
</Typography>
</VerticallyCentered>
<Stack sx={{ alignItems: "center", flex: 1, overflow: "auto" }}>
<Paper sx={{ maxWidth: "390px", padding: "4px", mb: "2rem" }}>
{!showConsent ? (
<EnableML onEnable={() => setShowConsent(true)} />
) : (
<FaceConsent
onConsent={handleConsent}
onCancel={() => setShowConsent(false)}
/>
)}
</Paper>
</Stack>
);
};

View File

@@ -54,7 +54,7 @@ export const MLSettings: React.FC<NestedSidebarDrawerVisibilityProps> = ({
if (!mlStatus) {
component = <Loading />;
} else if (mlStatus.phase == "disabled") {
component = <EnableML onEnable={handleEnableML} />;
component = <EnableML onEnable={handleEnableML} showMagicSearchHint />;
} else {
component = (
<ManageML {...{ mlStatus }} onDisableML={handleDisableML} />
@@ -77,7 +77,7 @@ export const MLSettings: React.FC<NestedSidebarDrawerVisibilityProps> = ({
</Stack>
</NestedSidebarDrawer>
<FaceConsent
<FaceConsentDrawer
open={openFaceConsent}
onClose={() => setOpenFaceConsent(false)}
onRootClose={handleRootClose}
@@ -98,9 +98,16 @@ const Loading: React.FC = () => {
interface EnableMLProps {
/** Called when the user enables ML. */
onEnable: () => void;
/**
* If true, a footnote describing the magic search feature will be shown.
*/
showMagicSearchHint?: boolean;
}
const EnableML: React.FC<EnableMLProps> = ({ onEnable }) => {
export const EnableML: React.FC<EnableMLProps> = ({
onEnable,
showMagicSearchHint,
}) => {
const moreDetails = () =>
openURL("https://help.ente.io/photos/features/machine-learning");
@@ -118,35 +125,63 @@ const EnableML: React.FC<EnableMLProps> = ({ onEnable }) => {
{t("more_details")}
</Button>
</Stack>
<Typography color="text.faint" variant="small">
{t("ml_search_footnote")}
</Typography>
{showMagicSearchHint && (
<Typography color="text.faint" variant="small">
{t("ml_search_footnote")}
</Typography>
)}
</Stack>
);
};
type FaceConsentProps = NestedSidebarDrawerVisibilityProps & {
/** Called when the user provides their consent. */
onConsent: () => void;
};
type FaceConsentDrawerProps = NestedSidebarDrawerVisibilityProps &
Pick<FaceConsentProps, "onConsent">;
const FaceConsent: React.FC<FaceConsentProps> = ({
const FaceConsentDrawer: React.FC<FaceConsentDrawerProps> = ({
open,
onClose,
onRootClose,
onConsent,
}) => {
const [acceptTerms, setAcceptTerms] = useState(false);
useEffect(() => {
setAcceptTerms(false);
}, [open]);
const handleRootClose = () => {
onClose();
onRootClose();
};
return (
<NestedSidebarDrawer
{...{ open, onClose }}
onRootClose={handleRootClose}
>
<Stack spacing={"4px"} py={"12px"}>
<SidebarDrawerTitlebar
onClose={onClose}
onRootClose={handleRootClose}
title={t("ml_consent_title")}
/>
<FaceConsent onConsent={onConsent} onCancel={onClose} />
</Stack>
</NestedSidebarDrawer>
);
};
interface FaceConsentProps {
/** Called when the user provides their consent. */
onConsent: () => void;
/** Called when the user cancels out. */
onCancel: () => void;
}
export const FaceConsent: React.FC<FaceConsentProps> = ({
onConsent,
onCancel,
}) => {
const [acceptTerms, setAcceptTerms] = useState(false);
useEffect(() => {
setAcceptTerms(false);
}, []);
const privacyPolicyLink = (
<Link
target="_blank"
@@ -160,62 +195,44 @@ const FaceConsent: React.FC<FaceConsentProps> = ({
);
return (
<NestedSidebarDrawer
{...{ open, onClose }}
onRootClose={handleRootClose}
>
<Stack spacing={"4px"} py={"12px"}>
<SidebarDrawerTitlebar
onClose={onClose}
onRootClose={handleRootClose}
title={t("ml_consent_title")}
<Stack py={"20px"} px={"8px"} spacing={"32px"}>
<Typography component="div" color="text.muted" px={"8px"}>
<Trans
i18nKey={"ml_consent_description"}
components={{ a: privacyPolicyLink }}
/>
<Stack py={"20px"} px={"8px"} spacing={"32px"}>
<Typography component="div" color="text.muted" px={"8px"}>
<Trans
i18nKey={"ml_consent_description"}
components={{ a: privacyPolicyLink }}
</Typography>
<FormGroup sx={{ width: "100%" }}>
<FormControlLabel
sx={{
color: "text.muted",
ml: 0,
mt: 2,
}}
control={
<Checkbox
size="small"
checked={acceptTerms}
onChange={(e) => setAcceptTerms(e.target.checked)}
/>
</Typography>
<FormGroup sx={{ width: "100%" }}>
<FormControlLabel
sx={{
color: "text.muted",
ml: 0,
mt: 2,
}}
control={
<Checkbox
size="small"
checked={acceptTerms}
onChange={(e) =>
setAcceptTerms(e.target.checked)
}
/>
}
label={t("ml_consent_confirmation")}
/>
</FormGroup>
<Stack px={"8px"} spacing={"8px"}>
<Button
color={"accent"}
size="large"
disabled={!acceptTerms}
onClick={onConsent}
>
{t("ml_consent")}
</Button>
<Button
color={"secondary"}
size="large"
onClick={onClose}
>
{t("cancel")}
</Button>
</Stack>
</Stack>
}
label={t("ml_consent_confirmation")}
/>
</FormGroup>
<Stack px={"8px"} spacing={"8px"}>
<Button
color={"accent"}
size="large"
disabled={!acceptTerms}
onClick={onConsent}
>
{t("ml_consent")}
</Button>
<Button color={"secondary"} size="large" onClick={onCancel}>
{t("cancel")}
</Button>
</Stack>
</NestedSidebarDrawer>
</Stack>
);
};