[web] Remove emotion cache (#1272)
- Still doesn't work in dev mode - Prepares ground for removing bootstrap
This commit is contained in:
commit
a9b92b9bfa
45 changed files with 575 additions and 2198 deletions
|
@ -31,7 +31,7 @@ are built against `electron`'s packaged `node` version. We use
|
|||
to rebuild those modules automatically after each `yarn install` by invoking it
|
||||
in as the `postinstall` step in our package.json.
|
||||
|
||||
### lint and lint-fix
|
||||
### lint, lint-fix
|
||||
|
||||
Use `yarn lint` to check that your code formatting is as expected, and that
|
||||
there are no linter errors. Use `yarn lint-fix` to try and automatically fix the
|
||||
|
|
|
@ -32,7 +32,7 @@ yarn dev
|
|||
|
||||
That's it. The web app will automatically hot reload when you make changes.
|
||||
|
||||
If you're new to web development and unsure about how to get started, or are
|
||||
If you're new to web development and unsure about how to get started, or are
|
||||
facing some problems when running the above steps, see [docs/new](docs/new.md).
|
||||
|
||||
## Other apps
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { setupI18n } from "@/ui/i18n";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import { APPS, APP_TITLES } from "@ente/shared/apps/constants";
|
||||
import { EnteAppProps } from "@ente/shared/apps/types";
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import {
|
||||
|
@ -15,9 +13,9 @@ import HTTPService from "@ente/shared/network/HTTPService";
|
|||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import createEmotionCache from "@ente/shared/themes/createEmotionCache";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
|
@ -31,10 +29,7 @@ interface AppContextProps {
|
|||
|
||||
export const AppContext = createContext<AppContextProps>({} as AppContextProps);
|
||||
|
||||
// Client-side cache, shared for the whole session of the user in the browser.
|
||||
const clientSideEmotionCache = createEmotionCache();
|
||||
|
||||
export default function App(props: EnteAppProps) {
|
||||
export default function App(props: AppProps) {
|
||||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||
|
||||
const [showNavbar, setShowNavBar] = useState(false);
|
||||
|
@ -54,11 +49,7 @@ export default function App(props: EnteAppProps) {
|
|||
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
Component,
|
||||
emotionCache = clientSideEmotionCache,
|
||||
pageProps,
|
||||
} = props;
|
||||
const { Component, pageProps } = props;
|
||||
|
||||
const [themeColor] = useLocalState(LS_KEYS.THEME, THEME_COLOR.DARK);
|
||||
|
||||
|
@ -87,7 +78,7 @@ export default function App(props: EnteAppProps) {
|
|||
|
||||
// TODO: Localise APP_TITLES
|
||||
return (
|
||||
<CacheProvider value={emotionCache}>
|
||||
<>
|
||||
<Head>
|
||||
<title>{APP_TITLES.get(APPS.ACCOUNTS)}</title>
|
||||
<meta
|
||||
|
@ -128,9 +119,9 @@ export default function App(props: EnteAppProps) {
|
|||
</Overlay>
|
||||
)}
|
||||
{showNavbar && <AppNavbar isMobile={isMobile} />}
|
||||
<Component {...pageProps} />
|
||||
{isI18nReady && <Component {...pageProps} />}
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import DocumentPage, {
|
||||
EnteDocumentProps,
|
||||
} from "@ente/shared/next/pages/_document";
|
||||
import DocumentPage from "@ente/shared/next/pages/_document";
|
||||
|
||||
export default function Document(props: EnteDocumentProps) {
|
||||
return <DocumentPage {...props} />;
|
||||
}
|
||||
export default DocumentPage;
|
||||
|
|
|
@ -150,21 +150,6 @@ body {
|
|||
background-color: #51cd7c;
|
||||
}
|
||||
|
||||
.carousel-inner {
|
||||
padding-bottom: 50px !important;
|
||||
}
|
||||
|
||||
.carousel-indicators li {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.carousel-indicators .active {
|
||||
background-color: #51cd7c;
|
||||
}
|
||||
|
||||
div.otp-input input {
|
||||
width: 36px !important;
|
||||
height: 36px;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { t } from "i18next";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { setupI18n } from "@/ui/i18n";
|
||||
import {
|
||||
APPS,
|
||||
APP_TITLES,
|
||||
CLIENT_PACKAGE_NAMES,
|
||||
} from "@ente/shared/apps/constants";
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import {
|
||||
|
@ -10,32 +12,26 @@ import {
|
|||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import {
|
||||
clearLogsIfLocalStorageLimitExceeded,
|
||||
logStartupMessage,
|
||||
} from "@ente/shared/logging/web";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import LoadingBar from "react-top-loading-bar";
|
||||
|
||||
import { setupI18n } from "@/ui/i18n";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import {
|
||||
APP_TITLES,
|
||||
APPS,
|
||||
CLIENT_PACKAGE_NAMES,
|
||||
} from "@ente/shared/apps/constants";
|
||||
import { EnteAppProps } from "@ente/shared/apps/types";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import createEmotionCache from "@ente/shared/themes/createEmotionCache";
|
||||
import { SetTheme } from "@ente/shared/themes/types";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { t } from "i18next";
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
import LoadingBar from "react-top-loading-bar";
|
||||
import "../../public/css/global.css";
|
||||
|
||||
type AppContextType = {
|
||||
|
@ -51,15 +47,8 @@ type AppContextType = {
|
|||
|
||||
export const AppContext = createContext<AppContextType>(null);
|
||||
|
||||
// Client-side cache, shared for the whole session of the user in the browser.
|
||||
const clientSideEmotionCache = createEmotionCache();
|
||||
|
||||
export default function App(props: EnteAppProps) {
|
||||
const {
|
||||
Component,
|
||||
emotionCache = clientSideEmotionCache,
|
||||
pageProps,
|
||||
} = props;
|
||||
export default function App(props: AppProps) {
|
||||
const { Component, pageProps } = props;
|
||||
const router = useRouter();
|
||||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
@ -141,7 +130,7 @@ export default function App(props: EnteAppProps) {
|
|||
});
|
||||
|
||||
return (
|
||||
<CacheProvider value={emotionCache}>
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{isI18nReady
|
||||
|
@ -195,9 +184,11 @@ export default function App(props: EnteAppProps) {
|
|||
<EnteSpinner />
|
||||
</Overlay>
|
||||
)}
|
||||
<Component setLoading={setLoading} {...pageProps} />
|
||||
{isI18nReady && (
|
||||
<Component setLoading={setLoading} {...pageProps} />
|
||||
)}
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import DocumentPage, {
|
||||
EnteDocumentProps,
|
||||
} from "@ente/shared/next/pages/_document";
|
||||
import DocumentPage from "@ente/shared/next/pages/_document";
|
||||
|
||||
export default function Document(props: EnteDocumentProps) {
|
||||
return <DocumentPage {...props} />;
|
||||
}
|
||||
export default DocumentPage;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
"@tensorflow/tfjs-converter": "^4.10.0",
|
||||
"@tensorflow/tfjs-core": "^4.10.0",
|
||||
"@tensorflow/tfjs-tflite": "0.0.1-alpha.7",
|
||||
"@zip.js/zip.js": "2.4.2",
|
||||
"bip39": "^3.0.4",
|
||||
"blazeface-back": "^0.0.9",
|
||||
"bootstrap": "^4.5.2",
|
||||
|
@ -45,6 +44,7 @@
|
|||
"p-queue": "^7.1.0",
|
||||
"photoswipe": "file:./thirdparty/photoswipe",
|
||||
"piexifjs": "^1.0.6",
|
||||
"pure-react-carousel": "^1.30.1",
|
||||
"react-bootstrap": "^1.3.0",
|
||||
"react-datepicker": "^4.16.0",
|
||||
"react-dropzone": "^11.2.4",
|
||||
|
|
|
@ -7,11 +7,11 @@ import {
|
|||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
LinearProgress,
|
||||
styled,
|
||||
} from "@mui/material";
|
||||
import { ExportStage } from "constants/export";
|
||||
import { t } from "i18next";
|
||||
import { ProgressBar } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
import { ExportProgress } from "types/export";
|
||||
|
||||
|
@ -69,21 +69,19 @@ export default function ExportInProgress(props: Props) {
|
|||
)}
|
||||
</Box>
|
||||
<FlexWrapper px={1}>
|
||||
<ProgressBar
|
||||
style={{ width: "100%" }}
|
||||
now={
|
||||
showIndeterminateProgress()
|
||||
? 100
|
||||
: Math.round(
|
||||
((props.exportProgress.success +
|
||||
props.exportProgress.failed) *
|
||||
100) /
|
||||
props.exportProgress.total,
|
||||
)
|
||||
}
|
||||
animated
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
{showIndeterminateProgress() ? (
|
||||
<LinearProgress />
|
||||
) : (
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={Math.round(
|
||||
((props.exportProgress.success +
|
||||
props.exportProgress.failed) *
|
||||
100) /
|
||||
props.exportProgress.total,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
</VerticallyCentered>
|
||||
</DialogContent>
|
||||
|
|
299
web/apps/photos/src/components/FixCreationTime.tsx
Normal file
299
web/apps/photos/src/components/FixCreationTime.tsx
Normal file
|
@ -0,0 +1,299 @@
|
|||
import { Row, Value } from "@ente/shared/components/Container";
|
||||
import DialogBox from "@ente/shared/components/DialogBox/";
|
||||
import { Button, LinearProgress } from "@mui/material";
|
||||
import EnteDateTimePicker from "components/EnteDateTimePicker";
|
||||
import { ComfySpan } from "components/ExportInProgress";
|
||||
import { Formik } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
import React, { ChangeEvent, useContext, useEffect, useState } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { updateCreationTimeWithExif } from "services/updateCreationTimeWithExif";
|
||||
import { EnteFile } from "types/file";
|
||||
|
||||
export interface FixCreationTimeAttributes {
|
||||
files: EnteFile[];
|
||||
}
|
||||
|
||||
type Step = "running" | "completed" | "error";
|
||||
|
||||
export enum FIX_OPTIONS {
|
||||
DATE_TIME_ORIGINAL,
|
||||
DATE_TIME_DIGITIZED,
|
||||
METADATA_DATE,
|
||||
CUSTOM_TIME,
|
||||
}
|
||||
|
||||
interface formValues {
|
||||
option: FIX_OPTIONS;
|
||||
customTime: Date;
|
||||
}
|
||||
|
||||
interface FixCreationTimeProps {
|
||||
isOpen: boolean;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
attributes: FixCreationTimeAttributes;
|
||||
}
|
||||
|
||||
const FixCreationTime: React.FC<FixCreationTimeProps> = (props) => {
|
||||
const [step, setStep] = useState<Step | undefined>();
|
||||
const [progressTracker, setProgressTracker] = useState({
|
||||
current: 0,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO (MR): Not sure why this is needed
|
||||
if (props.attributes && props.isOpen && step !== "running") {
|
||||
setStep(undefined);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
const startFix = async (option: FIX_OPTIONS, customTime: Date) => {
|
||||
setStep("running");
|
||||
const failed = await updateCreationTimeWithExif(
|
||||
props.attributes.files,
|
||||
option,
|
||||
customTime,
|
||||
setProgressTracker,
|
||||
);
|
||||
setStep(failed ? "error" : "completed");
|
||||
await galleryContext.syncWithRemote();
|
||||
};
|
||||
|
||||
const onSubmit = (values: formValues) => {
|
||||
startFix(Number(values.option), new Date(values.customTime));
|
||||
};
|
||||
|
||||
const title =
|
||||
step === "running"
|
||||
? t("FIX_CREATION_TIME_IN_PROGRESS")
|
||||
: t("FIX_CREATION_TIME");
|
||||
|
||||
const message = messageForStep(step);
|
||||
|
||||
if (!props.attributes) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogBox
|
||||
open={props.isOpen}
|
||||
onClose={props.hide}
|
||||
attributes={{ title, nonClosable: true }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...(step === "running" ? { alignItems: "center" } : {}),
|
||||
}}
|
||||
>
|
||||
{message && <div>{message}</div>}
|
||||
|
||||
{step === "running" && (
|
||||
<FixCreationTimeRunning progressTracker={progressTracker} />
|
||||
)}
|
||||
<Formik<formValues>
|
||||
initialValues={{
|
||||
option: FIX_OPTIONS.DATE_TIME_ORIGINAL,
|
||||
customTime: new Date(),
|
||||
}}
|
||||
validateOnBlur={false}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ values, handleChange, handleSubmit }) => (
|
||||
<>
|
||||
{(step === undefined || step === "error") && (
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<FixCreationTimeOptions
|
||||
handleChange={handleChange}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FixCreationTimeFooter
|
||||
step={step}
|
||||
startFix={handleSubmit}
|
||||
hide={props.hide}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</DialogBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default FixCreationTime;
|
||||
|
||||
const messageForStep = (step?: Step) => {
|
||||
switch (step) {
|
||||
case undefined:
|
||||
return t("UPDATE_CREATION_TIME_NOT_STARTED");
|
||||
case "running":
|
||||
return undefined;
|
||||
case "completed":
|
||||
return t("UPDATE_CREATION_TIME_COMPLETED");
|
||||
case "error":
|
||||
return t("UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR");
|
||||
}
|
||||
};
|
||||
|
||||
const Option = ({
|
||||
value,
|
||||
selected,
|
||||
onChange,
|
||||
label,
|
||||
}: {
|
||||
value: FIX_OPTIONS;
|
||||
selected: FIX_OPTIONS;
|
||||
onChange: (e: string | ChangeEvent<any>) => void;
|
||||
label: string;
|
||||
}) => (
|
||||
<Form.Check
|
||||
name="group1"
|
||||
style={{
|
||||
margin: "5px 0",
|
||||
color: value !== Number(selected) ? "#aaa" : "#fff",
|
||||
}}
|
||||
>
|
||||
<Form.Check.Input
|
||||
id={value.toString()}
|
||||
type="radio"
|
||||
value={value}
|
||||
checked={value === Number(selected)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Form.Check.Label
|
||||
style={{ cursor: "pointer" }}
|
||||
htmlFor={value.toString()}
|
||||
>
|
||||
{label}
|
||||
</Form.Check.Label>
|
||||
</Form.Check>
|
||||
);
|
||||
|
||||
function FixCreationTimeOptions({ handleChange, values }) {
|
||||
return (
|
||||
<Form noValidate>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.DATE_TIME_ORIGINAL}
|
||||
onChange={handleChange("option")}
|
||||
label={t("DATE_TIME_ORIGINAL")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.DATE_TIME_DIGITIZED}
|
||||
onChange={handleChange("option")}
|
||||
label={t("DATE_TIME_DIGITIZED")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.METADATA_DATE}
|
||||
onChange={handleChange("option")}
|
||||
label={t("METADATA_DATE")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Value width="50%">
|
||||
<Option
|
||||
value={FIX_OPTIONS.CUSTOM_TIME}
|
||||
onChange={handleChange("option")}
|
||||
label={t("CUSTOM_TIME")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Value>
|
||||
{Number(values.option) === FIX_OPTIONS.CUSTOM_TIME && (
|
||||
<Value width="40%">
|
||||
<EnteDateTimePicker
|
||||
onSubmit={(x: Date) =>
|
||||
handleChange("customTime")(x.toUTCString())
|
||||
}
|
||||
/>
|
||||
</Value>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
const FixCreationTimeFooter = ({ step, startFix, ...props }) => {
|
||||
return (
|
||||
step !== "running" && (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
marginTop: "30px",
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
{(step === undefined || step === "error") && (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
props.hide();
|
||||
}}
|
||||
>
|
||||
{t("CANCEL")}
|
||||
</Button>
|
||||
)}
|
||||
{step === "completed" && (
|
||||
<Button color="primary" size="large" onClick={props.hide}>
|
||||
{t("CLOSE")}
|
||||
</Button>
|
||||
)}
|
||||
{(step === undefined || step === "error") && (
|
||||
<>
|
||||
<div style={{ width: "30px" }} />
|
||||
|
||||
<Button color="accent" size="large" onClick={startFix}>
|
||||
{t("FIX_CREATION_TIME")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const FixCreationTimeRunning = ({ progressTracker }) => {
|
||||
const progress = Math.round(
|
||||
(progressTracker.current * 100) / progressTracker.total,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<ComfySpan>
|
||||
{" "}
|
||||
{progressTracker.current} / {progressTracker.total}{" "}
|
||||
</ComfySpan>{" "}
|
||||
<span style={{ marginLeft: "10px" }}>
|
||||
{" "}
|
||||
{t("CREATION_TIME_UPDATED")}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,58 +0,0 @@
|
|||
import { t } from "i18next";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { FIX_STATE } from ".";
|
||||
|
||||
export default function FixCreationTimeFooter({
|
||||
fixState,
|
||||
startFix,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
fixState !== FIX_STATE.RUNNING && (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
marginTop: "30px",
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<Button
|
||||
block
|
||||
variant={"outline-secondary"}
|
||||
onClick={() => {
|
||||
props.hide();
|
||||
}}
|
||||
>
|
||||
{t("CANCEL")}
|
||||
</Button>
|
||||
)}
|
||||
{fixState === FIX_STATE.COMPLETED && (
|
||||
<Button
|
||||
block
|
||||
variant={"outline-secondary"}
|
||||
onClick={props.hide}
|
||||
>
|
||||
{t("CLOSE")}
|
||||
</Button>
|
||||
)}
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<>
|
||||
<div style={{ width: "30px" }} />
|
||||
|
||||
<Button
|
||||
block
|
||||
variant={"outline-success"}
|
||||
onClick={startFix}
|
||||
>
|
||||
{t("FIX_CREATION_TIME")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
import DialogBox from "@ente/shared/components/DialogBox/";
|
||||
import { Formik } from "formik";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { updateCreationTimeWithExif } from "services/updateCreationTimeWithExif";
|
||||
import { EnteFile } from "types/file";
|
||||
import FixCreationTimeFooter from "./footer";
|
||||
import FixCreationTimeRunning from "./running";
|
||||
|
||||
import { t } from "i18next";
|
||||
import FixCreationTimeOptions from "./options";
|
||||
export interface FixCreationTimeAttributes {
|
||||
files: EnteFile[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
attributes: FixCreationTimeAttributes;
|
||||
}
|
||||
export enum FIX_STATE {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
COMPLETED_WITH_ERRORS,
|
||||
}
|
||||
|
||||
export enum FIX_OPTIONS {
|
||||
DATE_TIME_ORIGINAL,
|
||||
DATE_TIME_DIGITIZED,
|
||||
METADATA_DATE,
|
||||
CUSTOM_TIME,
|
||||
}
|
||||
|
||||
interface formValues {
|
||||
option: FIX_OPTIONS;
|
||||
customTime: Date;
|
||||
}
|
||||
|
||||
function Message({ fixState }: { fixState: FIX_STATE }) {
|
||||
let message = null;
|
||||
switch (fixState) {
|
||||
case FIX_STATE.NOT_STARTED:
|
||||
message = t("UPDATE_CREATION_TIME_NOT_STARTED");
|
||||
break;
|
||||
case FIX_STATE.COMPLETED:
|
||||
message = t("UPDATE_CREATION_TIME_COMPLETED");
|
||||
break;
|
||||
case FIX_STATE.COMPLETED_WITH_ERRORS:
|
||||
message = t("UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR");
|
||||
break;
|
||||
}
|
||||
return message ? <div>{message}</div> : <></>;
|
||||
}
|
||||
export default function FixCreationTime(props: Props) {
|
||||
const [fixState, setFixState] = useState(FIX_STATE.NOT_STARTED);
|
||||
const [progressTracker, setProgressTracker] = useState({
|
||||
current: 0,
|
||||
total: 0,
|
||||
});
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.attributes &&
|
||||
props.isOpen &&
|
||||
fixState !== FIX_STATE.RUNNING
|
||||
) {
|
||||
setFixState(FIX_STATE.NOT_STARTED);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
const startFix = async (option: FIX_OPTIONS, customTime: Date) => {
|
||||
setFixState(FIX_STATE.RUNNING);
|
||||
const completedWithoutError = await updateCreationTimeWithExif(
|
||||
props.attributes.files,
|
||||
option,
|
||||
customTime,
|
||||
setProgressTracker,
|
||||
);
|
||||
if (!completedWithoutError) {
|
||||
setFixState(FIX_STATE.COMPLETED);
|
||||
} else {
|
||||
setFixState(FIX_STATE.COMPLETED_WITH_ERRORS);
|
||||
}
|
||||
await galleryContext.syncWithRemote();
|
||||
};
|
||||
if (!props.attributes) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const onSubmit = (values: formValues) => {
|
||||
startFix(Number(values.option), new Date(values.customTime));
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogBox
|
||||
open={props.isOpen}
|
||||
onClose={props.hide}
|
||||
attributes={{
|
||||
title:
|
||||
fixState === FIX_STATE.RUNNING
|
||||
? t("FIX_CREATION_TIME_IN_PROGRESS")
|
||||
: t("FIX_CREATION_TIME"),
|
||||
nonClosable: true,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...(fixState === FIX_STATE.RUNNING
|
||||
? { alignItems: "center" }
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
<Message fixState={fixState} />
|
||||
|
||||
{fixState === FIX_STATE.RUNNING && (
|
||||
<FixCreationTimeRunning progressTracker={progressTracker} />
|
||||
)}
|
||||
<Formik<formValues>
|
||||
initialValues={{
|
||||
option: FIX_OPTIONS.DATE_TIME_ORIGINAL,
|
||||
customTime: new Date(),
|
||||
}}
|
||||
validateOnBlur={false}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ values, handleChange, handleSubmit }) => (
|
||||
<>
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState ===
|
||||
FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<FixCreationTimeOptions
|
||||
handleChange={handleChange}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FixCreationTimeFooter
|
||||
fixState={fixState}
|
||||
startFix={handleSubmit}
|
||||
hide={props.hide}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</DialogBox>
|
||||
);
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
import { Row, Value } from "@ente/shared/components/Container";
|
||||
import EnteDateTimePicker from "components/EnteDateTimePicker";
|
||||
import { t } from "i18next";
|
||||
import { ChangeEvent } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { FIX_OPTIONS } from ".";
|
||||
|
||||
const Option = ({
|
||||
value,
|
||||
selected,
|
||||
onChange,
|
||||
label,
|
||||
}: {
|
||||
value: FIX_OPTIONS;
|
||||
selected: FIX_OPTIONS;
|
||||
onChange: (e: string | ChangeEvent<any>) => void;
|
||||
label: string;
|
||||
}) => (
|
||||
<Form.Check
|
||||
name="group1"
|
||||
style={{
|
||||
margin: "5px 0",
|
||||
color: value !== Number(selected) ? "#aaa" : "#fff",
|
||||
}}
|
||||
>
|
||||
<Form.Check.Input
|
||||
id={value.toString()}
|
||||
type="radio"
|
||||
value={value}
|
||||
checked={value === Number(selected)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Form.Check.Label
|
||||
style={{ cursor: "pointer" }}
|
||||
htmlFor={value.toString()}
|
||||
>
|
||||
{label}
|
||||
</Form.Check.Label>
|
||||
</Form.Check>
|
||||
);
|
||||
|
||||
export default function FixCreationTimeOptions({ handleChange, values }) {
|
||||
return (
|
||||
<Form noValidate>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.DATE_TIME_ORIGINAL}
|
||||
onChange={handleChange("option")}
|
||||
label={t("DATE_TIME_ORIGINAL")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.DATE_TIME_DIGITIZED}
|
||||
onChange={handleChange("option")}
|
||||
label={t("DATE_TIME_DIGITIZED")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.METADATA_DATE}
|
||||
onChange={handleChange("option")}
|
||||
label={t("METADATA_DATE")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: "0" }}>
|
||||
<Value width="50%">
|
||||
<Option
|
||||
value={FIX_OPTIONS.CUSTOM_TIME}
|
||||
onChange={handleChange("option")}
|
||||
label={t("CUSTOM_TIME")}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Value>
|
||||
{Number(values.option) === FIX_OPTIONS.CUSTOM_TIME && (
|
||||
<Value width="40%">
|
||||
<EnteDateTimePicker
|
||||
onSubmit={(x: Date) =>
|
||||
handleChange("customTime")(x.toUTCString())
|
||||
}
|
||||
/>
|
||||
</Value>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
);
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import { ComfySpan } from "components/ExportInProgress";
|
||||
import { t } from "i18next";
|
||||
import { ProgressBar } from "react-bootstrap";
|
||||
|
||||
export default function FixCreationTimeRunning({ progressTracker }) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<ComfySpan>
|
||||
{" "}
|
||||
{progressTracker.current} / {progressTracker.total}{" "}
|
||||
</ComfySpan>{" "}
|
||||
<span style={{ marginLeft: "10px" }}>
|
||||
{" "}
|
||||
{t("CREATION_TIME_UPDATED")}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<ProgressBar
|
||||
now={Math.round(
|
||||
(progressTracker.current * 100) / progressTracker.total,
|
||||
)}
|
||||
animated={true}
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
import DialogBox from "@ente/shared/components/DialogBox/";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
||||
import { t } from "i18next";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, ProgressBar } from "react-bootstrap";
|
||||
import {
|
||||
getLargeThumbnailFiles,
|
||||
replaceThumbnail,
|
||||
} from "services/migrateThumbnailService";
|
||||
import { ComfySpan } from "./ExportInProgress";
|
||||
|
||||
export type SetProgressTracker = React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
current: number;
|
||||
total: number;
|
||||
}>
|
||||
>;
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export enum FIX_STATE {
|
||||
NOT_STARTED,
|
||||
FIX_LATER,
|
||||
NOOP,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
COMPLETED_WITH_ERRORS,
|
||||
}
|
||||
function Message({ fixState }: { fixState: FIX_STATE }) {
|
||||
let message = null;
|
||||
switch (fixState) {
|
||||
case FIX_STATE.NOT_STARTED:
|
||||
case FIX_STATE.FIX_LATER:
|
||||
message = t("REPLACE_THUMBNAIL_NOT_STARTED");
|
||||
break;
|
||||
case FIX_STATE.COMPLETED:
|
||||
message = t("REPLACE_THUMBNAIL_COMPLETED");
|
||||
break;
|
||||
case FIX_STATE.NOOP:
|
||||
message = t("REPLACE_THUMBNAIL_NOOP");
|
||||
break;
|
||||
case FIX_STATE.COMPLETED_WITH_ERRORS:
|
||||
message = t("REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR");
|
||||
break;
|
||||
}
|
||||
return message ? (
|
||||
<div style={{ marginBottom: "30px" }}>{message}</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
export default function FixLargeThumbnails(props: Props) {
|
||||
const [fixState, setFixState] = useState(FIX_STATE.NOT_STARTED);
|
||||
const [progressTracker, setProgressTracker] = useState({
|
||||
current: 0,
|
||||
total: 0,
|
||||
});
|
||||
const [largeThumbnailFiles, setLargeThumbnailFiles] = useState<number[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const init = (): FIX_STATE => {
|
||||
let fixState = getData(LS_KEYS.THUMBNAIL_FIX_STATE)?.state;
|
||||
if (!fixState || fixState === FIX_STATE.RUNNING) {
|
||||
fixState = FIX_STATE.NOT_STARTED;
|
||||
updateFixState(fixState);
|
||||
}
|
||||
if (fixState === FIX_STATE.COMPLETED) {
|
||||
fixState = FIX_STATE.NOOP;
|
||||
updateFixState(fixState);
|
||||
}
|
||||
setFixState(fixState);
|
||||
return fixState;
|
||||
};
|
||||
|
||||
const fetchLargeThumbnail = async () => {
|
||||
const largeThumbnailFiles = (await getLargeThumbnailFiles()) ?? [];
|
||||
setLargeThumbnailFiles(largeThumbnailFiles);
|
||||
return largeThumbnailFiles;
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const largeThumbnailFiles = await fetchLargeThumbnail();
|
||||
if (
|
||||
fixState === FIX_STATE.NOT_STARTED &&
|
||||
largeThumbnailFiles.length > 0
|
||||
) {
|
||||
props.show();
|
||||
}
|
||||
if (
|
||||
(fixState === FIX_STATE.COMPLETED || fixState === FIX_STATE.NOOP) &&
|
||||
largeThumbnailFiles.length > 0
|
||||
) {
|
||||
updateFixState(FIX_STATE.NOT_STARTED);
|
||||
logError(Error(), "large thumbnail files left after migration");
|
||||
}
|
||||
if (largeThumbnailFiles.length === 0 && fixState !== FIX_STATE.NOOP) {
|
||||
updateFixState(FIX_STATE.NOOP);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (props.isOpen && fixState !== FIX_STATE.RUNNING) {
|
||||
main();
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const fixState = init();
|
||||
if (fixState === FIX_STATE.NOT_STARTED) {
|
||||
main();
|
||||
}
|
||||
}, []);
|
||||
const startFix = async (newlyFetchedLargeThumbnailFiles?: number[]) => {
|
||||
updateFixState(FIX_STATE.RUNNING);
|
||||
const completedWithError = await replaceThumbnail(
|
||||
setProgressTracker,
|
||||
new Set(
|
||||
newlyFetchedLargeThumbnailFiles ?? largeThumbnailFiles ?? [],
|
||||
),
|
||||
);
|
||||
if (typeof completedWithError !== "undefined") {
|
||||
updateFixState(
|
||||
completedWithError
|
||||
? FIX_STATE.COMPLETED_WITH_ERRORS
|
||||
: FIX_STATE.COMPLETED,
|
||||
);
|
||||
}
|
||||
await fetchLargeThumbnail();
|
||||
};
|
||||
|
||||
const updateFixState = (fixState: FIX_STATE) => {
|
||||
setFixState(fixState);
|
||||
setData(LS_KEYS.THUMBNAIL_FIX_STATE, { state: fixState });
|
||||
};
|
||||
return (
|
||||
<DialogBox
|
||||
open={props.isOpen}
|
||||
onClose={props.hide}
|
||||
attributes={{
|
||||
title: t("COMPRESS_THUMBNAILS"),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "20px",
|
||||
padding: "0 5%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Message fixState={fixState} />
|
||||
|
||||
{fixState === FIX_STATE.RUNNING && (
|
||||
<>
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<ComfySpan>
|
||||
{" "}
|
||||
{progressTracker.current} /{" "}
|
||||
{progressTracker.total}{" "}
|
||||
</ComfySpan>{" "}
|
||||
<span style={{ marginLeft: "10px" }}>
|
||||
{" "}
|
||||
{t("THUMBNAIL_REPLACED")}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<ProgressBar
|
||||
now={Math.round(
|
||||
(progressTracker.current * 100) /
|
||||
progressTracker.total,
|
||||
)}
|
||||
animated={true}
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
{fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.FIX_LATER ? (
|
||||
<Button
|
||||
block
|
||||
variant={"outline-secondary"}
|
||||
onClick={() => {
|
||||
updateFixState(FIX_STATE.FIX_LATER);
|
||||
props.hide();
|
||||
}}
|
||||
>
|
||||
{t("FIX_THUMBNAIL_LATER")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
block
|
||||
variant={"outline-secondary"}
|
||||
onClick={props.hide}
|
||||
>
|
||||
{t("CLOSE")}
|
||||
</Button>
|
||||
)}
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.FIX_LATER ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<>
|
||||
<div style={{ width: "30px" }} />
|
||||
|
||||
<Button
|
||||
block
|
||||
variant={"outline-success"}
|
||||
onClick={() => startFix()}
|
||||
>
|
||||
{t("FIX_THUMBNAIL")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogBox>
|
||||
);
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
import { addLogLine } from "@ente/shared/logging";
|
||||
import "@tensorflow/tfjs-backend-cpu";
|
||||
import "@tensorflow/tfjs-backend-webgl";
|
||||
import { DEFAULT_ML_SYNC_CONFIG } from "constants/mlConfig";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import arcfaceAlignmentService from "services/machineLearning/arcfaceAlignmentService";
|
||||
import arcfaceCropService from "services/machineLearning/arcfaceCropService";
|
||||
import blazeFaceDetectionService from "services/machineLearning/blazeFaceDetectionService";
|
||||
import imageSceneService from "services/machineLearning/imageSceneService";
|
||||
import ssdMobileNetV2Service from "services/machineLearning/ssdMobileNetV2Service";
|
||||
import { AlignedFace, FaceCrop, ObjectDetection } from "types/machineLearning";
|
||||
import { getMLSyncConfig } from "utils/machineLearning/config";
|
||||
import {
|
||||
getAlignedFaceBox,
|
||||
ibExtractFaceImage,
|
||||
ibExtractFaceImageUsingTransform,
|
||||
} from "utils/machineLearning/faceAlign";
|
||||
import { ibExtractFaceImageFromCrop } from "utils/machineLearning/faceCrop";
|
||||
import { FaceCropsRow, FaceImagesRow, ImageBitmapView } from "./ImageViews";
|
||||
|
||||
interface MLFileDebugViewProps {
|
||||
file: File;
|
||||
}
|
||||
|
||||
function drawFaceDetection(face: AlignedFace, ctx: CanvasRenderingContext2D) {
|
||||
const pointSize = Math.ceil(
|
||||
Math.max(ctx.canvas.width / 512, face.detection.box.width / 32),
|
||||
);
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = "rgba(255, 0, 0, 0.8)";
|
||||
ctx.lineWidth = pointSize;
|
||||
ctx.strokeRect(
|
||||
face.detection.box.x,
|
||||
face.detection.box.y,
|
||||
face.detection.box.width,
|
||||
face.detection.box.height,
|
||||
);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = "rgba(0, 255, 0, 0.8)";
|
||||
ctx.lineWidth = Math.round(pointSize * 1.5);
|
||||
const alignedBox = getAlignedFaceBox(face.alignment);
|
||||
ctx.strokeRect(
|
||||
alignedBox.x,
|
||||
alignedBox.y,
|
||||
alignedBox.width,
|
||||
alignedBox.height,
|
||||
);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = "rgba(0, 0, 255, 0.8)";
|
||||
face.detection.landmarks.forEach((l) => {
|
||||
ctx.beginPath();
|
||||
ctx.arc(l.x, l.y, pointSize, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawBbox(object: ObjectDetection, ctx: CanvasRenderingContext2D) {
|
||||
ctx.font = "100px Arial";
|
||||
ctx.save();
|
||||
ctx.restore();
|
||||
ctx.rect(...object.bbox);
|
||||
ctx.lineWidth = 10;
|
||||
ctx.strokeStyle = "green";
|
||||
ctx.fillStyle = "green";
|
||||
ctx.stroke();
|
||||
ctx.fillText(
|
||||
object.score.toFixed(3) + " " + object.class,
|
||||
object.bbox[0],
|
||||
object.bbox[1] > 10 ? object.bbox[1] - 5 : 10,
|
||||
);
|
||||
}
|
||||
|
||||
export default function MLFileDebugView(props: MLFileDebugViewProps) {
|
||||
// const [imageBitmap, setImageBitmap] = useState<ImageBitmap>();
|
||||
const [faceCrops, setFaceCrops] = useState<FaceCrop[]>();
|
||||
const [facesUsingCrops, setFacesUsingCrops] = useState<ImageBitmap[]>();
|
||||
const [facesUsingImage, setFacesUsingImage] = useState<ImageBitmap[]>();
|
||||
const [facesUsingTransform, setFacesUsingTransform] =
|
||||
useState<ImageBitmap[]>();
|
||||
|
||||
const canvasRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
const loadFile = async () => {
|
||||
// TODO: go through worker for these apis, to not include ml code in main bundle
|
||||
const imageBitmap = await createImageBitmap(props.file);
|
||||
const faceDetections =
|
||||
await blazeFaceDetectionService.detectFaces(imageBitmap);
|
||||
addLogLine("detectedFaces: ", faceDetections.length);
|
||||
|
||||
const objectDetections = await ssdMobileNetV2Service.detectObjects(
|
||||
imageBitmap,
|
||||
DEFAULT_ML_SYNC_CONFIG.objectDetection.maxNumBoxes,
|
||||
DEFAULT_ML_SYNC_CONFIG.objectDetection.minScore,
|
||||
);
|
||||
addLogLine("detectedObjects: ", JSON.stringify(objectDetections));
|
||||
|
||||
const sceneDetections = await imageSceneService.detectScenes(
|
||||
imageBitmap,
|
||||
DEFAULT_ML_SYNC_CONFIG.sceneDetection.minScore,
|
||||
);
|
||||
addLogLine("detectedScenes: ", JSON.stringify(sceneDetections));
|
||||
|
||||
const mlSyncConfig = await getMLSyncConfig();
|
||||
const faceCropPromises = faceDetections.map(async (faceDetection) =>
|
||||
arcfaceCropService.getFaceCrop(
|
||||
imageBitmap,
|
||||
faceDetection,
|
||||
mlSyncConfig.faceCrop,
|
||||
),
|
||||
);
|
||||
|
||||
const faceCrops = await Promise.all(faceCropPromises);
|
||||
if (didCancel) return;
|
||||
setFaceCrops(faceCrops);
|
||||
|
||||
const faceAlignments = faceDetections.map((detection) =>
|
||||
arcfaceAlignmentService.getFaceAlignment(detection),
|
||||
);
|
||||
addLogLine("alignedFaces: ", JSON.stringify(faceAlignments));
|
||||
|
||||
const canvas: HTMLCanvasElement = canvasRef.current;
|
||||
canvas.width = imageBitmap.width;
|
||||
canvas.height = imageBitmap.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (didCancel) return;
|
||||
ctx.drawImage(imageBitmap, 0, 0);
|
||||
const alignedFaces = faceAlignments.map((alignment, i) => {
|
||||
return {
|
||||
detection: faceDetections[i],
|
||||
alignment,
|
||||
} as AlignedFace;
|
||||
});
|
||||
alignedFaces.forEach((alignedFace) =>
|
||||
drawFaceDetection(alignedFace, ctx),
|
||||
);
|
||||
|
||||
objectDetections.forEach((object) => drawBbox(object, ctx));
|
||||
|
||||
const facesUsingCrops = await Promise.all(
|
||||
alignedFaces.map((face, i) => {
|
||||
return ibExtractFaceImageFromCrop(
|
||||
faceCrops[i],
|
||||
face.alignment,
|
||||
112,
|
||||
);
|
||||
}),
|
||||
);
|
||||
const facesUsingImage = await Promise.all(
|
||||
alignedFaces.map((face) => {
|
||||
return ibExtractFaceImage(imageBitmap, face.alignment, 112);
|
||||
}),
|
||||
);
|
||||
const facesUsingTransform = await Promise.all(
|
||||
alignedFaces.map((face) => {
|
||||
return ibExtractFaceImageUsingTransform(
|
||||
imageBitmap,
|
||||
face.alignment,
|
||||
112,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
if (didCancel) return;
|
||||
setFacesUsingCrops(facesUsingCrops);
|
||||
setFacesUsingImage(facesUsingImage);
|
||||
setFacesUsingTransform(facesUsingTransform);
|
||||
};
|
||||
|
||||
props.file && loadFile();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
};
|
||||
}, [props.file]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p></p>
|
||||
{/* <ImageBitmapView image={imageBitmap}></ImageBitmapView> */}
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ maxWidth: "100%" }}
|
||||
/>
|
||||
<p></p>
|
||||
<div>Face Crops:</div>
|
||||
<FaceCropsRow>
|
||||
{faceCrops?.map((faceCrop, i) => (
|
||||
<ImageBitmapView
|
||||
key={i}
|
||||
image={faceCrop.image}
|
||||
></ImageBitmapView>
|
||||
))}
|
||||
</FaceCropsRow>
|
||||
|
||||
<p></p>
|
||||
|
||||
<div>Face Images using face crops:</div>
|
||||
<FaceImagesRow>
|
||||
{facesUsingCrops?.map((image, i) => (
|
||||
<ImageBitmapView key={i} image={image}></ImageBitmapView>
|
||||
))}
|
||||
</FaceImagesRow>
|
||||
|
||||
<div>Face Images using original image:</div>
|
||||
<FaceImagesRow>
|
||||
{facesUsingImage?.map((image, i) => (
|
||||
<ImageBitmapView key={i} image={image}></ImageBitmapView>
|
||||
))}
|
||||
</FaceImagesRow>
|
||||
|
||||
<div>Face Images using transfrom:</div>
|
||||
<FaceImagesRow>
|
||||
{facesUsingTransform?.map((image, i) => (
|
||||
<ImageBitmapView key={i} image={image}></ImageBitmapView>
|
||||
))}
|
||||
</FaceImagesRow>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import { getToken, getUserID } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { useState } from "react";
|
||||
import { Button, Spinner } from "react-bootstrap";
|
||||
import { EnteFile } from "types/file";
|
||||
import mlService from "../../services/machineLearning/machineLearningService";
|
||||
|
||||
function MLServiceFileInfoButton({
|
||||
file,
|
||||
updateMLDataIndex,
|
||||
setUpdateMLDataIndex,
|
||||
}: {
|
||||
file: EnteFile;
|
||||
updateMLDataIndex: number;
|
||||
setUpdateMLDataIndex: (num: number) => void;
|
||||
}) {
|
||||
const [mlServiceRunning, setMlServiceRunning] = useState(false);
|
||||
|
||||
const runMLService = async () => {
|
||||
setMlServiceRunning(true);
|
||||
const token = getToken();
|
||||
const userID = getUserID();
|
||||
|
||||
// index 4 is for timeout of 240 seconds
|
||||
await mlService.syncLocalFile(token, userID, file as EnteFile, null, 4);
|
||||
|
||||
setUpdateMLDataIndex(updateMLDataIndex + 1);
|
||||
setMlServiceRunning(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "18px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={runMLService}
|
||||
disabled={mlServiceRunning}
|
||||
variant={mlServiceRunning ? "secondary" : "primary"}
|
||||
>
|
||||
{!mlServiceRunning ? (
|
||||
"Run ML Service"
|
||||
) : (
|
||||
<>
|
||||
ML Service Running{" "}
|
||||
<Spinner
|
||||
animation="border"
|
||||
size="sm"
|
||||
style={{
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MLServiceFileInfoButton;
|
|
@ -10,11 +10,24 @@ import TextSnippetOutlined from "@mui/icons-material/TextSnippetOutlined";
|
|||
import { Box, DialogProps, Link, Stack, styled } from "@mui/material";
|
||||
import { Chip } from "components/Chip";
|
||||
import { EnteDrawer } from "components/EnteDrawer";
|
||||
import { ObjectLabelList } from "components/MachineLearning/ObjectList";
|
||||
import {
|
||||
PhotoPeopleList,
|
||||
UnidentifiedFaces,
|
||||
} from "components/MachineLearning/PeopleList";
|
||||
import Titlebar from "components/Titlebar";
|
||||
import LinkButton from "components/pages/gallery/LinkButton";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { getEXIFLocation } from "services/upload/exifService";
|
||||
import { EnteFile } from "types/file";
|
||||
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
|
||||
import {
|
||||
getMapDisableConfirmationDialog,
|
||||
getMapEnableConfirmationDialog,
|
||||
} from "utils/ui";
|
||||
import { ExifData } from "./ExifData";
|
||||
import InfoItem from "./InfoItem";
|
||||
import MapBox from "./MapBox";
|
||||
|
@ -22,23 +35,6 @@ import { RenderCaption } from "./RenderCaption";
|
|||
import { RenderCreationTime } from "./RenderCreationTime";
|
||||
import { RenderFileName } from "./RenderFileName";
|
||||
|
||||
import {
|
||||
PhotoPeopleList,
|
||||
UnidentifiedFaces,
|
||||
} from "components/MachineLearning/PeopleList";
|
||||
|
||||
import { ObjectLabelList } from "components/MachineLearning/ObjectList";
|
||||
|
||||
// import MLServiceFileInfoButton from 'components/MachineLearning/MLServiceFileInfoButton';
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
|
||||
import {
|
||||
getMapDisableConfirmationDialog,
|
||||
getMapEnableConfirmationDialog,
|
||||
} from "utils/ui";
|
||||
|
||||
export const FileInfoSidebar = styled((props: DialogProps) => (
|
||||
<EnteDrawer {...props} anchor="right" />
|
||||
))({
|
||||
|
@ -352,14 +348,6 @@ export function FileInfo({
|
|||
file={file}
|
||||
updateMLDataIndex={updateMLDataIndex}
|
||||
/>
|
||||
|
||||
{/* <Box pt={1}>
|
||||
<MLServiceFileInfoButton
|
||||
file={file}
|
||||
updateMLDataIndex={updateMLDataIndex}
|
||||
setUpdateMLDataIndex={setUpdateMLDataIndex}
|
||||
/>
|
||||
</Box> */}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { t } from "i18next";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
// import FixLargeThumbnails from 'components/FixLargeThumbnail';
|
||||
import RecoveryKey from "@ente/shared/components/RecoveryKey";
|
||||
import {
|
||||
ACCOUNTS_PAGES,
|
||||
PHOTOS_PAGES as PAGES,
|
||||
} from "@ente/shared/constants/pages";
|
||||
import TwoFactorModal from "components/TwoFactor/Modal";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext, useState } from "react";
|
||||
// import mlIDbStorage from 'utils/storage/mlIDbStorage';
|
||||
import {
|
||||
configurePasskeyRecovery,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
// import dynamic from 'next/dynamic';
|
||||
|
||||
// const MLDebugWithNoSSR = dynamic(
|
||||
// () => import('components/MachineLearning/MlDebug-disabled'),
|
||||
// {
|
||||
// ssr: false,
|
||||
// }
|
||||
// );
|
||||
|
||||
export default function MLDebug() {
|
||||
return <div>{/* <MLDebugWithNoSSR></MLDebugWithNoSSR> */}</div>;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { styled } from "@mui/material";
|
||||
import { Button } from "react-bootstrap";
|
||||
const Container = styled("div")`
|
||||
position: fixed;
|
||||
bottom: 7%;
|
||||
right: 2%;
|
||||
align-self: flex-end;
|
||||
`;
|
||||
|
||||
interface Iprops {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function ReportAbuse(props: Iprops) {
|
||||
return (
|
||||
<Container>
|
||||
<Button onClick={props.onClick}>report abuse?</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export enum REPORT_REASON {
|
||||
COPYRIGHT = "COPYRIGHT",
|
||||
MALICIOUS_CONTENT = "MALICIOUS_CONTENT",
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { t } from "i18next";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { setupI18n } from "@/ui/i18n";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import {
|
||||
APP_TITLES,
|
||||
APPS,
|
||||
APP_TITLES,
|
||||
CLIENT_PACKAGE_NAMES,
|
||||
} from "@ente/shared/apps/constants";
|
||||
import { EnteAppProps } from "@ente/shared/apps/types";
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBox from "@ente/shared/components/DialogBox";
|
||||
import {
|
||||
|
@ -23,11 +17,12 @@ import {
|
|||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { AppUpdateInfo } from "@ente/shared/electron/types";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { eventBus, Events } from "@ente/shared/events";
|
||||
import { Events, eventBus } from "@ente/shared/events";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import {
|
||||
|
@ -36,7 +31,7 @@ import {
|
|||
} from "@ente/shared/logging/web";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { getData, LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import {
|
||||
getLocalMapEnabled,
|
||||
getToken,
|
||||
|
@ -44,7 +39,6 @@ import {
|
|||
} from "@ente/shared/storage/localStorage/helpers";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import createEmotionCache from "@ente/shared/themes/createEmotionCache";
|
||||
import { SetTheme } from "@ente/shared/themes/types";
|
||||
import ArrowForward from "@mui/icons-material/ArrowForward";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
|
@ -52,10 +46,13 @@ import { ThemeProvider } from "@mui/material/styles";
|
|||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import Notification from "components/Notification";
|
||||
import { REDIRECTS } from "constants/redirects";
|
||||
import { t } from "i18next";
|
||||
import isElectron from "is-electron";
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import "photoswipe/dist/photoswipe.css";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
import LoadingBar from "react-top-loading-bar";
|
||||
import DownloadManager from "services/download";
|
||||
import exportService from "services/export";
|
||||
|
@ -115,15 +112,8 @@ type AppContextType = {
|
|||
|
||||
export const AppContext = createContext<AppContextType>(null);
|
||||
|
||||
// Client-side cache, shared for the whole session of the user in the browser.
|
||||
const clientSideEmotionCache = createEmotionCache();
|
||||
|
||||
export default function App(props: EnteAppProps) {
|
||||
const {
|
||||
Component,
|
||||
emotionCache = clientSideEmotionCache,
|
||||
pageProps,
|
||||
} = props;
|
||||
export default function App(props: AppProps) {
|
||||
const { Component, pageProps } = props;
|
||||
const router = useRouter();
|
||||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
@ -390,7 +380,7 @@ export default function App(props: EnteAppProps) {
|
|||
});
|
||||
|
||||
return (
|
||||
<CacheProvider value={emotionCache}>
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{isI18nReady
|
||||
|
@ -491,6 +481,6 @@ export default function App(props: EnteAppProps) {
|
|||
)}
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import DocumentPage, {
|
||||
EnteDocumentProps,
|
||||
} from "@ente/shared/next/pages/_document";
|
||||
import DocumentPage from "@ente/shared/next/pages/_document";
|
||||
|
||||
export default function Document(props: EnteDocumentProps) {
|
||||
return <DocumentPage {...props} />;
|
||||
}
|
||||
export default DocumentPage;
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import Login from "@ente/accounts/components/Login";
|
||||
import SignUp from "@ente/accounts/components/SignUp";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { getData, LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { Button, styled, Typography, TypographyProps } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import Carousel from "react-bootstrap/Carousel";
|
||||
import { AppContext } from "./_app";
|
||||
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { EnteLogo } from "@ente/shared/components/EnteLogo";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { saveKeyInSessionStore } from "@ente/shared/crypto/helpers";
|
||||
import ElectronAPIs from "@ente/shared/electron";
|
||||
import { getAlbumsURL } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import localForage from "@ente/shared/storage/localForage";
|
||||
import { getData, LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { getKey, SESSION_KEYS } from "@ente/shared/storage/sessionStorage";
|
||||
import { Button, styled, Typography, TypographyProps } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import isElectron from "is-electron";
|
||||
import { useRouter } from "next/router";
|
||||
import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel";
|
||||
import "pure-react-carousel/dist/react-carousel.es.css";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { AppContext } from "./_app";
|
||||
|
||||
const Container = styled("div")`
|
||||
display: flex;
|
||||
|
@ -186,47 +186,7 @@ export default function LandingPage() {
|
|||
<>
|
||||
<SlideContainer>
|
||||
<EnteLogo height={24} sx={{ mb: 8 }} />
|
||||
<Carousel controls={false}>
|
||||
<Carousel.Item>
|
||||
<Img
|
||||
src="/images/onboarding-lock/1x.png"
|
||||
srcSet="/images/onboarding-lock/2x.png 2x,
|
||||
/images/onboarding-lock/3x.png 3x"
|
||||
/>
|
||||
<FeatureText>
|
||||
<Trans i18nKey={"HERO_SLIDE_1_TITLE"} />
|
||||
</FeatureText>
|
||||
<TextContainer>
|
||||
{t("HERO_SLIDE_1")}
|
||||
</TextContainer>
|
||||
</Carousel.Item>
|
||||
<Carousel.Item>
|
||||
<Img
|
||||
src="/images/onboarding-safe/1x.png"
|
||||
srcSet="/images/onboarding-safe/2x.png 2x,
|
||||
/images/onboarding-safe/3x.png 3x"
|
||||
/>
|
||||
<FeatureText>
|
||||
<Trans i18nKey={"HERO_SLIDE_2_TITLE"} />
|
||||
</FeatureText>
|
||||
<TextContainer>
|
||||
{t("HERO_SLIDE_2")}
|
||||
</TextContainer>
|
||||
</Carousel.Item>
|
||||
<Carousel.Item>
|
||||
<Img
|
||||
src="/images/onboarding-sync/1x.png"
|
||||
srcSet="/images/onboarding-sync/2x.png 2x,
|
||||
/images/onboarding-sync/3x.png 3x"
|
||||
/>
|
||||
<FeatureText>
|
||||
<Trans i18nKey={"HERO_SLIDE_3_TITLE"} />
|
||||
</FeatureText>
|
||||
<TextContainer>
|
||||
{t("HERO_SLIDE_3")}
|
||||
</TextContainer>
|
||||
</Carousel.Item>
|
||||
</Carousel>
|
||||
<Slideshow />
|
||||
</SlideContainer>
|
||||
<MobileBox>
|
||||
<Button
|
||||
|
@ -258,3 +218,83 @@ export default function LandingPage() {
|
|||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Slideshow: React.FC = () => {
|
||||
return (
|
||||
<CarouselProvider
|
||||
naturalSlideWidth={400}
|
||||
naturalSlideHeight={300}
|
||||
isIntrinsicHeight={true}
|
||||
totalSlides={3}
|
||||
isPlaying={true}
|
||||
>
|
||||
<Slider>
|
||||
<Slide index={0}>
|
||||
<Img
|
||||
src="/images/onboarding-lock/1x.png"
|
||||
srcSet="/images/onboarding-lock/2x.png 2x,
|
||||
/images/onboarding-lock/3x.png 3x"
|
||||
/>
|
||||
<FeatureText>
|
||||
<Trans i18nKey={"HERO_SLIDE_1_TITLE"} />
|
||||
</FeatureText>
|
||||
<TextContainer>{t("HERO_SLIDE_1")}</TextContainer>
|
||||
</Slide>
|
||||
<Slide index={1}>
|
||||
<SlideContents>
|
||||
<Img
|
||||
src="/images/onboarding-safe/1x.png"
|
||||
srcSet="/images/onboarding-safe/2x.png 2x,
|
||||
/images/onboarding-safe/3x.png 3x"
|
||||
/>
|
||||
<FeatureText>
|
||||
<Trans i18nKey={"HERO_SLIDE_2_TITLE"} />
|
||||
</FeatureText>
|
||||
<TextContainer>{t("HERO_SLIDE_2")}</TextContainer>
|
||||
</SlideContents>
|
||||
</Slide>
|
||||
<Slide index={2}>
|
||||
<SlideContents>
|
||||
<Img
|
||||
src="/images/onboarding-sync/1x.png"
|
||||
srcSet="/images/onboarding-sync/2x.png 2x,
|
||||
/images/onboarding-sync/3x.png 3x"
|
||||
/>
|
||||
<FeatureText>
|
||||
<Trans i18nKey={"HERO_SLIDE_3_TITLE"} />
|
||||
</FeatureText>
|
||||
<TextContainer>{t("HERO_SLIDE_3")}</TextContainer>
|
||||
</SlideContents>
|
||||
</Slide>
|
||||
</Slider>
|
||||
<CustomDotGroup />
|
||||
</CarouselProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomDotGroup = styled(DotGroup)`
|
||||
margin-block-start: 2px;
|
||||
margin-block-end: 24px;
|
||||
|
||||
button {
|
||||
margin-inline-end: 14px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.6s ease;
|
||||
}
|
||||
|
||||
button.carousel__dot--selected {
|
||||
background-color: #51cd7c;
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const SlideContents = styled("div")`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
|
|
@ -1,63 +1,34 @@
|
|||
import { getLocalFiles } from "services/fileService";
|
||||
import { EnteFile } from "types/file";
|
||||
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { CustomError, parseUploadErrorCodes } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import "@tensorflow/tfjs-backend-cpu";
|
||||
import "@tensorflow/tfjs-backend-webgl";
|
||||
import * as tf from "@tensorflow/tfjs-core";
|
||||
// import '@tensorflow/tfjs-backend-wasm';
|
||||
// import { setWasmPaths } from '@tensorflow/tfjs-backend-wasm';
|
||||
// import '@tensorflow/tfjs-backend-cpu';
|
||||
|
||||
import { MAX_ML_SYNC_ERROR_COUNT } from "constants/mlConfig";
|
||||
import downloadManager from "services/download";
|
||||
import { getLocalFiles } from "services/fileService";
|
||||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
MlFileData,
|
||||
MLSyncContext,
|
||||
MLSyncFileContext,
|
||||
MLSyncResult,
|
||||
MlFileData,
|
||||
} from "types/machineLearning";
|
||||
|
||||
// import { toTSNE } from 'utils/machineLearning/visualization';
|
||||
// import {
|
||||
// incrementIndexVersion,
|
||||
// mlFilesStore
|
||||
// } from 'utils/storage/mlStorage';
|
||||
// import { getAllFacesFromMap } from 'utils/machineLearning';
|
||||
import { CustomError, parseUploadErrorCodes } from "@ente/shared/error";
|
||||
import { MAX_ML_SYNC_ERROR_COUNT } from "constants/mlConfig";
|
||||
import { getMLSyncConfig } from "utils/machineLearning/config";
|
||||
import mlIDbStorage from "utils/storage/mlIDbStorage";
|
||||
import FaceService from "./faceService";
|
||||
import { MLFactory } from "./machineLearningFactory";
|
||||
import ObjectService from "./objectService";
|
||||
import PeopleService from "./peopleService";
|
||||
// import TextService from './textService';
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import downloadManager from "services/download";
|
||||
import ReaderService from "./readerService";
|
||||
|
||||
class MachineLearningService {
|
||||
private initialized = false;
|
||||
// private faceDetectionService: FaceDetectionService;
|
||||
// private faceLandmarkService: FAPIFaceLandmarksService;
|
||||
// private faceAlignmentService: FaceAlignmentService;
|
||||
// private faceEmbeddingService: FaceEmbeddingService;
|
||||
// private faceEmbeddingService: FAPIFaceEmbeddingService;
|
||||
// private clusteringService: ClusteringService;
|
||||
|
||||
private localSyncContext: Promise<MLSyncContext>;
|
||||
private syncContext: Promise<MLSyncContext>;
|
||||
|
||||
public constructor() {
|
||||
// setWasmPaths('/js/tfjs/');
|
||||
// this.faceDetectionService = new TFJSFaceDetectionService();
|
||||
// this.faceLandmarkService = new FAPIFaceLandmarksService();
|
||||
// this.faceAlignmentService = new ArcfaceAlignmentService();
|
||||
// this.faceEmbeddingService = new TFJSFaceEmbeddingService();
|
||||
// this.faceEmbeddingService = new FAPIFaceEmbeddingService();
|
||||
// this.clusteringService = new ClusteringService();
|
||||
}
|
||||
|
||||
public async sync(token: string, userID: number): Promise<MLSyncResult> {
|
||||
if (!token) {
|
||||
throw Error("Token needed by ml service to sync file");
|
||||
|
@ -198,30 +169,6 @@ class MachineLearningService {
|
|||
addLogLine("syncLocalFiles", Date.now() - startTime, "ms");
|
||||
}
|
||||
|
||||
// TODO: not required if ml data is stored as field inside ente file object
|
||||
// remove, not required now
|
||||
// it removes ml data for files in trash, they will be resynced if restored
|
||||
// private async syncRemovedFiles(syncContext: MLSyncContext) {
|
||||
// const db = await mlIDbStorage.db;
|
||||
// const localFileIdMap = await this.getLocalFilesMap(syncContext);
|
||||
|
||||
// const removedFileIds: Array<string> = [];
|
||||
// await mlFilesStore.iterate((file, idStr) => {
|
||||
// if (!localFileIdMap.has(parseInt(idStr))) {
|
||||
// removedFileIds.push(idStr);
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (removedFileIds.length < 1) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// removedFileIds.forEach((fileId) => mlFilesStore.removeItem(fileId));
|
||||
// addLogLine('Removed local file ids: ', removedFileIds);
|
||||
|
||||
// await incrementIndexVersion('files');
|
||||
// }
|
||||
|
||||
private async getOutOfSyncFiles(syncContext: MLSyncContext) {
|
||||
const startTime = Date.now();
|
||||
const fileIds = await mlIDbStorage.getFileIds(
|
||||
|
@ -239,50 +186,6 @@ class MachineLearningService {
|
|||
addLogLine("getOutOfSyncFiles", Date.now() - startTime, "ms");
|
||||
}
|
||||
|
||||
// TODO: optimize, use indexdb indexes, move facecrops to cache to reduce io
|
||||
// remove, already done
|
||||
private async getUniqueOutOfSyncFilesNoIdx(
|
||||
syncContext: MLSyncContext,
|
||||
files: EnteFile[],
|
||||
) {
|
||||
const limit = syncContext.config.batchSize;
|
||||
const mlVersion = syncContext.config.mlVersion;
|
||||
const uniqueFiles: Map<number, EnteFile> = new Map<number, EnteFile>();
|
||||
for (let i = 0; uniqueFiles.size < limit && i < files.length; i++) {
|
||||
const mlFileData = await this.getMLFileData(files[i].id);
|
||||
const mlFileVersion = mlFileData?.mlVersion || 0;
|
||||
if (
|
||||
!uniqueFiles.has(files[i].id) &&
|
||||
(!mlFileData?.errorCount || mlFileData.errorCount < 2) &&
|
||||
(mlFileVersion < mlVersion ||
|
||||
syncContext.config.imageSource !== mlFileData.imageSource)
|
||||
) {
|
||||
uniqueFiles.set(files[i].id, files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return [...uniqueFiles.values()];
|
||||
}
|
||||
|
||||
// private async getOutOfSyncFilesNoIdx(syncContext: MLSyncContext) {
|
||||
// const existingFilesMap = await this.getLocalFilesMap(syncContext);
|
||||
// // existingFiles.sort(
|
||||
// // (a, b) => b.metadata.creationTime - a.metadata.creationTime
|
||||
// // );
|
||||
// console.time('getUniqueOutOfSyncFiles');
|
||||
// syncContext.outOfSyncFiles = await this.getUniqueOutOfSyncFilesNoIdx(
|
||||
// syncContext,
|
||||
// [...existingFilesMap.values()]
|
||||
// );
|
||||
// addLogLine('getUniqueOutOfSyncFiles');
|
||||
// addLogLine(
|
||||
// 'Got unique outOfSyncFiles: ',
|
||||
// syncContext.outOfSyncFiles.length,
|
||||
// 'for batchSize: ',
|
||||
// syncContext.config.batchSize
|
||||
// );
|
||||
// }
|
||||
|
||||
private async syncFiles(syncContext: MLSyncContext) {
|
||||
try {
|
||||
const functions = syncContext.outOfSyncFiles.map(
|
||||
|
@ -448,22 +351,12 @@ class MachineLearningService {
|
|||
|
||||
try {
|
||||
await ReaderService.getImageBitmap(syncContext, fileContext);
|
||||
// await this.syncFaceDetections(syncContext, fileContext);
|
||||
// await ObjectService.syncFileObjectDetections(
|
||||
// syncContext,
|
||||
// fileContext
|
||||
// );
|
||||
await Promise.all([
|
||||
this.syncFaceDetections(syncContext, fileContext),
|
||||
ObjectService.syncFileObjectDetections(
|
||||
syncContext,
|
||||
fileContext,
|
||||
),
|
||||
// TextService.syncFileTextDetections(
|
||||
// syncContext,
|
||||
// fileContext,
|
||||
// textDetectionTimeoutIndex
|
||||
// ),
|
||||
]);
|
||||
newMlFile.errorCount = 0;
|
||||
newMlFile.lastErrorMessage = undefined;
|
||||
|
@ -495,30 +388,15 @@ class MachineLearningService {
|
|||
await tf.ready();
|
||||
|
||||
addLogLine("01 TF Memory stats: ", JSON.stringify(tf.memory()));
|
||||
// await tfjsFaceDetectionService.init();
|
||||
// // addLogLine('02 TF Memory stats: ',JSON.stringify(tf.memory()));
|
||||
// await this.faceLandmarkService.init();
|
||||
// await faceapi.nets.faceLandmark68Net.loadFromUri('/models/face-api/');
|
||||
// // addLogLine('03 TF Memory stats: ',JSON.stringify(tf.memory()));
|
||||
// await tfjsFaceEmbeddingService.init();
|
||||
// await faceapi.nets.faceRecognitionNet.loadFromUri('/models/face-api/');
|
||||
// addLogLine('04 TF Memory stats: ',JSON.stringify(tf.memory()));
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
public async dispose() {
|
||||
this.initialized = false;
|
||||
// await this.faceDetectionService.dispose();
|
||||
// addLogLine('11 TF Memory stats: ',JSON.stringify(tf.memory()));
|
||||
// await this.faceLandmarkService.dispose();
|
||||
// addLogLine('12 TF Memory stats: ',JSON.stringify(tf.memory()));
|
||||
// await this.faceEmbeddingService.dispose();
|
||||
// addLogLine('13 TF Memory stats: ',JSON.stringify(tf.memory()));
|
||||
}
|
||||
|
||||
private async getMLFileData(fileId: number) {
|
||||
// return mlFilesStore.getItem<MlFileData>(fileId);
|
||||
return mlIDbStorage.getFile(fileId);
|
||||
}
|
||||
|
||||
|
@ -526,7 +404,6 @@ class MachineLearningService {
|
|||
syncContext: MLSyncContext,
|
||||
mlFileData: MlFileData,
|
||||
) {
|
||||
// return mlFilesStore.setItem(mlFileData.fileId.toString(), mlFileData);
|
||||
mlIDbStorage.putFile(mlFileData);
|
||||
}
|
||||
|
||||
|
@ -559,14 +436,12 @@ class MachineLearningService {
|
|||
}
|
||||
|
||||
private async persistMLLibraryData(syncContext: MLSyncContext) {
|
||||
// return mlLibraryStore.setItem('data', syncContext.mlLibraryData);
|
||||
return mlIDbStorage.putLibraryData(syncContext.mlLibraryData);
|
||||
}
|
||||
|
||||
public async syncIndex(syncContext: MLSyncContext) {
|
||||
await this.getMLLibraryData(syncContext);
|
||||
|
||||
// await this.init();
|
||||
await PeopleService.syncPeopleIndex(syncContext);
|
||||
|
||||
await ObjectService.syncThingsIndex(syncContext);
|
||||
|
@ -574,17 +449,6 @@ class MachineLearningService {
|
|||
await this.persistMLLibraryData(syncContext);
|
||||
}
|
||||
|
||||
// private async runTSNE(syncContext: MLSyncContext) {
|
||||
// const allFacesMap = await FaceService.getAllSyncedFacesMap(syncContext);
|
||||
// const allFaces = getAllFacesFromMap(allFacesMap);
|
||||
|
||||
// const input = allFaces
|
||||
// .slice(0, syncContext.config.tsne.samples)
|
||||
// .map((f) => Array.from(f.embedding));
|
||||
// syncContext.tsne = toTSNE(input, syncContext.config.tsne);
|
||||
// addLogLine('tsne: ', syncContext.tsne);
|
||||
// }
|
||||
|
||||
private async syncFaceDetections(
|
||||
syncContext: MLSyncContext,
|
||||
fileContext: MLSyncFileContext,
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { getEndpoint } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { Remote } from "comlink";
|
||||
import { SetProgressTracker } from "components/FixLargeThumbnail";
|
||||
import downloadManager from "services/download";
|
||||
import { getLocalFiles } from "services/fileService";
|
||||
import { getFileType } from "services/typeDetectionService";
|
||||
import { generateThumbnail } from "services/upload/thumbnailService";
|
||||
import uploadHttpClient from "services/upload/uploadHttpClient";
|
||||
import { S3FileAttributes } from "types/file";
|
||||
import { UploadURL } from "types/upload";
|
||||
import { getLocalTrashedFiles } from "./trashService";
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
const REPLACE_THUMBNAIL_THRESHOLD = 500 * 1024; // 500KB
|
||||
export async function getLargeThumbnailFiles() {
|
||||
try {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const resp = await HTTPService.get(
|
||||
`${ENDPOINT}/files/large-thumbnails`,
|
||||
{
|
||||
threshold: REPLACE_THUMBNAIL_THRESHOLD,
|
||||
},
|
||||
{
|
||||
"X-Auth-Token": token,
|
||||
},
|
||||
);
|
||||
return resp.data.largeThumbnailFiles as number[];
|
||||
} catch (e) {
|
||||
logError(e, "failed to get large thumbnail files");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
export async function replaceThumbnail(
|
||||
setProgressTracker: SetProgressTracker,
|
||||
largeThumbnailFileIDs: Set<number>,
|
||||
) {
|
||||
let completedWithError = false;
|
||||
try {
|
||||
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
|
||||
const files = await getLocalFiles();
|
||||
const trashFiles = await getLocalTrashedFiles();
|
||||
const largeThumbnailFiles = [...files, ...trashFiles].filter((file) =>
|
||||
largeThumbnailFileIDs.has(file.id),
|
||||
);
|
||||
if (largeThumbnailFileIDs.size !== largeThumbnailFiles.length) {
|
||||
logError(Error(), "all large thumbnail files not found locally");
|
||||
}
|
||||
if (largeThumbnailFiles.length === 0) {
|
||||
return completedWithError;
|
||||
}
|
||||
setProgressTracker({ current: 0, total: largeThumbnailFiles.length });
|
||||
const uploadURLs: UploadURL[] = [];
|
||||
await uploadHttpClient.fetchUploadURLs(
|
||||
largeThumbnailFiles.length,
|
||||
uploadURLs,
|
||||
);
|
||||
for (const [idx, file] of largeThumbnailFiles.entries()) {
|
||||
try {
|
||||
setProgressTracker({
|
||||
current: idx,
|
||||
total: largeThumbnailFiles.length,
|
||||
});
|
||||
const originalThumbnail =
|
||||
await downloadManager.getThumbnail(file);
|
||||
const dummyImageFile = new File(
|
||||
[originalThumbnail],
|
||||
file.metadata.title,
|
||||
);
|
||||
const fileTypeInfo = await getFileType(dummyImageFile);
|
||||
const { thumbnail: newThumbnail } = await generateThumbnail(
|
||||
dummyImageFile,
|
||||
fileTypeInfo,
|
||||
);
|
||||
const newUploadedThumbnail = await uploadThumbnail(
|
||||
cryptoWorker,
|
||||
file.key,
|
||||
newThumbnail,
|
||||
uploadURLs.pop(),
|
||||
);
|
||||
await updateThumbnail(file.id, newUploadedThumbnail);
|
||||
} catch (e) {
|
||||
logError(e, "failed to replace a thumbnail");
|
||||
completedWithError = true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, "replace Thumbnail function failed");
|
||||
completedWithError = true;
|
||||
}
|
||||
return completedWithError;
|
||||
}
|
||||
|
||||
export async function uploadThumbnail(
|
||||
worker: Remote<DedicatedCryptoWorker>,
|
||||
fileKey: string,
|
||||
updatedThumbnail: Uint8Array,
|
||||
uploadURL: UploadURL,
|
||||
): Promise<S3FileAttributes> {
|
||||
const { file: encryptedThumbnail } = await worker.encryptThumbnail(
|
||||
updatedThumbnail,
|
||||
fileKey,
|
||||
);
|
||||
const thumbnailObjectKey = await uploadHttpClient.putFile(
|
||||
uploadURL,
|
||||
encryptedThumbnail.encryptedData,
|
||||
() => {},
|
||||
);
|
||||
|
||||
return {
|
||||
objectKey: thumbnailObjectKey,
|
||||
decryptionHeader: encryptedThumbnail.decryptionHeader,
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateThumbnail(
|
||||
fileID: number,
|
||||
newThumbnail: S3FileAttributes,
|
||||
) {
|
||||
try {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
await HTTPService.put(
|
||||
`${ENDPOINT}/files/thumbnail`,
|
||||
{
|
||||
fileID: fileID,
|
||||
thumbnail: newThumbnail,
|
||||
},
|
||||
null,
|
||||
{
|
||||
"X-Auth-Token": token,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, "failed to update thumbnail");
|
||||
throw e;
|
||||
}
|
||||
}
|
|
@ -4,14 +4,9 @@ import HTTPService from "@ente/shared/network/HTTPService";
|
|||
import { getEndpoint } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import localForage from "@ente/shared/storage/localForage";
|
||||
import { REPORT_REASON } from "constants/publicCollection";
|
||||
import { Collection, CollectionPublicMagicMetadata } from "types/collection";
|
||||
import { EncryptedEnteFile, EnteFile } from "types/file";
|
||||
import {
|
||||
AbuseReportDetails,
|
||||
AbuseReportRequest,
|
||||
LocalSavedPublicCollectionFiles,
|
||||
} from "types/publicCollection";
|
||||
import { LocalSavedPublicCollectionFiles } from "types/publicCollection";
|
||||
import { decryptFile, mergeMetadata, sortFiles } from "utils/file";
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
|
@ -376,30 +371,6 @@ export const verifyPublicCollectionPassword = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const reportAbuse = async (
|
||||
token: string,
|
||||
url: string,
|
||||
reason: REPORT_REASON,
|
||||
details: AbuseReportDetails,
|
||||
) => {
|
||||
try {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const abuseReportRequest: AbuseReportRequest = { url, reason, details };
|
||||
|
||||
await HTTPService.post(
|
||||
`${ENDPOINT}/public-collection/report-abuse`,
|
||||
abuseReportRequest,
|
||||
null,
|
||||
{ "X-Auth-Access-Token": token },
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, "failed to post abuse report");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const removePublicCollectionWithFiles = async (
|
||||
collectionUID: string,
|
||||
collectionKey: string,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { logError } from "@ente/shared/sentry";
|
||||
import { FIX_OPTIONS } from "components/FixCreationTime";
|
||||
import { SetProgressTracker } from "components/FixLargeThumbnail";
|
||||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
changeFileCreationTime,
|
||||
|
@ -21,6 +20,13 @@ const EXIF_TIME_TAGS = [
|
|||
"MetadataDate",
|
||||
];
|
||||
|
||||
export type SetProgressTracker = React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
current: number;
|
||||
total: number;
|
||||
}>
|
||||
>;
|
||||
|
||||
export async function updateCreationTimeWithExif(
|
||||
filesToBeUpdated: EnteFile[],
|
||||
fixOption: FIX_OPTIONS,
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
import * as tf from "@tensorflow/tfjs-core";
|
||||
|
||||
// import {
|
||||
// FaceDetection,
|
||||
// FaceLandmarks68,
|
||||
// WithFaceDescriptor,
|
||||
// WithFaceLandmarks,
|
||||
// } from 'face-api.js';
|
||||
import { DebugInfo } from "hdbscan";
|
||||
import PQueue from "p-queue";
|
||||
|
||||
// import { Point as D3Point, RawNodeDatum } from 'react-d3-tree/lib/types/common';
|
||||
import { EnteFile } from "types/file";
|
||||
import { Dimensions } from "types/image";
|
||||
import { Box, Point } from "../../../thirdparty/face-api/classes";
|
||||
|
@ -24,33 +15,9 @@ export interface MLSyncResult {
|
|||
error?: Error;
|
||||
}
|
||||
|
||||
export interface DebugFace {
|
||||
fileId: string;
|
||||
// face: FaceApiResult;
|
||||
face: AlignedFace;
|
||||
embedding: FaceEmbedding;
|
||||
faceImage: FaceImage;
|
||||
}
|
||||
|
||||
// export interface MLDebugResult {
|
||||
// allFaces: DebugFace[];
|
||||
// clustersWithNoise: FacesClustersWithNoise;
|
||||
// tree: RawNodeDatum;
|
||||
// tsne: TSNEData;
|
||||
// }
|
||||
|
||||
export declare type FaceImage = Array<Array<Array<number>>>;
|
||||
export declare type FaceImageBlob = Blob;
|
||||
|
||||
// export declare type FaceApiResult = WithFaceDescriptor<
|
||||
// WithFaceLandmarks<
|
||||
// {
|
||||
// detection: FaceDetection;
|
||||
// },
|
||||
// FaceLandmarks68
|
||||
// >
|
||||
// >;
|
||||
|
||||
export declare type FaceDescriptor = Float32Array;
|
||||
|
||||
export declare type Cluster = Array<number>;
|
||||
|
@ -79,12 +46,6 @@ export interface NearestCluster {
|
|||
distance: number;
|
||||
}
|
||||
|
||||
// export interface TSNEData {
|
||||
// width: number;
|
||||
// height: number;
|
||||
// dataset: D3Point[];
|
||||
// }
|
||||
|
||||
export declare type Landmark = Point;
|
||||
|
||||
export declare type ImageType = "Original" | "Preview";
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { TimeStampListItem } from "components/PhotoList";
|
||||
import { REPORT_REASON } from "constants/publicCollection";
|
||||
import { PublicURL } from "types/collection";
|
||||
import { EnteFile } from "types/file";
|
||||
|
||||
|
@ -17,31 +16,6 @@ export interface LocalSavedPublicCollectionFiles {
|
|||
files: EnteFile[];
|
||||
}
|
||||
|
||||
export interface AbuseReportRequest {
|
||||
url: string;
|
||||
reason: REPORT_REASON;
|
||||
details: AbuseReportDetails;
|
||||
}
|
||||
|
||||
export interface AbuseReportDetails {
|
||||
fullName: string;
|
||||
email: string;
|
||||
comment: string;
|
||||
signature: string;
|
||||
onBehalfOf: string;
|
||||
jobTitle: string;
|
||||
address: Address;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
country: string;
|
||||
postalCode: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export type SetPublicShareProp = React.Dispatch<
|
||||
React.SetStateAction<PublicURL>
|
||||
>;
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
import { euclidean } from "hdbscan";
|
||||
// import { RawNodeDatum } from 'react-d3-tree/lib/types/common';
|
||||
// import { f32Average, getAllFacesFromMap } from '.';
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import {
|
||||
FacesCluster,
|
||||
// Cluster,
|
||||
// FaceDescriptor,
|
||||
FaceWithEmbedding,
|
||||
MLSyncContext,
|
||||
NearestCluster,
|
||||
} from "types/machineLearning";
|
||||
// import { getAllFacesMap } from 'utils/storage/mlStorage';
|
||||
|
||||
// export function getClusterSummary(cluster: Cluster): FaceDescriptor {
|
||||
// const faceScore = (f) => f.detection.score; // f.alignedRect.box.width *
|
||||
// return cluster
|
||||
// .map((f) => this.allFaces[f].face)
|
||||
// .sort((f1, f2) => faceScore(f2) - faceScore(f1))[0].descriptor;
|
||||
// const descriptors = cluster.map((f) => this.allFaces[f].embedding);
|
||||
// return f32Average(descriptors);
|
||||
// }
|
||||
|
||||
export function updateClusterSummaries(syncContext: MLSyncContext) {
|
||||
if (
|
||||
!syncContext.mlLibraryData?.faceClusteringResults?.clusters ||
|
||||
syncContext.mlLibraryData?.faceClusteringResults?.clusters.length < 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultClusters =
|
||||
syncContext.mlLibraryData.faceClusteringResults.clusters;
|
||||
|
||||
resultClusters.forEach((resultCluster) => {
|
||||
syncContext.mlLibraryData.faceClustersWithNoise.clusters.push({
|
||||
faces: resultCluster,
|
||||
// summary: this.getClusterSummary(resultCluster),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getNearestCluster(
|
||||
syncContext: MLSyncContext,
|
||||
noise: FaceWithEmbedding,
|
||||
): NearestCluster {
|
||||
let nearest: FacesCluster = null;
|
||||
let nearestDist = 100000;
|
||||
syncContext.mlLibraryData.faceClustersWithNoise.clusters.forEach((c) => {
|
||||
const dist = euclidean(
|
||||
Array.from(noise.embedding),
|
||||
Array.from(c.summary),
|
||||
);
|
||||
if (dist < nearestDist) {
|
||||
nearestDist = dist;
|
||||
nearest = c;
|
||||
}
|
||||
});
|
||||
|
||||
addLogLine("nearestDist: ", nearestDist);
|
||||
return { cluster: nearest, distance: nearestDist };
|
||||
}
|
||||
|
||||
// export async function assignNoiseWithinLimit(syncContext: MLSyncContext) {
|
||||
// if (
|
||||
// !syncContext.mlLibraryData?.faceClusteringResults?.noise ||
|
||||
// syncContext.mlLibraryData?.faceClusteringResults.noise.length < 1
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const noise = syncContext.mlLibraryData.faceClusteringResults.noise;
|
||||
// const allFacesMap = await getAllFacesMap();
|
||||
// const allFaces = getAllFacesFromMap(allFacesMap);
|
||||
|
||||
// noise.forEach((n) => {
|
||||
// const noiseFace = allFaces[n];
|
||||
// const nearest = this.getNearestCluster(syncContext, noiseFace);
|
||||
|
||||
// if (nearest.cluster && nearest.distance < this.maxFaceDistance) {
|
||||
// addLogLine('Adding noise to cluser: ', n, nearest.distance);
|
||||
// nearest.cluster.faces.push(n);
|
||||
// } else {
|
||||
// addLogLine(
|
||||
// 'No cluster for noise: ',
|
||||
// n,
|
||||
// 'within distance: ',
|
||||
// this.maxFaceDistance
|
||||
// );
|
||||
// this.clustersWithNoise.noise.push(n);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// TODO: remove recursion to avoid stack size limits
|
||||
// export function toD3Tree(
|
||||
// treeNode: TreeNode<number>,
|
||||
// allObjects: Array<any>
|
||||
// ): RawNodeDatum {
|
||||
// if (!treeNode.left && !treeNode.right) {
|
||||
// return {
|
||||
// name: treeNode.data.toString(),
|
||||
// attributes: {
|
||||
// face: allObjects[treeNode.data],
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// const children = [];
|
||||
// treeNode.left && children.push(toD3Tree(treeNode.left, allObjects));
|
||||
// treeNode.right && children.push(toD3Tree(treeNode.right, allObjects));
|
||||
|
||||
// return {
|
||||
// name: treeNode.data.toString(),
|
||||
// children: children,
|
||||
// };
|
||||
// }
|
|
@ -1,28 +0,0 @@
|
|||
import {
|
||||
offscreenCanvasSupported,
|
||||
runningInChrome,
|
||||
webglSupported,
|
||||
} from "utils/common";
|
||||
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import isElectron from "is-electron";
|
||||
|
||||
export function canEnableMlSearch(): boolean {
|
||||
// check if is chrome or ente desktop
|
||||
if (!runningInChrome(false) && !isElectron()) {
|
||||
addLogLine("Not running in Chrome Desktop or Ente Desktop App");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!offscreenCanvasSupported()) {
|
||||
addLogLine("OffscreenCanvas is NOT supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!webglSupported()) {
|
||||
addLogLine("webgl is NOT supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { CACHES } from "@ente/shared/storage/cacheStorage/constants";
|
||||
import { cached } from "@ente/shared/storage/cacheStorage/helpers";
|
||||
import * as tf from "@tensorflow/tfjs-core";
|
||||
import { NormalizedFace } from "blazeface-back";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { BLAZEFACE_FACE_SIZE } from "constants/mlConfig";
|
||||
import { euclidean } from "hdbscan";
|
||||
import PQueue from "p-queue";
|
||||
import DownloadManager from "services/download";
|
||||
import { getLocalFiles } from "services/fileService";
|
||||
import { decodeLivePhoto } from "services/livePhotoService";
|
||||
import { EnteFile } from "types/file";
|
||||
import { Dimensions } from "types/image";
|
||||
import {
|
||||
|
@ -18,12 +23,6 @@ import {
|
|||
RealWorldObject,
|
||||
Versioned,
|
||||
} from "types/machineLearning";
|
||||
// import { mlFilesStore, mlPeopleStore } from 'utils/storage/mlStorage';
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { CACHES } from "@ente/shared/storage/cacheStorage/constants";
|
||||
import { cached } from "@ente/shared/storage/cacheStorage/helpers";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { decodeLivePhoto } from "services/livePhotoService";
|
||||
import { getRenderableImage } from "utils/file";
|
||||
import { imageBitmapToBlob } from "utils/image";
|
||||
import mlIDbStorage from "utils/storage/mlIDbStorage";
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { Face, MlFileData } from "types/machineLearning";
|
||||
import mlIDbStorage from "utils/storage/mlIDbStorage";
|
||||
import { mlFilesStore } from "utils/storage/mlStorage";
|
||||
import { getFaceId } from ".";
|
||||
import { storeFaceCropForBlob } from "./faceCrop";
|
||||
|
||||
// TODO: for migrating existing data, to be removed
|
||||
export async function migrateExistingFiles() {
|
||||
const existingFiles: Array<MlFileData> = [];
|
||||
await mlFilesStore.iterate((mlFileData: MlFileData) => {
|
||||
if (!mlFileData.errorCount) {
|
||||
mlFileData.errorCount = 0;
|
||||
existingFiles.push(mlFileData);
|
||||
}
|
||||
});
|
||||
addLogLine("existing files: ", existingFiles.length);
|
||||
|
||||
try {
|
||||
for (const file of existingFiles) {
|
||||
await mlIDbStorage.putFile(file);
|
||||
}
|
||||
await mlIDbStorage.setIndexVersion("files", 1);
|
||||
addLogLine("migrateExistingFiles done");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateFaceCropsToCache() {
|
||||
const startTime = Date.now();
|
||||
addLogLine("migrateFaceCropsToCache started");
|
||||
const allFiles = await mlIDbStorage.getAllFiles();
|
||||
const allFilesWithFaces = allFiles.filter(
|
||||
(f) => f.faces && f.faces.length > 0,
|
||||
);
|
||||
const updatedFacesMap = new Map<number, Array<Face>>();
|
||||
|
||||
for (const file of allFilesWithFaces) {
|
||||
let updated = false;
|
||||
for (const face of file.faces) {
|
||||
if (!face["id"]) {
|
||||
const faceCropBlob = face.crop["image"];
|
||||
const faceId = getFaceId(face, file.imageDimensions);
|
||||
face.crop = await storeFaceCropForBlob(
|
||||
faceId,
|
||||
face.crop.imageBox,
|
||||
faceCropBlob,
|
||||
);
|
||||
face["id"] = faceId;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
updatedFacesMap.set(file.fileId, file.faces);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedFacesMap.size > 0) {
|
||||
addLogLine("updating face crops: ", updatedFacesMap.size);
|
||||
await mlIDbStorage.updateFaces(updatedFacesMap);
|
||||
} else {
|
||||
addLogLine("not updating face crops: ", updatedFacesMap.size);
|
||||
}
|
||||
addLogLine("migrateFaceCropsToCache", Date.now() - startTime, "ms");
|
||||
}
|
||||
|
||||
export async function migrateFaceInterfaceUpdate() {
|
||||
const startTime = Date.now();
|
||||
addLogLine("migrateFaceInterfaceUpdate started");
|
||||
|
||||
const faceSchemaVersion = await mlIDbStorage.getIndexVersion("faceSchema");
|
||||
if (faceSchemaVersion) {
|
||||
addLogLine("not running migrateFaceInterfaceUpdate");
|
||||
return;
|
||||
}
|
||||
|
||||
const allFiles = await mlIDbStorage.getAllFiles();
|
||||
|
||||
const updatedFiles = allFiles.map((file) => {
|
||||
const updatedFaces = file.faces?.map((f) => {
|
||||
const updatedFace = {
|
||||
id: f["faceId"],
|
||||
fileId: f.fileId,
|
||||
|
||||
detection: {
|
||||
box: f["box"],
|
||||
landmarks: f["landmarks"],
|
||||
probability: f["probability"],
|
||||
},
|
||||
crop: f["faceCrop"],
|
||||
alignment: {
|
||||
affineMatrix: f["affineMatrix"],
|
||||
center: f["center"],
|
||||
rotation: f["rotation"],
|
||||
size: f["size"],
|
||||
},
|
||||
embedding: Float32Array.from(f.embedding),
|
||||
|
||||
personId: f.personId,
|
||||
} as Face;
|
||||
if (!updatedFace.id) {
|
||||
updatedFace.id = getFaceId(updatedFace, file.imageDimensions);
|
||||
}
|
||||
return updatedFace;
|
||||
});
|
||||
const updated: MlFileData = {
|
||||
fileId: file.fileId,
|
||||
|
||||
faceDetectionMethod: file["detectionMethod"],
|
||||
faceCropMethod: {
|
||||
value: "ArcFace",
|
||||
version: 1,
|
||||
},
|
||||
faceAlignmentMethod: file["alignmentMethod"],
|
||||
faceEmbeddingMethod: file["embeddingMethod"],
|
||||
|
||||
faces: updatedFaces,
|
||||
|
||||
imageDimensions: file.imageDimensions,
|
||||
imageSource: file.imageSource,
|
||||
errorCount: file.errorCount,
|
||||
lastErrorMessage: file.lastErrorMessage,
|
||||
mlVersion: file.mlVersion,
|
||||
};
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
addLogLine("migrateFaceInterfaceUpdate updating: ", updatedFiles.length);
|
||||
await mlIDbStorage.putAllFilesInTx(updatedFiles);
|
||||
|
||||
await mlIDbStorage.setIndexVersion("faceSchema", 1);
|
||||
addLogLine("migrateFaceInterfaceUpdate done");
|
||||
addLogLine("migrateFaceInterfaceUpdate", Date.now() - startTime, "ms");
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { CacheStorageService } from "@ente/shared/storage/cacheStorage";
|
||||
import { CACHES } from "@ente/shared/storage/cacheStorage/constants";
|
||||
import * as zip from "@zip.js/zip.js";
|
||||
import { MlFileData } from "types/machineLearning";
|
||||
import mlIDbStorage from "utils/storage/mlIDbStorage";
|
||||
|
||||
class FileSystemWriter extends zip.Writer {
|
||||
writableStream: FileSystemWritableFileStream;
|
||||
|
||||
constructor(writableStream: FileSystemWritableFileStream) {
|
||||
super();
|
||||
this.writableStream = writableStream;
|
||||
}
|
||||
|
||||
async writeUint8Array(array: Uint8Array) {
|
||||
// addLogLine('zipWriter needs to write data: ', array.byteLength);
|
||||
return this.writableStream.write(array);
|
||||
}
|
||||
|
||||
async getData() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class FileReader extends zip.Reader {
|
||||
file: File;
|
||||
|
||||
constructor(file: File) {
|
||||
super();
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.size = this.file.size;
|
||||
// addLogLine('zipReader init, size: ', this.size);
|
||||
}
|
||||
|
||||
public async readUint8Array(
|
||||
index: number,
|
||||
length: number,
|
||||
): Promise<Uint8Array> {
|
||||
// addLogLine('zipReader needs data: ', index, length);
|
||||
const slicedFile = this.file.slice(index, index + length);
|
||||
const arrayBuffer = await slicedFile.arrayBuffer();
|
||||
|
||||
return new Uint8Array(arrayBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportMlData(
|
||||
mlDataZipWritable: FileSystemWritableFileStream,
|
||||
) {
|
||||
const zipWriter = new zip.ZipWriter(
|
||||
new FileSystemWriter(mlDataZipWritable),
|
||||
);
|
||||
|
||||
try {
|
||||
try {
|
||||
await exportMlDataToZipWriter(zipWriter);
|
||||
} finally {
|
||||
await zipWriter.close();
|
||||
}
|
||||
} catch (e) {
|
||||
await mlDataZipWritable.abort();
|
||||
throw e;
|
||||
}
|
||||
|
||||
await mlDataZipWritable.close();
|
||||
addLogLine("Ml Data Exported");
|
||||
}
|
||||
|
||||
async function exportMlDataToZipWriter(zipWriter: zip.ZipWriter) {
|
||||
const mlDbData = await mlIDbStorage.getAllMLData();
|
||||
const faceClusteringResults =
|
||||
mlDbData?.library?.data?.faceClusteringResults;
|
||||
faceClusteringResults && (faceClusteringResults.debugInfo = undefined);
|
||||
addLogLine(
|
||||
"Exporting ML DB data: ",
|
||||
JSON.stringify(Object.keys(mlDbData)),
|
||||
JSON.stringify(
|
||||
Object.keys(mlDbData)?.map((k) => Object.keys(mlDbData[k])?.length),
|
||||
),
|
||||
);
|
||||
await zipWriter.add(
|
||||
"indexeddb/mldata.json",
|
||||
new zip.TextReader(JSON.stringify(mlDbData)),
|
||||
);
|
||||
|
||||
const faceCropCache = await CacheStorageService.open(CACHES.FACE_CROPS);
|
||||
const files =
|
||||
mlDbData["files"] && (Object.values(mlDbData["files"]) as MlFileData[]);
|
||||
for (const fileData of files || []) {
|
||||
for (const face of fileData.faces || []) {
|
||||
const faceCropUrl = face.crop?.imageUrl;
|
||||
if (!faceCropUrl) {
|
||||
console.error("face crop not found for faceId: ", face.id);
|
||||
continue;
|
||||
}
|
||||
const response = await faceCropCache.match(faceCropUrl);
|
||||
if (response && response.ok) {
|
||||
const blob = await response.blob();
|
||||
await zipWriter.add(
|
||||
`caches/${CACHES.FACE_CROPS}${faceCropUrl}`,
|
||||
new zip.BlobReader(blob),
|
||||
{ level: 0 },
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
"face crop cache entry not found for faceCropUrl: ",
|
||||
faceCropUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function importMlData(mlDataZipFile: File) {
|
||||
const zipReader = new zip.ZipReader(new FileReader(mlDataZipFile));
|
||||
|
||||
try {
|
||||
await importMlDataFromZipReader(zipReader);
|
||||
} finally {
|
||||
await zipReader.close();
|
||||
}
|
||||
|
||||
addLogLine("ML Data Imported");
|
||||
}
|
||||
|
||||
async function importMlDataFromZipReader(zipReader: zip.ZipReader) {
|
||||
const zipEntries = await zipReader.getEntries();
|
||||
// addLogLine(zipEntries);
|
||||
|
||||
const faceCropPath = `caches/${CACHES.FACE_CROPS}`;
|
||||
const faceCropCache = await CacheStorageService.open(CACHES.FACE_CROPS);
|
||||
let mldataEntry;
|
||||
for (const entry of zipEntries) {
|
||||
if (entry.filename === "indexeddb/mldata.json") {
|
||||
mldataEntry = entry;
|
||||
} else if (entry.filename.startsWith(faceCropPath)) {
|
||||
const faceCropUrl = entry.filename.substring(faceCropPath.length);
|
||||
// addLogLine('importing faceCropUrl: ', faceCropUrl);
|
||||
const faceCropCacheBlob: Blob = await entry.getData(
|
||||
new zip.BlobWriter("image/jpeg"),
|
||||
);
|
||||
faceCropCache.put(faceCropUrl, new Response(faceCropCacheBlob));
|
||||
}
|
||||
}
|
||||
|
||||
const mlDataJsonStr: string = await mldataEntry.getData(
|
||||
new zip.TextWriter(),
|
||||
);
|
||||
const mlDbData = JSON.parse(mlDataJsonStr);
|
||||
addLogLine(
|
||||
"importing ML DB data: ",
|
||||
JSON.stringify(Object.keys(mlDbData)),
|
||||
JSON.stringify(
|
||||
Object.keys(mlDbData)?.map((k) => Object.keys(mlDbData[k])?.length),
|
||||
),
|
||||
);
|
||||
await mlIDbStorage.putAllMLData(mlDbData);
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// // import TSNE from 'tsne-js';
|
||||
// import { TSNEConfig, TSNEData } from 'types/machineLearning';
|
||||
|
||||
// export function toD3Tsne(tsne) {
|
||||
// const data: TSNEData = {
|
||||
// width: 800,
|
||||
// height: 800,
|
||||
// dataset: [],
|
||||
// };
|
||||
// data.dataset = tsne.map((t) => {
|
||||
// return {
|
||||
// x: (data.width * (t[0] + 1.0)) / 2,
|
||||
// y: (data.height * (t[1] + 1.0)) / 2,
|
||||
// };
|
||||
// });
|
||||
|
||||
// return data;
|
||||
// }
|
||||
|
||||
// export function toTSNE(denseInput: Array<Array<number>>, config: TSNEConfig) {
|
||||
// if (!denseInput || denseInput.length < 1) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// const model = new TSNE(config);
|
||||
|
||||
// model.init({
|
||||
// data: denseInput,
|
||||
// type: 'dense',
|
||||
// });
|
||||
|
||||
// // `error`, `iter`: final error and iteration number
|
||||
// // note: computation-heavy action happens here
|
||||
// model.run();
|
||||
|
||||
// // `outputScaled` is `output` scaled to a range of [-1, 1]
|
||||
// return model.getOutputScaled();
|
||||
// }
|
||||
|
||||
export {};
|
|
@ -1,97 +0,0 @@
|
|||
import localForage from "localforage";
|
||||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
Face,
|
||||
MlFileData,
|
||||
MLIndex,
|
||||
MLSyncContext,
|
||||
} from "types/machineLearning";
|
||||
|
||||
export const mlFilesStore = localForage.createInstance({
|
||||
driver: localForage.INDEXEDDB,
|
||||
name: "ml-data",
|
||||
version: 1.0,
|
||||
storeName: "files",
|
||||
});
|
||||
|
||||
export const mlPeopleStore = localForage.createInstance({
|
||||
driver: localForage.INDEXEDDB,
|
||||
name: "ml-data",
|
||||
version: 1.0,
|
||||
storeName: "people",
|
||||
});
|
||||
|
||||
export const mlLibraryStore = localForage.createInstance({
|
||||
driver: localForage.INDEXEDDB,
|
||||
name: "ml-data",
|
||||
version: 1.0,
|
||||
storeName: "library",
|
||||
});
|
||||
|
||||
export const mlVersionStore = localForage.createInstance({
|
||||
driver: localForage.INDEXEDDB,
|
||||
name: "ml-data",
|
||||
version: 1.0,
|
||||
storeName: "versions",
|
||||
});
|
||||
|
||||
export async function clearMLStorage() {
|
||||
await mlFilesStore.clear();
|
||||
await mlPeopleStore.clear();
|
||||
await mlLibraryStore.clear();
|
||||
await mlVersionStore.clear();
|
||||
}
|
||||
|
||||
export async function getIndexVersion(index: MLIndex): Promise<number> {
|
||||
return ((await mlVersionStore.getItem(`${index}`)) as number) || 0;
|
||||
}
|
||||
|
||||
export async function setIndexVersion(
|
||||
index: MLIndex,
|
||||
version: number,
|
||||
): Promise<number> {
|
||||
await mlVersionStore.setItem(`${index}`, version);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
export async function incrementIndexVersion(index: MLIndex): Promise<number> {
|
||||
let currentVersion = await getIndexVersion(index);
|
||||
currentVersion = currentVersion + 1;
|
||||
await setIndexVersion(index, currentVersion);
|
||||
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
export async function isVersionOutdated(index: MLIndex, thanIndex: MLIndex) {
|
||||
const indexVersion = await getIndexVersion(index);
|
||||
const thanIndexVersion = await getIndexVersion(thanIndex);
|
||||
|
||||
return indexVersion < thanIndexVersion;
|
||||
}
|
||||
|
||||
export function newMlData(
|
||||
syncContext: MLSyncContext,
|
||||
enteFile: EnteFile,
|
||||
): MlFileData {
|
||||
return {
|
||||
fileId: enteFile.id,
|
||||
imageSource: syncContext.config.imageSource,
|
||||
faceDetectionMethod: syncContext.faceDetectionService.method,
|
||||
faceCropMethod: syncContext.faceCropService.method,
|
||||
faceAlignmentMethod: syncContext.faceAlignmentService.method,
|
||||
faceEmbeddingMethod: syncContext.faceEmbeddingService.method,
|
||||
errorCount: 0,
|
||||
mlVersion: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllFacesMap() {
|
||||
const allSyncedFacesMap = new Map<number, Array<Face>>();
|
||||
await mlFilesStore.iterate((mlFileData: MlFileData) => {
|
||||
mlFileData.faces &&
|
||||
allSyncedFacesMap.set(mlFileData.fileId, mlFileData.faces);
|
||||
});
|
||||
|
||||
return allSyncedFacesMap;
|
||||
}
|
|
@ -3,23 +3,12 @@ import { expose } from "comlink";
|
|||
import mlService from "services/machineLearning/machineLearningService";
|
||||
import { EnteFile } from "types/file";
|
||||
import { MachineLearningWorker } from "types/machineLearning";
|
||||
// import ReverseProxiedElectronCacheStorageProxy from './electronCacheStorageProxy.proxy';
|
||||
// import { setupResponseComlinkTransferHandler } from 'utils/comlink';
|
||||
|
||||
export class DedicatedMLWorker implements MachineLearningWorker {
|
||||
constructor() {
|
||||
addLogLine("DedicatedMLWorker constructor called");
|
||||
// this.init();
|
||||
}
|
||||
|
||||
// public async init() {
|
||||
// const recp = new ReverseProxiedElectronCacheStorageProxy();
|
||||
// const cacheProxy = await recp.open('thumbs');
|
||||
|
||||
// const thumb = await cacheProxy.match('13578875');
|
||||
// addLogLine('worker init cache.match', thumb);
|
||||
// }
|
||||
|
||||
public async closeLocalSyncContext() {
|
||||
return mlService.closeLocalSyncContext();
|
||||
}
|
||||
|
@ -51,5 +40,3 @@ export class DedicatedMLWorker implements MachineLearningWorker {
|
|||
}
|
||||
|
||||
expose(DedicatedMLWorker, self);
|
||||
|
||||
// setupResponseComlinkTransferHandler();
|
||||
|
|
|
@ -1,4 +1,52 @@
|
|||
# Notes for Developers
|
||||
# Development
|
||||
|
||||
## Editor setup
|
||||
|
||||
We recommend VS Code, with the following extensions:
|
||||
|
||||
- Prettier - reformats your code automatically (enable format on save),
|
||||
- ESLint - warns you about issues
|
||||
|
||||
Optionally, if you're going to make many changes to the CSS in JS, you might
|
||||
also find it useful to install the _vscode-styled-components_ extension.
|
||||
|
||||
## Yarn commands
|
||||
|
||||
Make sure you're on yarn 1.x series (aka yarn "classic").
|
||||
|
||||
### yarn install
|
||||
|
||||
Installs dependencies. This needs to be done once, and thereafter wherever there
|
||||
is a change in `yarn.lock` (e.g. when pulling the latest upstream).
|
||||
|
||||
### yarn dev:*
|
||||
|
||||
Launch the app in development mode. There is one `yarn dev:foo` for each app,
|
||||
e.g. `yarn dev:auth`. `yarn dev` is a shortcut for `yarn dev:photos`.
|
||||
|
||||
The ports are different for the main apps (3000), various sidecars (3001, 3002).
|
||||
|
||||
### yarn build:*
|
||||
|
||||
Build a production export for the app. This is a bunch of static HTML/JS/CSS
|
||||
that can be then deployed to any web server.
|
||||
|
||||
There is one `yarn build:foo` for each app, e.g. `yarn build:auth`. The output
|
||||
will be placed in `apps/<foo>/out`, e.g. `apps/auth/out`.
|
||||
|
||||
### yarn preview:*
|
||||
|
||||
Build a production export and start a local web server to serve it. This uses
|
||||
Python's built in web server, and is okay for quick testing but should not be
|
||||
used in production.
|
||||
|
||||
The ports are the same as that for `yarn dev:*`
|
||||
|
||||
### lint, lint-fix
|
||||
|
||||
Use `yarn lint` to check that your code formatting is as expected, and that
|
||||
there are no linter errors. Use `yarn lint-fix` to try and automatically fix the
|
||||
issues.
|
||||
|
||||
## Monorepo
|
||||
|
||||
|
|
|
@ -26,7 +26,11 @@
|
|||
"dev:payments": "yarn workspace payments next dev -p 3001",
|
||||
"dev:photos": "yarn workspace photos next dev",
|
||||
"lint": "yarn prettier --check . && yarn workspaces run eslint .",
|
||||
"lint-fix": "yarn prettier --write . && yarn workspaces run eslint --fix ."
|
||||
"lint-fix": "yarn prettier --write . && yarn workspaces run eslint --fix .",
|
||||
"preview:accounts": "yarn build:accounts && python3 -m http.server -d apps/accounts/out 3001",
|
||||
"preview:auth": "yarn build:auth && python3 -m http.server -d apps/auth/out 3000",
|
||||
"preview:cast": "yarn build:cast && python3 -m http.server -d apps/accounts/out 3001",
|
||||
"preview:photos": "yarn build:photos && python3 -m http.server -d apps/photos/out 3000"
|
||||
},
|
||||
"resolutions": {
|
||||
"libsodium": "0.7.9"
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import { EmotionCache } from "@emotion/react";
|
||||
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
|
||||
import { SetDialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||
import { AppProps } from "next/app";
|
||||
import { NextRouter } from "next/router";
|
||||
import { APPS } from "./constants";
|
||||
|
||||
export interface EnteAppProps extends AppProps {
|
||||
emotionCache?: EmotionCache;
|
||||
}
|
||||
|
||||
export interface PageProps {
|
||||
appContext: {
|
||||
showNavBar: (show: boolean) => void;
|
||||
|
|
|
@ -1,23 +1,6 @@
|
|||
import Document, {
|
||||
DocumentContext,
|
||||
DocumentProps,
|
||||
Head,
|
||||
Html,
|
||||
Main,
|
||||
NextScript,
|
||||
} from "next/document";
|
||||
import React from "react";
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
import createEmotionServer from "@emotion/server/create-instance";
|
||||
import { EnteAppProps } from "@ente/shared/apps/types";
|
||||
import createEmotionCache from "@ente/shared/themes/createEmotionCache";
|
||||
import { AppType } from "next/app";
|
||||
|
||||
export interface EnteDocumentProps extends DocumentProps {
|
||||
emotionStyleTags: JSX.Element[];
|
||||
}
|
||||
|
||||
export default function EnteDocument({ emotionStyleTags }: EnteDocumentProps) {
|
||||
export default function EnteDocument() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
|
@ -27,7 +10,6 @@ export default function EnteDocument({ emotionStyleTags }: EnteDocumentProps) {
|
|||
/>
|
||||
<link rel="icon" href="/images/favicon.png" type="image/png" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
{emotionStyleTags}
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
|
@ -36,67 +18,3 @@ export default function EnteDocument({ emotionStyleTags }: EnteDocumentProps) {
|
|||
</Html>
|
||||
);
|
||||
}
|
||||
|
||||
// `getInitialProps` belongs to `_document` (instead of `_app`),
|
||||
// it's compatible with static-site generation (SSG).
|
||||
EnteDocument.getInitialProps = async (ctx: DocumentContext) => {
|
||||
// Resolution order
|
||||
//
|
||||
// On the server:
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. document.getInitialProps
|
||||
// 4. app.render
|
||||
// 5. page.render
|
||||
// 6. document.render
|
||||
//
|
||||
// On the server with error:
|
||||
// 1. document.getInitialProps
|
||||
// 2. app.render
|
||||
// 3. page.render
|
||||
// 4. document.render
|
||||
//
|
||||
// On the client
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. app.render
|
||||
// 4. page.render
|
||||
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
|
||||
// You can consider sharing the same Emotion cache between all the SSR requests to speed up performance.
|
||||
// However, be aware that it can have global side effects.
|
||||
const cache = createEmotionCache();
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { extractCriticalToChunks } = createEmotionServer(cache);
|
||||
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: (
|
||||
App: React.ComponentType<
|
||||
React.ComponentProps<AppType> & EnteAppProps
|
||||
>,
|
||||
) =>
|
||||
function EnhanceApp(props) {
|
||||
return <App emotionCache={cache} {...props} />;
|
||||
},
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
// This is important. It prevents Emotion to render invalid HTML.
|
||||
// See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
|
||||
const emotionStyles = extractCriticalToChunks(initialProps.html);
|
||||
const emotionStyleTags = emotionStyles.styles.map((style) => (
|
||||
<style
|
||||
data-emotion={`${style.key} ${style.ids.join(" ")}`}
|
||||
key={style.key}
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: style.css }}
|
||||
/>
|
||||
));
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
emotionStyleTags,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import createCache from "@emotion/cache";
|
||||
|
||||
const isBrowser = typeof document !== "undefined";
|
||||
|
||||
// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
|
||||
// This assures that MUI styles are loaded first.
|
||||
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
|
||||
export default function createEmotionCache() {
|
||||
let insertionPoint;
|
||||
|
||||
if (isBrowser) {
|
||||
const emotionInsertionPoint = document.querySelector<HTMLMetaElement>(
|
||||
'meta[name="emotion-insertion-point"]',
|
||||
);
|
||||
insertionPoint = emotionInsertionPoint ?? undefined;
|
||||
}
|
||||
|
||||
return createCache({ key: "mui-style", insertionPoint });
|
||||
}
|
|
@ -947,11 +947,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.38.tgz#6fda4b410edc753d3213c648320ebcf319669020"
|
||||
integrity sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==
|
||||
|
||||
"@zip.js/zip.js@2.4.2":
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.4.2.tgz#7c2d4b381c0f73dffa1d02ef630c4168ebd5cc4a"
|
||||
integrity sha512-D4xr9g7U625Q01lQASr9g1sQWEGhndyd+G3v3OvY/qH3pwaJDpbVDy+TO6yEq7c8teQdjjTiiBvlcQcVtT+itg==
|
||||
|
||||
acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
|
@ -1470,12 +1465,17 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
|||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
deep-freeze@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
|
||||
integrity sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg==
|
||||
|
||||
deep-is@^0.1.3:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
deepmerge@^2.1.1:
|
||||
deepmerge@^2.1.1, deepmerge@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
||||
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
|
||||
|
@ -1587,6 +1587,13 @@ enhanced-resolve@^5.12.0:
|
|||
graceful-fs "^4.2.4"
|
||||
tapable "^2.2.0"
|
||||
|
||||
equals@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/equals/-/equals-1.0.5.tgz#212062dde5e1a510d955f13598efcc6a621b6ace"
|
||||
integrity sha512-wI15a6ZoaaXPv+55+Vh2Kqn3+efKRv8QPtcGTjW5xmanMnQzESdAt566jevtMZyt3W/jwLDTzXpMph5ECDJ2zg==
|
||||
dependencies:
|
||||
jkroso-type "1"
|
||||
|
||||
error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
|
@ -2688,6 +2695,11 @@ jackspeak@^2.3.5:
|
|||
optionalDependencies:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
jkroso-type@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jkroso-type/-/jkroso-type-1.1.1.tgz#bc4ced6d6c45fe0745282bafc86a9f8c4fc9ce61"
|
||||
integrity sha512-zZgay+fPG6PgMUrpyFADmQmvLo39+AZa7Gc5pZhev2RhDxwANEq2etwD8d0e6rTg5NkwOIlQmaEmns3draC6Ng==
|
||||
|
||||
jpeg-js@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
|
||||
|
@ -3354,6 +3366,17 @@ punycode@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||
|
||||
pure-react-carousel@^1.30.1:
|
||||
version "1.30.1"
|
||||
resolved "https://registry.yarnpkg.com/pure-react-carousel/-/pure-react-carousel-1.30.1.tgz#006a333869b51339dafcdee2afa0561eb46d1743"
|
||||
integrity sha512-B1qi62hZk0OFqRR4cTjtgIeOn/Ls5wo+HsLtrXT4jVf5et8ldBHSt+6LsYRJN86Or8dm+XbnJNEHy6WDJ0/DQw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
deep-freeze "0.0.1"
|
||||
deepmerge "^2.2.1"
|
||||
equals "^1.0.5"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
|
|
Loading…
Add table
Reference in a new issue