Name input
This commit is contained in:
@@ -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");
|
||||
|
||||
68
web/packages/new/photos/components/NameInputDialog.tsx
Normal file
68
web/packages/new/photos/components/NameInputDialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user