Improve UI feedback
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
mlStatusSnapshot,
|
||||
mlStatusSubscribe,
|
||||
pauseML,
|
||||
resumeML,
|
||||
type MLStatus,
|
||||
} from "@/new/photos/services/ml";
|
||||
import { EnteDrawer } from "@/new/shared/components/EnteDrawer";
|
||||
@@ -70,13 +71,7 @@ export const MLSettings: React.FC<MLSettingsProps> = ({
|
||||
else onClose();
|
||||
};
|
||||
|
||||
// The user may've changed the remote flag on a different device, so in both
|
||||
// cases (enable or resume), do the same flow:
|
||||
//
|
||||
// - If remote flag is not set, then show the consent dialog
|
||||
// - Otherwise enable ML (both locally and on remote).
|
||||
//
|
||||
const handleEnableOrResumeML = async () => {
|
||||
const handleEnableML = async () => {
|
||||
startLoading();
|
||||
try {
|
||||
if (!(await getIsMLEnabledRemote())) {
|
||||
@@ -106,15 +101,6 @@ export const MLSettings: React.FC<MLSettingsProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleLocal = async () => {
|
||||
try {
|
||||
isMLEnabled() ? pauseML() : await handleEnableOrResumeML();
|
||||
} catch (e) {
|
||||
log.error("Failed to toggle local state of ML", e);
|
||||
somethingWentWrong();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisableML = async () => {
|
||||
startLoading();
|
||||
try {
|
||||
@@ -131,12 +117,11 @@ export const MLSettings: React.FC<MLSettingsProps> = ({
|
||||
if (!mlStatus) {
|
||||
component = <Loading />;
|
||||
} else if (mlStatus.phase == "disabled") {
|
||||
component = <EnableML onEnable={handleEnableOrResumeML} />;
|
||||
component = <EnableML onEnable={handleEnableML} />;
|
||||
} else {
|
||||
component = (
|
||||
<ManageML
|
||||
{...{ mlStatus, setDialogBoxAttributesV2 }}
|
||||
onToggleLocal={handleToggleLocal}
|
||||
onDisableML={handleDisableML}
|
||||
/>
|
||||
);
|
||||
@@ -321,8 +306,6 @@ const FaceConsent: React.FC<FaceConsentProps> = ({
|
||||
interface ManageMLProps {
|
||||
/** The {@link MLStatus}; a non-disabled one. */
|
||||
mlStatus: Exclude<MLStatus, { phase: "disabled" }>;
|
||||
/** Called when the user wants to toggle the ML status locally. */
|
||||
onToggleLocal: () => void;
|
||||
/** Called when the user wants to disable ML. */
|
||||
onDisableML: () => void;
|
||||
/** Subset of appContext. */
|
||||
@@ -331,12 +314,31 @@ interface ManageMLProps {
|
||||
|
||||
const ManageML: React.FC<ManageMLProps> = ({
|
||||
mlStatus,
|
||||
onToggleLocal,
|
||||
onDisableML,
|
||||
setDialogBoxAttributesV2,
|
||||
}) => {
|
||||
const { phase, nSyncedFiles, nTotalFiles } = mlStatus;
|
||||
|
||||
let status: string;
|
||||
switch (phase) {
|
||||
case "paused":
|
||||
status = pt("Paused");
|
||||
break;
|
||||
case "indexing":
|
||||
status = pt("Indexing");
|
||||
break;
|
||||
case "scheduled":
|
||||
status = pt("Scheduled");
|
||||
break;
|
||||
// TODO: Clustering
|
||||
default:
|
||||
status = pt("Done");
|
||||
break;
|
||||
}
|
||||
const processed = `${nSyncedFiles} / ${nTotalFiles}`;
|
||||
|
||||
const handleToggleLocal = () => (isMLEnabled() ? pauseML() : resumeML());
|
||||
|
||||
const confirmDisableML = () => {
|
||||
setDialogBoxAttributesV2({
|
||||
title: pt("Disable ML search"),
|
||||
@@ -373,19 +375,19 @@ const ManageML: React.FC<ManageMLProps> = ({
|
||||
label={pt("On this device")}
|
||||
variant="toggle"
|
||||
checked={phase != "paused"}
|
||||
onClick={onToggleLocal}
|
||||
onClick={handleToggleLocal}
|
||||
/>
|
||||
</MenuItemGroup>
|
||||
</Stack>
|
||||
<Paper variant="outlined">
|
||||
<Stack gap={4} px={2} py={2}>
|
||||
<Stack gap={"28px"} px={2} py={"20px"}>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={2}
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<Typography color="text.muted">Status</Typography>
|
||||
<Typography>Indexing</Typography>
|
||||
<Typography>{status}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
@@ -393,9 +395,7 @@ const ManageML: React.FC<ManageMLProps> = ({
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<Typography color="text.muted">Processed</Typography>
|
||||
<Typography textAlign="right">
|
||||
{`${nSyncedFiles} / ${nTotalFiles}`}
|
||||
</Typography>
|
||||
<Typography textAlign="right">{processed}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -149,6 +149,7 @@ export const enableML = async () => {
|
||||
setIsMLEnabledLocally(true);
|
||||
_isMLEnabledRemote = true;
|
||||
_isMLEnabledLocal = true;
|
||||
setInterimScheduledStatus();
|
||||
triggerStatusUpdate();
|
||||
triggerMLSync();
|
||||
};
|
||||
@@ -188,6 +189,7 @@ export const pauseML = () => {
|
||||
export const resumeML = () => {
|
||||
setIsMLEnabledLocally(true);
|
||||
_isMLEnabledLocal = true;
|
||||
setInterimScheduledStatus();
|
||||
triggerStatusUpdate();
|
||||
triggerMLSync();
|
||||
};
|
||||
@@ -347,8 +349,11 @@ export const mlStatusSnapshot = (): MLStatus | undefined => {
|
||||
const triggerStatusUpdate = () => void updateMLStatusSnapshot();
|
||||
|
||||
/** Unconditionally update of the {@link MLStatus} snapshot. */
|
||||
const updateMLStatusSnapshot = async () => {
|
||||
_mlStatusSnapshot = await getMLStatus();
|
||||
const updateMLStatusSnapshot = async () =>
|
||||
setMLStatusSnapshot(await getMLStatus());
|
||||
|
||||
const setMLStatusSnapshot = (snapshot: MLStatus) => {
|
||||
_mlStatusSnapshot = snapshot;
|
||||
_mlStatusListeners.forEach((l) => l());
|
||||
};
|
||||
|
||||
@@ -358,7 +363,7 @@ const updateMLStatusSnapshot = async () => {
|
||||
* Precondition: ML must be enabled on remote, though it is fine if it is paused
|
||||
* locally.
|
||||
*/
|
||||
export const getMLStatus = async (): Promise<MLStatus> => {
|
||||
const getMLStatus = async (): Promise<MLStatus> => {
|
||||
if (!_isMLEnabledRemote) return { phase: "disabled" };
|
||||
|
||||
const { indexedCount, indexableCount } = await indexableAndIndexedCounts();
|
||||
@@ -383,6 +388,26 @@ export const getMLStatus = async (): Promise<MLStatus> => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* When the user enables or resumes ML, we wish to give immediate feedback.
|
||||
*
|
||||
* So this is an intermediate state with possibly incorrect counts (but correct
|
||||
* phase) that is set immediately to trigger a UI update. It uses the counts
|
||||
* from the last known status, just updates the phase.
|
||||
*
|
||||
* Once the worker is initialized and the correct counts fetched, this will
|
||||
* update to the correct state (should take less than one second).
|
||||
*/
|
||||
const setInterimScheduledStatus = () => {
|
||||
let nSyncedFiles = 0,
|
||||
nTotalFiles = 0;
|
||||
if (_mlStatusSnapshot && _mlStatusSnapshot.phase != "disabled") {
|
||||
nSyncedFiles = _mlStatusSnapshot.nSyncedFiles;
|
||||
nTotalFiles = _mlStatusSnapshot.nTotalFiles;
|
||||
}
|
||||
setMLStatusSnapshot({ phase: "scheduled", nSyncedFiles, nTotalFiles });
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the IDs of all the faces in the given {@link enteFile} that are not
|
||||
* associated with a person cluster.
|
||||
|
||||
Reference in New Issue
Block a user