Name input

This commit is contained in:
Manav Rathi
2024-09-26 15:49:25 +05:30
parent f9cbce66c0
commit aef32027a1
5 changed files with 140 additions and 16 deletions

View File

@@ -21,7 +21,7 @@ import { t } from "i18next";
import React from "react";
import type { NewAppContextPhotos } from "../../types/context";
import { SpaceBetweenFlex } from "../mui-custom";
import { useWrapAsyncOperation } from "../use-wrap";
import { useWrapLoad, useWrapLoadError } from "../use-wrap";
import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader";
/**
@@ -64,15 +64,18 @@ export const PersonListHeader: React.FC<PeopleListHeaderProps> = ({
// TODO-Cluster
const hasOptions = process.env.NEXT_PUBLIC_ENTE_WIP_CL;
const wrap = useWrapAsyncOperation(appContext);
const wrapLoadError = useWrapLoadError(appContext);
const addPerson = wrap(async () => {
const wrapLoad = useWrapLoad(appContext);
const addPerson = wrapLoadError(async () => {
console.log("add person");
await wait(2000);
throw new Error("test");
});
const rename = wrap(async () => {
const rename = wrapLoad(async () => {
console.log("add person");
await wait(2000);
throw new Error("test");

View File

@@ -0,0 +1,68 @@
import log from "@/base/log";
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";
import { t } from "i18next";
import React from "react";
import type { DialogVisiblityProps } from "./mui-custom";
type NameInputDialogProps = DialogVisiblityProps & {
/** Title of the dialog. */
title: string;
/** Placeholder string to show in the text input when it is empty. */
placeholder: string;
/** The existing value, if any, of the text input. */
initialValue: string | undefined;
/** Title of the submit button */
submitButtonTitle: string;
/**
* Callback invoked when the submit button is pressed.
*
* @param name The current value of the text input.
* */
onSubmit: (name: string) => Promise<void>;
};
/**
* A dialog that can be used to ask for a name or some other such singular text
* input.
*
* See also: {@link CollectionNamer}, its older sibling.
*/
export const NameInputDialog: React.FC<NameInputDialogProps> = ({
open,
onClose,
title,
placeholder,
initialValue,
submitButtonTitle,
onSubmit,
}) => {
const handleSubmit: SingleInputFormProps["callback"] = async (
inputValue,
setFieldError,
) => {
try {
await onSubmit(inputValue);
onClose();
} catch (e) {
log.error(`Error when submitting value ${inputValue}`, e);
setFieldError(t("UNKNOWN_ERROR"));
}
};
return (
<DialogBoxV2 open={open} onClose={onClose} attributes={{ title }}>
<SingleInputForm
fieldType="text"
placeholder={placeholder}
initialValue={initialValue}
callback={handleSubmit}
buttonText={submitButtonTitle}
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
secondaryButtonAction={onClose}
/>
</DialogBoxV2>
);
};

View File

@@ -1,5 +1,15 @@
import { Box, IconButton, styled } from "@mui/material";
/**
* Common props to control the display of a dialog-like component.
*/
export interface DialogVisiblityProps {
/** If `true`, the dialog is shown. */
open: boolean;
/** Callback fired when the dialog wants to be closed. */
onClose: () => void;
}
/**
* A MUI {@link IconButton} filled in with at faint background.
*/

View File

@@ -4,17 +4,21 @@ import type { NewAppContextPhotos } from "../types/context";
/**
* Return a wrap function.
*
* This wrap function itself takes an async function, and returns new
* function by wrapping an async function in an error handler, showing the
* global loading bar when the function runs.
* This returned wrap function itself takes an async function, and will return a
* new function that wraps the provided async function (a) in an error handler,
* and (b) shows the global loading bar when the function runs.
*
* This legend of the three functions that are involved might help:
*
* - useWrap: () => wrap
* - wrap: (f) => void
* - f: async () => Promise<void>
*/
export const useWrapAsyncOperation = (
export const useWrapLoadError = (
/** See: [Note: Migrating components that need the app context]. */
appContext: NewAppContextPhotos,
) => {
const { startLoading, finishLoading, onGenericError } = appContext;
const wrap = React.useCallback(
{ startLoading, finishLoading, onGenericError }: NewAppContextPhotos,
) =>
React.useCallback(
(f: () => Promise<void>) => {
const wrapped = async () => {
startLoading();
@@ -31,5 +35,25 @@ export const useWrapAsyncOperation = (
[onGenericError, startLoading, finishLoading],
);
return wrap;
};
/**
* A variant of {@link useWrapLoadError} that does not handle the error, only
* does the loading indicator.
*/
export const useWrapLoad = (
/** See: [Note: Migrating components that need the app context]. */
{ startLoading, finishLoading }: NewAppContextPhotos,
) =>
React.useCallback(
(f: () => Promise<void>) => {
const wrapped = async () => {
startLoading();
try {
await f();
} finally {
finishLoading();
}
};
return (): void => void wrapped();
},
[startLoading, finishLoading],
);

View File

@@ -1,7 +1,8 @@
import { masterKeyFromSession } from "@/base/session-store";
import { wipClusterEnable } from ".";
import type { EnteFile } from "../../types/file";
import { getLocalFiles } from "../files";
import { savedCGroupUserEntities } from "../user-entity";
import { addUserEntity, savedCGroupUserEntities } from "../user-entity";
import type { FaceCluster } from "./cluster";
import { getFaceIndexes, savedFaceClusters } from "./db";
import { fileIDFromFaceID } from "./face";
@@ -252,3 +253,21 @@ export const reconstructPeople = async (): Promise<Person[]> => {
.filter((c) => !!c)
.sort((a, b) => b.fileIDs.length - a.fileIDs.length);
};
/**
* Convert a cluster into a named cgroup, updating both remote and local state.
*
* @param name Name of the new cgroup user entity.
*
* @param cluster The underlying cluster to use to populate the cgroup.
*/
export const addPerson = async (name: string, cluster: FaceCluster) =>
addUserEntity(
"cgroup",
{
name,
assigned: [cluster],
isHidden: false,
},
await masterKeyFromSession(),
);