[web] Enable strictNullChecks in photos tsconfig (#6492)

This commit is contained in:
Manav Rathi
2025-07-08 13:38:21 +05:30
committed by GitHub
18 changed files with 381 additions and 355 deletions

View File

@@ -9,17 +9,10 @@ export default [
* "This rule requires the `strictNullChecks` compiler option to be
* turned on to function correctly"
*/
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "off",
"@typescript-eslint/no-unnecessary-condition": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-call": "off",
/** TODO: Disabled as we migrate, try to prune these again */
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off",
"react-hooks/exhaustive-deps": "off",
},
},

View File

@@ -87,6 +87,7 @@ export const AlbumCastDialogContents: React.FC<AlbumCastDialogProps> = ({
// (effectively, only Chrome).
//
// Override, otherwise tsc complains about unknown property `chrome`.
// @ts-expect-error TODO: why is this needed
// eslint-disable-next-line @typescript-eslint/dot-notation
setBrowserCanCast(typeof window["chrome"] != "undefined");
}, []);
@@ -127,7 +128,10 @@ export const AlbumCastDialogContents: React.FC<AlbumCastDialogProps> = ({
"urn:x-cast:pair-request",
(_, message) => {
const data = message;
// TODO:
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const obj = JSON.parse(data);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const code = obj.code;
if (code) {

View File

@@ -101,7 +101,15 @@ const AllAlbumsDialog = styled(Dialog)(({ theme }) => ({
},
}));
const Title = ({
type TitleProps = { collectionCount: number } & Pick<
AllAlbums,
| "onClose"
| "collectionsSortBy"
| "onChangeCollectionsSortBy"
| "isInHiddenSection"
>;
const Title: React.FC<TitleProps> = ({
onClose,
collectionCount,
collectionsSortBy,
@@ -154,7 +162,10 @@ interface ItemData {
// If we were only passing a single, stable value (e.g. items),
// We could just pass the value directly.
const createItemData = memoize((collectionRowList, onCollectionClick) => ({
// TODO:
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
collectionRowList,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
onCollectionClick,
}));
@@ -174,7 +185,7 @@ const AlbumsRow = React.memo(
return (
<div style={style}>
<Stack direction="row" sx={{ p: 2, gap: 0.5 }}>
{collectionRow.map((item: any) => (
{collectionRow.map((item) => (
<AlbumCard
isScrolling={isScrolling}
onCollectionClick={onCollectionClick}

View File

@@ -128,7 +128,7 @@ export const GalleryBarAndListHeader: React.FC<
const group = saveGroups.find(
(g) => g.collectionSummaryID === activeCollectionID,
);
return group && !isSaveComplete(group) && !isSaveCancelled(group);
return !!group && !isSaveComplete(group) && !isSaveCancelled(group);
}, [saveGroups, activeCollectionID]);
useEffect(() => {
@@ -145,9 +145,9 @@ export const GalleryBarAndListHeader: React.FC<
onRemotePull,
onAddSaveGroup,
}}
collectionSummary={toShowCollectionSummaries.get(
activeCollectionID,
)}
collectionSummary={
toShowCollectionSummaries.get(activeCollectionID!)!
}
onCollectionShare={showCollectionShare}
onCollectionCast={showCollectionCast}
/>
@@ -170,7 +170,7 @@ export const GalleryBarAndListHeader: React.FC<
activePerson,
showCollectionShare,
showCollectionCast,
// TODO-Cluster
// TODO: Cluster
// This causes a loop since it is an array dep
// people,
]);
@@ -211,9 +211,9 @@ export const GalleryBarAndListHeader: React.FC<
/>
<CollectionShare
{...collectionShareVisibilityProps}
collectionSummary={toShowCollectionSummaries.get(
activeCollectionID,
)}
collectionSummary={
toShowCollectionSummaries.get(activeCollectionID!)!
}
collection={activeCollection}
{...{
user,

View File

@@ -1,5 +1,4 @@
// TODO: Audit this file
/* eslint-disable @typescript-eslint/restrict-plus-operands */
import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined";
import FavoriteRoundedIcon from "@mui/icons-material/FavoriteRounded";
import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined";
@@ -30,10 +29,16 @@ import { TileBottomTextOverlay } from "ente-new/photos/components/Tiles";
import { PseudoCollectionID } from "ente-new/photos/services/collection-summary";
import { t } from "i18next";
import memoize from "memoize-one";
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
import React, {
useDeferredValue,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
VariableSizeList as List,
type ListChildComponentProps,
VariableSizeList,
areEqual,
} from "react-window";
import { type SelectedState, shouldShowAvatar } from "utils/file";
@@ -41,13 +46,9 @@ import {
handleSelectCreator,
handleSelectCreatorMulti,
} from "utils/photoFrame";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
export const DATE_CONTAINER_HEIGHT = 48;
export const SPACE_BTW_DATES = 44;
const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244;
/**
* A component with an explicit height suitable for being plugged in as the
* {@link header} or {@link footer} of the {@link FileList}.
@@ -71,7 +72,7 @@ interface TimeStampListItem {
tag?: "date" | "file";
items?: FileListAnnotatedFile[];
itemStartIndex?: number;
date?: string;
date?: string | null;
dates?: { date: string; span: number }[];
groups?: number[];
item?: any;
@@ -210,14 +211,12 @@ export const FileList: React.FC<FileListProps> = ({
emailByUserID,
onItemClick,
}) => {
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext,
const [_timeStampList, setTimeStampList] = useState(
new Array<TimeStampListItem>(),
);
const timeStampList = useDeferredValue(_timeStampList);
const [timeStampList, setTimeStampList] = useState<TimeStampListItem[]>([]);
const refreshInProgress = useRef(false);
const shouldRefresh = useRef(false);
const listRef = useRef(null);
const listRef = useRef<VariableSizeList | null>(null);
// Timeline date strings for which all photos have been selected.
//
@@ -225,8 +224,8 @@ export const FileList: React.FC<FileListProps> = ({
const [checkedTimelineDateStrings, setCheckedTimelineDateStrings] =
useState(new Set());
const [rangeStart, setRangeStart] = useState(null);
const [currentHover, setCurrentHover] = useState(null);
const [rangeStart, setRangeStart] = useState<number | null>(null);
const [currentHover, setCurrentHover] = useState<number | null>(null);
const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false);
const fittableColumns = getFractionFittableColumns(width);
@@ -237,66 +236,74 @@ export const FileList: React.FC<FileListProps> = ({
columns = MIN_COLUMNS;
skipMerge = true;
}
const shrinkRatio = getShrinkRatio(width, columns);
const listItemHeight =
IMAGE_CONTAINER_MAX_HEIGHT * shrinkRatio + GAP_BTW_TILES;
const refreshList = () => {
listRef.current?.resetAfterIndex(0);
};
useEffect(() => {
const main = () => {
if (refreshInProgress.current) {
shouldRefresh.current = true;
return;
}
refreshInProgress.current = true;
let timeStampList: TimeStampListItem[] = [];
// Since width and height are dependencies, there might be too many
// updates to the list during a resize. The list computation too, while
// fast, is non-trivial.
//
// To avoid these issues, the we use `useDeferredValue`: if it gets
// another update when processing one, React will restart the background
// rerender from scratch.
if (header) {
timeStampList.push(asFullSpanListItem(header));
}
if (disableGrouping) {
noGrouping(timeStampList);
} else {
groupByTime(timeStampList);
}
let timeStampList: TimeStampListItem[] = [];
if (!skipMerge) {
timeStampList = mergeTimeStampList(timeStampList, columns);
}
if (timeStampList.length === 1) {
timeStampList.push(getEmptyListItem());
}
const footerHeight = footer?.height ?? 0;
timeStampList.push(getVacuumItem(timeStampList, footerHeight));
if (footer) {
timeStampList.push(asFullSpanListItem(footer));
}
if (header) {
timeStampList.push(asFullSpanListItem(header));
}
setTimeStampList(timeStampList);
refreshInProgress.current = false;
if (shouldRefresh.current) {
shouldRefresh.current = false;
setTimeout(main, 0);
}
};
main();
if (disableGrouping) {
noGrouping(timeStampList);
} else {
groupByTime(timeStampList);
}
if (!skipMerge) {
timeStampList = mergeTimeStampList(timeStampList, columns);
}
if (timeStampList.length == 1) {
timeStampList.push({
item: (
<NoFilesContainer span={columns}>
<Typography sx={{ color: "text.faint" }}>
{t("nothing_here")}
</Typography>
</NoFilesContainer>
),
id: "empty-list-banner",
height: height - 48,
});
}
const footerHeight = footer?.height ?? 0;
timeStampList.push(getVacuumItem(timeStampList, footerHeight));
if (footer) {
timeStampList.push(asFullSpanListItem(footer));
}
setTimeStampList(timeStampList);
}, [
width,
height,
annotatedFiles,
header,
footer,
annotatedFiles,
disableGrouping,
publicCollectionGalleryContext.credentials,
columns,
]);
useEffect(() => {
refreshList();
// Refresh list.
listRef.current?.resetAfterIndex(0);
}, [timeStampList]);
// TODO: Too many non-null assertions
const groupByTime = (timeStampList: TimeStampListItem[]) => {
let listItemIndex = 0;
let lastCreationTime: number | undefined;
@@ -320,7 +327,7 @@ export const FileList: React.FC<FileListProps> = ({
});
listItemIndex = 1;
} else if (listItemIndex < columns) {
timeStampList[timeStampList.length - 1].items.push(item);
timeStampList[timeStampList.length - 1]!.items!.push(item);
listItemIndex++;
} else {
listItemIndex = 1;
@@ -337,7 +344,7 @@ export const FileList: React.FC<FileListProps> = ({
let listItemIndex = columns;
annotatedFiles.forEach((item, index) => {
if (listItemIndex < columns) {
timeStampList[timeStampList.length - 1].items.push(item);
timeStampList[timeStampList.length - 1]!.items!.push(item);
listItemIndex++;
} else {
listItemIndex = 1;
@@ -350,21 +357,10 @@ export const FileList: React.FC<FileListProps> = ({
});
};
const getEmptyListItem = () => {
return {
item: (
<NothingContainer span={columns}>
<Typography sx={{ color: "text.faint" }}>
{t("nothing_here")}
</Typography>
</NothingContainer>
),
id: "empty-list-banner",
height: height - 48,
};
};
const getVacuumItem = (timeStampList, footerHeight: number) => {
const getVacuumItem = (
timeStampList: TimeStampListItem[],
footerHeight: number,
) => {
const fileListHeight = (() => {
let sum = 0;
const getCurrentItemSize = getItemSize(timeStampList);
@@ -393,30 +389,31 @@ export const FileList: React.FC<FileListProps> = ({
let index = 0;
let newIndex = 0;
while (index < items.length) {
const currItem = items[index];
const currItem = items[index]!;
// If the current item is of type time, then it is not part of an ongoing date.
// So, there is a possibility of merge.
if (currItem.tag == "date") {
// If new list pointer is not at the end of list then
// we can add more items to the same list.
if (newList[newIndex]) {
const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244;
// Check if items can be added to same list
if (
newList[newIndex + 1].items.length +
items[index + 1].items.length +
newList[newIndex + 1]!.items!.length +
items[index + 1]!.items!.length +
Math.ceil(
newList[newIndex].dates.length *
newList[newIndex]!.dates!.length *
SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO,
) <=
columns
) {
newList[newIndex].dates.push({
date: currItem.date,
span: items[index + 1].items.length,
newList[newIndex]!.dates!.push({
date: currItem.date!,
span: items[index + 1]!.items!.length,
});
newList[newIndex + 1].items = [
...newList[newIndex + 1].items,
...items[index + 1].items,
newList[newIndex + 1]!.items = [
...newList[newIndex + 1]!.items!,
...items[index + 1]!.items!,
];
index += 2;
} else {
@@ -432,12 +429,12 @@ export const FileList: React.FC<FileListProps> = ({
date: null,
dates: [
{
date: currItem.date,
span: items[index + 1].items.length,
date: currItem.date!,
span: items[index + 1]!.items!.length,
},
],
});
newList.push(items[index + 1]);
newList.push(items[index + 1]!);
index += 2;
}
} else {
@@ -449,11 +446,11 @@ export const FileList: React.FC<FileListProps> = ({
}
}
for (let i = 0; i < newList.length; i++) {
const currItem = newList[i];
const nextItem = newList[i + 1];
const currItem = newList[i]!;
const nextItem = newList[i + 1]!;
if (currItem.tag == "date") {
if (currItem.dates.length > 1) {
currItem.groups = currItem.dates.map((item) => item.span);
if (currItem.dates!.length > 1) {
currItem.groups = currItem.dates!.map((item) => item.span);
nextItem.groups = currItem.groups;
}
}
@@ -461,25 +458,26 @@ export const FileList: React.FC<FileListProps> = ({
return newList;
};
const getItemSize = (timeStampList) => (index) => {
switch (timeStampList[index].tag) {
case "date":
return DATE_CONTAINER_HEIGHT;
case "file":
return listItemHeight;
default:
return timeStampList[index].height;
}
};
const getItemSize =
(timeStampList: TimeStampListItem[]) => (index: number) => {
switch (timeStampList[index]!.tag) {
case "date":
return dateContainerHeight;
case "file":
return listItemHeight;
default:
return timeStampList[index]!.height!;
}
};
const generateKey = (index) => {
switch (timeStampList[index].tag) {
const generateKey = (index: number) => {
switch (timeStampList[index]!.tag) {
case "file":
return `${timeStampList[index].items[0].file.id}-${
timeStampList[index].items.slice(-1)[0].file.id
return `${timeStampList[index]!.items![0]!.file.id}-${
timeStampList[index]!.items!.slice(-1)[0]!.file.id
}`;
default:
return `${timeStampList[index].id}-${index}`;
return `${timeStampList[index]!.id}-${index}`;
}
};
@@ -563,23 +561,23 @@ export const FileList: React.FC<FileListProps> = ({
const handleRangeSelect = (index: number) => () => {
if (typeof rangeStart != "undefined" && rangeStart !== index) {
const direction =
(index - rangeStart) / Math.abs(index - rangeStart);
(index - rangeStart!) / Math.abs(index - rangeStart!);
let checked = true;
for (
let i = rangeStart;
let i = rangeStart!;
(index - i) * direction >= 0;
i += direction
) {
checked = checked && !!selected[annotatedFiles[i].file.id];
checked = checked && !!selected[annotatedFiles[i]!.file.id];
}
for (
let i = rangeStart;
let i = rangeStart!;
(index - i) * direction > 0;
i += direction
) {
handleSelect(annotatedFiles[i].file)(!checked);
handleSelect(annotatedFiles[i]!.file)(!checked);
}
handleSelect(annotatedFiles[index].file, index)(!checked);
handleSelect(annotatedFiles[index]!.file, index)(!checked);
}
};
@@ -621,7 +619,7 @@ export const FileList: React.FC<FileListProps> = ({
{...{ user, emailByUserID }}
file={file}
onClick={() => onItemClick(index)}
selectable={selectable}
selectable={selectable!}
onSelect={handleSelect(file, index)}
selected={
(!mode
@@ -637,8 +635,8 @@ export const FileList: React.FC<FileListProps> = ({
onRangeSelect={handleRangeSelect(index)}
isRangeSelectActive={isShiftKeyPressed && selected.count > 0}
isInsSelectRange={
(index >= rangeStart && index <= currentHover) ||
(index >= currentHover && index <= rangeStart)
(index >= rangeStart! && index <= currentHover!) ||
(index >= currentHover! && index <= rangeStart!)
}
activeCollectionID={activeCollectionID}
showPlaceholder={isScrolling}
@@ -648,7 +646,7 @@ export const FileList: React.FC<FileListProps> = ({
const renderListItem = (
listItem: TimeStampListItem,
isScrolling: boolean,
isScrolling: boolean | undefined,
) => {
const haveSelection = (selected.count ?? 0) > 0;
switch (listItem.tag) {
@@ -681,12 +679,12 @@ export const FileList: React.FC<FileListProps> = ({
{haveSelection && (
<Checkbox
key={listItem.date}
name={listItem.date}
name={listItem.date!}
checked={checkedTimelineDateStrings.has(
listItem.date,
)}
onChange={() =>
onChangeSelectAllCheckBox(listItem.date)
onChangeSelectAllCheckBox(listItem.date!)
}
size="small"
sx={{ pl: 0 }}
@@ -696,22 +694,22 @@ export const FileList: React.FC<FileListProps> = ({
</DateContainer>
);
case "file": {
const ret = listItem.items.map((item, idx) =>
const ret = listItem.items!.map((item, idx) =>
getThumbnail(
item,
listItem.itemStartIndex + idx,
isScrolling,
listItem.itemStartIndex! + idx,
!!isScrolling,
),
);
if (listItem.groups) {
let sum = 0;
for (let i = 0; i < listItem.groups.length - 1; i++) {
sum = sum + listItem.groups[i];
sum = sum + listItem.groups[i]!;
ret.splice(
sum,
0,
<div
key={`${listItem.items[0].file.id}-gap-${i}`}
key={`${listItem.items![0]!.file.id}-gap-${i}`}
/>,
);
sum += 1;
@@ -720,6 +718,8 @@ export const FileList: React.FC<FileListProps> = ({
return ret;
}
default:
// TODO:
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return listItem.item;
}
};
@@ -752,7 +752,7 @@ export const FileList: React.FC<FileListProps> = ({
}
return (
<List
<VariableSizeList
key={key}
itemData={itemData}
ref={listRef}
@@ -765,7 +765,7 @@ export const FileList: React.FC<FileListProps> = ({
useIsScrolling
>
{PhotoListRow}
</List>
</VariableSizeList>
);
};
@@ -835,34 +835,47 @@ const ListContainer = styled(Box, {
}
`;
/**
* An grid item, spanning {@link span} columns.
*/
const ListItemContainer = styled("div")<{ span: number }>`
grid-column: span ${(props) => props.span};
grid-column: span ${({ span }) => span};
display: flex;
align-items: center;
`;
/**
* A grid items that spans all columns.
*/
const FullSpanListItemContainer = styled("div")`
grid-column: 1 / -1;
display: flex;
align-items: center;
`;
const asFullSpanListItem = ({ item, ...rest }: TimeStampListItem) => ({
/**
* Convert a {@link FileListHeaderOrFooter} into a {@link TimeStampListItem}
* that spans all columns.
*/
const asFullSpanListItem = ({ item, ...rest }: FileListHeaderOrFooter) => ({
...rest,
item: <FullSpanListItemContainer>{item}</FullSpanListItemContainer>,
});
const DateContainer = styled(ListItemContainer)(
({ theme }) => `
/**
* The fixed height (in px) of {@link DateContainer}.
*/
const dateContainerHeight = 48;
const DateContainer = styled(ListItemContainer)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: ${DATE_CONTAINER_HEIGHT}px;
color: ${theme.vars.palette.text.muted};
`,
);
height: ${dateContainerHeight}px;
color: "text.muted";
`;
const NothingContainer = styled(ListItemContainer)`
const NoFilesContainer = styled(ListItemContainer)`
text-align: center;
justify-content: center;
`;
@@ -903,10 +916,10 @@ const PhotoListRow = React.memo(
gridTemplateColumns={getTemplateColumns(
columns,
shrinkRatio,
timeStampList[index].groups,
timeStampList[index]!.groups,
)}
>
{renderListItem(timeStampList[index], isScrolling)}
{renderListItem(timeStampList[index]!, isScrolling)}
</ListContainer>
</ListItem>
);
@@ -927,7 +940,7 @@ type FileThumbnailProps = {
isInsSelectRange: boolean;
activeCollectionID: number;
showPlaceholder: boolean;
isFav: boolean;
isFav: boolean | undefined;
} & Pick<FileListProps, "user" | "emailByUserID">;
const FileThumbnail: React.FC<FileThumbnailProps> = ({

View File

@@ -30,6 +30,7 @@ import {
} from "ente-base/components/utils/modal";
import { useBaseContext } from "ente-base/context";
import { basename, dirname, joinPath } from "ente-base/file-name";
import type { PublicAlbumsCredentials } from "ente-base/http";
import log from "ente-base/log";
import type { CollectionMapping, Electron, ZipItem } from "ente-base/types/ipc";
import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload";
@@ -68,13 +69,7 @@ import { redirectToCustomerPortal } from "ente-new/photos/services/user-details"
import { usePhotosAppContext } from "ente-new/photos/types/context";
import { firstNonEmpty } from "ente-utils/array";
import { t } from "i18next";
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import type {
InProgressUpload,
SegregatedFinishedUploads,
@@ -84,18 +79,24 @@ import type {
} from "services/upload-manager";
import { uploadManager } from "services/upload-manager";
import watcher from "services/watch";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
import { UploadProgress } from "./UploadProgress";
interface UploadProps {
/**
* The currently logged in user, if any.
* The logged in user, if any.
*
* This is only expected to be present when we're running it the context of
* the photos app, where there is a logged in user. When used by the public
* albums app, this prop can be omitted.
*/
user?: LocalUser;
/**
* The {@link PublicAlbumsCredentials} to use, if any.
*
* These are expected to be set if we are in the context of the public
* albums app, and should be undefined when we're in the photos app context.
*/
publicAlbumsCredentials?: PublicAlbumsCredentials;
isFirstUpload?: boolean;
uploadTypeSelectorView: boolean;
dragAndDropFiles: File[];
@@ -164,6 +165,7 @@ type UploadType = "files" | "folders" | "zips";
*/
export const Upload: React.FC<UploadProps> = ({
user,
publicAlbumsCredentials,
isFirstUpload,
dragAndDropFiles,
onRemotePull,
@@ -177,9 +179,6 @@ export const Upload: React.FC<UploadProps> = ({
}) => {
const { showMiniDialog, onGenericError } = useBaseContext();
const { showNotification, watchFolderView } = usePhotosAppContext();
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext,
);
const [uploadProgressView, setUploadProgressView] = useState(false);
const [uploadPhase, setUploadPhase] = useState<UploadPhase>("preparing");
@@ -378,7 +377,7 @@ export const Upload: React.FC<UploadProps> = ({
setUploadProgressView,
},
onUploadFile,
publicCollectionGalleryContext.credentials,
publicAlbumsCredentials,
);
if (uploadManager.isUploadRunning()) {
@@ -408,7 +407,7 @@ export const Upload: React.FC<UploadProps> = ({
setDesktopZipItems(zipItems);
});
}
}, [publicCollectionGalleryContext.credentials]);
}, [publicAlbumsCredentials]);
// Handle selected files when user selects files for upload through the open
// file / open folder selection dialog, or drag-and-drops them.
@@ -527,10 +526,10 @@ export const Upload: React.FC<UploadProps> = ({
props.setLoading(false);
(async () => {
if (publicCollectionGalleryContext.credentials) {
if (publicAlbumsCredentials) {
setUploaderName(
(await savedPublicCollectionUploaderName(
publicCollectionGalleryContext.credentials.accessToken,
publicAlbumsCredentials.accessToken,
)) ?? "",
);
showUploaderNameInput();
@@ -591,7 +590,13 @@ export const Upload: React.FC<UploadProps> = ({
onCancel: handleCollectionSelectorCancel,
});
})();
}, [webFiles, desktopFiles, desktopFilePaths, desktopZipItems]);
}, [
publicAlbumsCredentials,
webFiles,
desktopFiles,
desktopFilePaths,
desktopZipItems,
]);
const preCollectionCreationAction = () => {
onCloseCollectionSelector?.();
@@ -832,7 +837,7 @@ export const Upload: React.FC<UploadProps> = ({
const handlePublicUpload = (uploaderName: string) => {
savePublicCollectionUploaderName(
publicCollectionGalleryContext.credentials!.accessToken,
publicAlbumsCredentials!.accessToken,
uploaderName,
);
@@ -868,6 +873,7 @@ export const Upload: React.FC<UploadProps> = ({
<UploadTypeSelector
open={props.uploadTypeSelectorView}
onClose={props.closeUploadTypeSelector}
publicAlbumsCredentials={publicAlbumsCredentials}
intent={props.uploadTypeSelectorIntent}
pendingUploadType={
isInputPending ? selectedUploadType.current : undefined
@@ -1115,7 +1121,7 @@ type UploadTypeSelectorProps = ModalVisibilityProps & {
* Called when the user selects one of the options.
*/
onSelect: (type: UploadType) => void;
};
} & Pick<UploadProps, "publicAlbumsCredentials">;
/**
* Request the user to specify which type of file / folder / zip it is that they
@@ -1129,28 +1135,21 @@ type UploadTypeSelectorProps = ModalVisibilityProps & {
const UploadTypeSelector: React.FC<UploadTypeSelectorProps> = ({
open,
onClose,
publicAlbumsCredentials,
intent,
pendingUploadType,
onSelect,
}) => {
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext,
);
// Directly show the file selector for the public albums app on likely
// mobile devices.
const directlyShowUploadFiles = useIsTouchscreen();
useEffect(() => {
if (
open &&
directlyShowUploadFiles &&
publicCollectionGalleryContext.credentials
) {
if (open && directlyShowUploadFiles && publicAlbumsCredentials) {
onSelect("files");
onClose();
}
}, [open]);
}, [open, publicAlbumsCredentials]);
const handleClose: DialogProps["onClose"] = (_, reason) => {
// Disable backdrop clicks and esc keypresses if a selection is pending

View File

@@ -1,3 +1,5 @@
// TODO: Audit this file
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import CloseIcon from "@mui/icons-material/Close";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
@@ -32,6 +34,7 @@ import { t } from "i18next";
import memoize from "memoize-one";
import React, {
createContext,
useCallback,
useContext,
useEffect,
useState,
@@ -87,7 +90,7 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
if (open) setExpanded(false);
}, [open]);
const handleClose = () => {
const handleClose = useCallback(() => {
if (uploadPhase == "done") {
onClose();
} else {
@@ -102,7 +105,7 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
cancel: t("no"),
});
}
};
}, [uploadPhase, onClose, cancelUploads, showMiniDialog]);
if (!open) {
return <></>;
@@ -130,6 +133,9 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
);
};
/**
* A context internal to the components of this file.
*/
interface UploadProgressContextT {
open: boolean;
onClose: () => void;
@@ -145,20 +151,19 @@ interface UploadProgressContextT {
setExpanded: React.Dispatch<React.SetStateAction<boolean>>;
}
const UploadProgressContext = createContext<UploadProgressContextT>({
open: null,
onClose: () => null,
uploadCounter: null,
uploadPhase: undefined,
percentComplete: null,
retryFailed: () => null,
inProgressUploads: null,
uploadFileNames: null,
finishedUploads: null,
hasLivePhotos: null,
expanded: null,
setExpanded: () => null,
});
const UploadProgressContext = createContext<UploadProgressContextT | undefined>(
undefined,
);
/**
* Convenience hook to obtain the non-null asserted
* {@link UploadProgressContext}.
*
* The non-null assertion is reasonable since we provide it to the tree always
* in an invariant that is local to this file (and thus has less chance of being
* invalid in the future).
*/
const useUploadProgressContext = () => useContext(UploadProgressContext)!;
const MinimizedUploadProgress: React.FC = () => (
<Snackbar open anchorOrigin={{ horizontal: "right", vertical: "bottom" }}>
@@ -176,9 +181,7 @@ const UploadProgressHeader: React.FC = () => (
);
const UploadProgressTitle: React.FC = () => {
const { setExpanded, onClose, expanded } = useContext(
UploadProgressContext,
);
const { setExpanded, onClose, expanded } = useUploadProgressContext();
const toggleExpanded = () => setExpanded((expanded) => !expanded);
return (
@@ -202,9 +205,8 @@ const UploadProgressTitle: React.FC = () => {
};
const UploadProgressSubtitleText: React.FC = () => {
const { uploadPhase, uploadCounter, finishedUploads } = useContext(
UploadProgressContext,
);
const { uploadPhase, uploadCounter, finishedUploads } =
useUploadProgressContext();
return (
<Typography
@@ -280,7 +282,7 @@ const notUploadedFileCount = (
};
const UploadProgressBar: React.FC = () => {
const { uploadPhase, percentComplete } = useContext(UploadProgressContext);
const { uploadPhase, percentComplete } = useUploadProgressContext();
return (
<Box>
@@ -300,9 +302,8 @@ const UploadProgressBar: React.FC = () => {
};
function UploadProgressDialog() {
const { open, onClose, uploadPhase, finishedUploads } = useContext(
UploadProgressContext,
);
const { open, onClose, uploadPhase, finishedUploads } =
useUploadProgressContext();
const [hasUnUploadedFiles, setHasUnUploadedFiles] = useState(false);
@@ -377,11 +378,14 @@ function UploadProgressDialog() {
const InProgressSection: React.FC = () => {
const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadPhase } =
useContext(UploadProgressContext);
useUploadProgressContext();
const fileList = inProgressUploads ?? [];
// @ts-expect-error Need to add types
const renderListItem = ({ localFileID, progress }) => {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
<InProgressItemContainer key={localFileID}>
<span>{uploadFileNames.get(localFileID)}</span>
{uploadPhase == "uploading" && (
@@ -395,10 +399,12 @@ const InProgressSection: React.FC = () => {
);
};
// @ts-expect-error Need to add types
const getItemTitle = ({ localFileID, progress }) => {
return `${uploadFileNames.get(localFileID)} - ${progress}%`;
};
// @ts-expect-error Need to add types
const generateItemKey = ({ localFileID, progress }) => {
return `${localFileID}-${progress}`;
};
@@ -492,28 +498,32 @@ const ResultSection: React.FC<ResultSectionProps> = ({
sectionTitle,
sectionInfo,
}) => {
const { finishedUploads, uploadFileNames } = useContext(
UploadProgressContext,
);
const { finishedUploads, uploadFileNames } = useUploadProgressContext();
const fileList = finishedUploads.get(resultType);
if (!fileList?.length) {
return <></>;
}
// @ts-expect-error Need to add types
const renderListItem = (fileID) => {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
<ResultItemContainer key={fileID}>
{uploadFileNames.get(fileID)}
</ResultItemContainer>
);
};
// @ts-expect-error Need to add types
const getItemTitle = (fileID) => {
return uploadFileNames.get(fileID);
return uploadFileNames.get(fileID)!;
};
// @ts-expect-error Need to add types
const generateItemKey = (fileID) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return fileID;
};
@@ -589,11 +599,17 @@ const createItemData: <T>(
getItemTitle: (item: T) => string,
items: T[],
) => ItemData<T> = memoize((renderListItem, getItemTitle, items) => ({
// TODO
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
renderListItem,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
getItemTitle,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
items,
}));
// TODO: Too many non-null assertions
// @ts-expect-error "TODO: Understand and fix the type error here"
const Row: <T>({
index,
@@ -613,12 +629,12 @@ const Row: <T>({
],
},
}}
title={getItemTitle(items[index])}
title={getItemTitle(items[index]!)}
placement="bottom-start"
enterDelay={300}
enterNextDelay={100}
>
<div style={style}>{renderListItem(items[index])}</div>
<div style={style}>{renderListItem(items[index]!)}</div>
</Tooltip>
);
},
@@ -634,7 +650,7 @@ function ItemList<T>(props: ItemListProps<T>) {
const getItemKey: ListItemKeySelector<ItemData<T>> = (index, data) => {
const { items } = data;
return props.generateItemKey(items[index]);
return props.generateItemKey(items[index]!);
};
return (
@@ -642,11 +658,11 @@ function ItemList<T>(props: ItemListProps<T>) {
<List
itemData={itemData}
height={Math.min(
props.itemSize * props.items.length,
props.maxHeight,
props.itemSize! * props.items.length,
props.maxHeight!,
)}
width={"100%"}
itemSize={props.itemSize}
itemSize={props.itemSize!}
itemCount={props.items.length}
itemKey={getItemKey}
>
@@ -657,15 +673,14 @@ function ItemList<T>(props: ItemListProps<T>) {
}
const DoneFooter: React.FC = () => {
const { uploadPhase, finishedUploads, retryFailed, onClose } = useContext(
UploadProgressContext,
);
const { uploadPhase, finishedUploads, retryFailed, onClose } =
useUploadProgressContext();
return (
<DialogActions>
{uploadPhase == "done" &&
(finishedUploads?.get("failed")?.length > 0 ||
finishedUploads?.get("blocked")?.length > 0 ? (
((finishedUploads.get("failed")?.length ?? 0) > 0 ||
(finishedUploads.get("blocked")?.length ?? 0) > 0 ? (
<Button fullWidth onClick={retryFailed}>
{t("retry_failed_uploads")}
</Button>

View File

@@ -72,7 +72,8 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
void isLocalStorageAndIndexedDBMismatch().then((mismatch) => {
if (mismatch) {
log.error("Logging out (IndexedDB and local storage mismatch)");
return logout();
logout();
return;
} else {
return runMigrations();
}
@@ -130,9 +131,14 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
if (needsFamilyRedirect && savedPartialLocalUser()?.token)
redirectToFamilyPortal();
router.events.on("routeChangeStart", (url) => {
// Creating this inline, we need this on debug only and temporarily. Can
// remove the debug print itself after a while.
interface NROptions {
shallow: boolean;
}
router.events.on("routeChangeStart", (url: string, o: NROptions) => {
if (process.env.NEXT_PUBLIC_ENTE_TRACE_RT) {
log.debug(() => ["route", url]);
log.debug(() => [o?.shallow ? "route-shallow" : "route", url]);
}
if (needsFamilyRedirect && savedPartialLocalUser()?.token) {

View File

@@ -483,6 +483,7 @@ const Page: React.FC = () => {
selected.ownCount++;
}
selected.count++;
// @ts-expect-error Selection code needs type fixing
selected[item.id] = true;
});
setSelected(selected);
@@ -1075,8 +1076,10 @@ const Page: React.FC = () => {
<GalleryBarAndListHeader
{...{
user,
activeCollection,
activeCollectionID,
// TODO: These are incorrect assertions, the types of the
// component need to be updated.
activeCollection: activeCollection!,
activeCollectionID: activeCollectionID!,
activePerson,
setFileListHeader,
saveGroups,
@@ -1162,7 +1165,8 @@ const Page: React.FC = () => {
selectable={true}
selected={selected}
setSelected={setSelected}
activeCollectionID={activeCollectionID}
// TODO: Incorrect assertion, need to update the type
activeCollectionID={activeCollectionID!}
activePersonID={activePerson?.id}
isInIncomingSharedCollection={activeCollectionSummary?.attributes.has(
"sharedIncoming",

View File

@@ -1,4 +1,4 @@
// TODO: Audit this file
// TODO: Audit this file (too many null assertions)
import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined";
import CloseIcon from "@mui/icons-material/Close";
import DownloadIcon from "@mui/icons-material/Download";
@@ -95,7 +95,6 @@ import { type FileWithPath } from "react-dropzone";
import { Trans } from "react-i18next";
import { uploadManager } from "services/upload-manager";
import { getSelectedFiles, type SelectedState } from "utils/file";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
export default function PublicCollectionGallery() {
const { showMiniDialog, onGenericError } = useBaseContext();
@@ -124,9 +123,9 @@ export default function PublicCollectionGallery() {
context: undefined,
});
// TODO: Can we convert these to state
const credentials = useRef<PublicAlbumsCredentials | undefined>(undefined);
const collectionKey = useRef<string>(null);
const url = useRef<string>(null);
const collectionKey = useRef<string | undefined>(undefined);
const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups();
@@ -170,8 +169,7 @@ export default function PublicCollectionGallery() {
const main = async () => {
let redirectingToWebsite = false;
try {
url.current = window.location.href;
const currentURL = new URL(url.current);
const currentURL = new URL(window.location.href);
const t = currentURL.searchParams.get("t");
const ck = await extractCollectionKeyFromShareURL(currentURL);
if (!t && !ck) {
@@ -182,10 +180,7 @@ export default function PublicCollectionGallery() {
return;
}
collectionKey.current = ck;
url.current = window.location.href;
const collection = await savedPublicCollectionByKey(
collectionKey.current,
);
const collection = await savedPublicCollectionByKey(ck);
const accessToken = t;
let accessTokenJWT: string | undefined;
if (collection) {
@@ -227,12 +222,12 @@ export default function PublicCollectionGallery() {
* both our local database and component state.
*/
const publicAlbumsRemotePull = useCallback(async () => {
const accessToken = credentials.current.accessToken;
const accessToken = credentials.current!.accessToken;
showLoadingBar();
setLoading(true);
try {
const { collection, referralCode: userReferralCode } =
await pullCollection(accessToken, collectionKey.current);
await pullCollection(accessToken, collectionKey.current!);
setReferralCode(userReferralCode);
setPublicCollection(collection);
@@ -243,18 +238,18 @@ export default function PublicCollectionGallery() {
// Remove the locally cached accessTokenJWT if the sharer has
// disabled password protection on the link.
if (!isPasswordProtected && credentials.current.accessTokenJWT) {
if (!isPasswordProtected && credentials.current?.accessTokenJWT) {
credentials.current.accessTokenJWT = undefined;
downloadManager.setPublicAlbumsCredentials(credentials.current);
removePublicCollectionAccessTokenJWT(accessToken);
}
if (isPasswordProtected && !credentials.current.accessTokenJWT) {
if (isPasswordProtected && !credentials.current?.accessTokenJWT) {
await removePublicCollectionFileData(accessToken);
} else {
try {
await pullPublicCollectionFiles(
credentials.current,
credentials.current!,
collection,
(files) =>
setPublicFiles(
@@ -273,7 +268,7 @@ export default function PublicCollectionGallery() {
// Clear the locally cached accessTokenJWT and ask the user
// to reenter the password.
if (isHTTP401Error(e)) {
credentials.current.accessTokenJWT = undefined;
credentials.current!.accessTokenJWT = undefined;
downloadManager.setPublicAlbumsCredentials(
credentials.current,
);
@@ -304,7 +299,7 @@ export default function PublicCollectionGallery() {
);
// Sharing has been disabled. Clear out local cache.
await removePublicCollectionFileData(accessToken);
await removePublicCollectionByKey(collectionKey.current);
await removePublicCollectionByKey(collectionKey.current!);
setPublicCollection(undefined);
setPublicFiles(undefined);
} else {
@@ -330,13 +325,13 @@ export default function PublicCollectionGallery() {
setFieldError,
) => {
try {
const accessToken = credentials.current.accessToken;
const accessToken = credentials.current!.accessToken;
const accessTokenJWT = await verifyPublicAlbumPassword(
publicCollection.publicURLs[0]!,
publicCollection!.publicURLs[0]!,
password,
accessToken,
);
credentials.current.accessTokenJWT = accessTokenJWT;
credentials.current!.accessTokenJWT = accessTokenJWT;
downloadManager.setPublicAlbumsCredentials(credentials.current);
await savePublicCollectionAccessTokenJWT(
accessToken,
@@ -368,12 +363,12 @@ export default function PublicCollectionGallery() {
const handleUploadFile = (file: EnteFile) =>
setPublicFiles(
sortFilesForCollection([...publicFiles, file], publicCollection),
sortFilesForCollection([...publicFiles!, file], publicCollection),
);
const downloadFilesHelper = async () => {
try {
const selectedFiles = getSelectedFiles(selected, publicFiles);
const selectedFiles = getSelectedFiles(selected, publicFiles!);
await downloadAndSaveFiles(
selectedFiles,
t("files_count", { count: selectedFiles.length }),
@@ -432,7 +427,7 @@ export default function PublicCollectionGallery() {
</Typography>
</Stack100vhCenter>
);
} else if (isPasswordProtected && !credentials.current.accessTokenJWT) {
} else if (isPasswordProtected && !credentials.current?.accessTokenJWT) {
return (
<AccountsPageContents>
<AccountsPageTitle>{t("password")}</AccountsPageTitle>
@@ -461,73 +456,69 @@ export default function PublicCollectionGallery() {
);
}
// TODO: memo this (after the dependencies are traceable).
const context = { credentials: credentials.current };
return (
<PublicCollectionGalleryContext.Provider value={context}>
<FullScreenDropZone
disabled={shouldDisableDropzone}
onDrop={setDragAndDropFiles}
<FullScreenDropZone
disabled={shouldDisableDropzone}
onDrop={setDragAndDropFiles}
>
<NavbarBase
sx={{
mb: "16px",
px: "24px",
"@media (width < 720px)": { px: "4px" },
}}
>
<NavbarBase
sx={{
mb: "16px",
px: "24px",
"@media (width < 720px)": { px: "4px" },
}}
>
{selected.count > 0 ? (
<SelectedFileOptions
count={selected.count}
clearSelection={clearSelection}
downloadFilesHelper={downloadFilesHelper}
/>
) : (
<SpacedRow sx={{ flex: 1 }}>
<EnteLogoLink href="https://ente.io">
<EnteLogo height={15} />
</EnteLogoLink>
{onAddPhotos ? (
<AddPhotosButton onClick={onAddPhotos} />
) : (
<GoToEnte />
)}
</SpacedRow>
)}
</NavbarBase>
{selected.count > 0 ? (
<SelectedFileOptions
count={selected.count}
clearSelection={clearSelection}
downloadFilesHelper={downloadFilesHelper}
/>
) : (
<SpacedRow sx={{ flex: 1 }}>
<EnteLogoLink href="https://ente.io">
<EnteLogo height={15} />
</EnteLogoLink>
{onAddPhotos ? (
<AddPhotosButton onClick={onAddPhotos} />
) : (
<GoToEnte />
)}
</SpacedRow>
)}
</NavbarBase>
<FileListWithViewer
files={publicFiles}
header={fileListHeader}
footer={fileListFooter}
enableDownload={downloadEnabled}
selectable={downloadEnabled}
selected={selected}
setSelected={setSelected}
activeCollectionID={PseudoCollectionID.all}
onRemotePull={publicAlbumsRemotePull}
onVisualFeedback={handleVisualFeedback}
onAddSaveGroup={onAddSaveGroup}
/>
{blockingLoad && <TranslucentLoadingOverlay />}
<Upload
uploadCollection={publicCollection}
setLoading={setBlockingLoad}
setShouldDisableDropzone={setShouldDisableDropzone}
uploadTypeSelectorIntent="collect"
uploadTypeSelectorView={uploadTypeSelectorView}
onRemotePull={publicAlbumsRemotePull}
onUploadFile={handleUploadFile}
closeUploadTypeSelector={closeUploadTypeSelectorView}
onShowSessionExpiredDialog={showPublicLinkExpiredMessage}
{...{ dragAndDropFiles }}
/>
<DownloadStatusNotifications
{...{ saveGroups, onRemoveSaveGroup }}
/>
</FullScreenDropZone>
</PublicCollectionGalleryContext.Provider>
<FileListWithViewer
files={publicFiles}
header={fileListHeader}
footer={fileListFooter}
enableDownload={downloadEnabled}
selectable={downloadEnabled}
selected={selected}
setSelected={setSelected}
activeCollectionID={PseudoCollectionID.all}
onRemotePull={publicAlbumsRemotePull}
onVisualFeedback={handleVisualFeedback}
onAddSaveGroup={onAddSaveGroup}
/>
{blockingLoad && <TranslucentLoadingOverlay />}
<Upload
publicAlbumsCredentials={credentials.current}
uploadCollection={publicCollection}
setLoading={setBlockingLoad}
setShouldDisableDropzone={setShouldDisableDropzone}
uploadTypeSelectorIntent="collect"
uploadTypeSelectorView={uploadTypeSelectorView}
onRemotePull={publicAlbumsRemotePull}
onUploadFile={handleUploadFile}
closeUploadTypeSelector={closeUploadTypeSelectorView}
onShowSessionExpiredDialog={showPublicLinkExpiredMessage}
{...{ dragAndDropFiles }}
/>
<DownloadStatusNotifications
{...{ saveGroups, onRemoveSaveGroup }}
/>
</FullScreenDropZone>
);
}
@@ -688,7 +679,7 @@ interface FileListFooterProps {
}
/**
* The dynamic (prop-depedent) height of {@link FileListFooter}.
* The dynamic (prop-dependent) height of {@link FileListFooter}.
*/
const fileListFooterHeightForProps = ({
referralCode,

View File

@@ -235,7 +235,7 @@ class UIService {
}
}
function convertInProgressUploadsToList(inProgressUploads) {
function convertInProgressUploadsToList(inProgressUploads: InProgressUploads) {
return [...inProgressUploads.entries()].map(
([localFileID, progress]) =>
({ localFileID, progress }) as InProgressUpload,
@@ -252,6 +252,7 @@ const groupByResult = (finishedUploads: FinishedUploads) => {
};
class UploadManager {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
private comlinkCryptoWorkers: ComlinkWorker<typeof CryptoWorker>[] =
new Array(maxConcurrentUploads);
private parsedMetadataJSONMap = new Map<string, ParsedMetadataJSON>();
@@ -297,6 +298,7 @@ class UploadManager {
) {
this.itemsToBeUploaded = [];
this.failedItems = [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.parsedMetadataJSONMap = parsedMetadataJSONMap ?? new Map();
this.uploaderName = undefined;
this.shouldUploadBeCancelled = false;
@@ -668,11 +670,11 @@ type UploadItemWithCollectionIDAndName = UploadAsset & {
const makeUploadItemWithCollectionIDAndName = (
f: UploadItemWithCollection,
): UploadItemWithCollectionIDAndName => ({
localID: f.localID!,
collectionID: f.collectionID!,
fileName: (f.isLivePhoto
localID: f.localID,
collectionID: f.collectionID,
fileName: f.isLivePhoto
? uploadItemFileName(f.livePhotoAssets!.image)
: uploadItemFileName(f.uploadItem!))!,
: uploadItemFileName(f.uploadItem!),
isLivePhoto: f.isLivePhoto,
uploadItem: f.uploadItem,
pathPrefix: f.pathPrefix,
@@ -774,7 +776,9 @@ const logAboutMemoryPressureIfNeeded = () => {
// is the method recommended by the Electron team (see the link about the V8
// memory cage). The embedded Chromium supports it fine though, we just need
// to goad TypeScript to accept the type.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const heapSize = (performance as any).memory.totalJSHeapSize;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const heapLimit = (performance as any).memory.jsHeapSizeLimit;
if (heapSize / heapLimit > 0.7) {
log.info(

View File

@@ -1,3 +1,4 @@
// TODO: Audit this file
import type { SelectionContext } from "ente-new/photos/components/gallery";
import type { GalleryBarMode } from "ente-new/photos/components/gallery/reducer";
import type { SelectedState, SetSelectedState } from "utils/file";
@@ -10,14 +11,17 @@ export const handleSelectCreator =
userID: number | undefined,
activeCollectionID: number,
activePersonID: string | undefined,
// @ts-expect-error Need to add types
setRangeStart?,
) =>
({ id, ownerID }: { id: number; ownerID: number }, index?: number) =>
(checked: boolean) => {
if (typeof index != "undefined") {
if (checked) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
setRangeStart(index);
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
setRangeStart(undefined);
}
}
@@ -135,7 +139,7 @@ const createSelectedAndContext = (
context:
mode == "people"
? { mode, personID: activePersonID! }
: { mode, collectionID: activeCollectionID! },
: { mode, collectionID: activeCollectionID },
};
} else {
// Both mode and context are defined.
@@ -148,7 +152,7 @@ const createSelectedAndContext = (
context:
mode == "people"
? { mode, personID: activePersonID! }
: { mode, collectionID: activeCollectionID! },
: { mode, collectionID: activeCollectionID },
};
} else {
if (selected.context?.mode == "people") {
@@ -173,7 +177,7 @@ const createSelectedAndContext = (
collectionID: 0,
context: {
mode: selected.context?.mode,
collectionID: activeCollectionID!,
collectionID: activeCollectionID,
},
};
}
@@ -185,7 +189,7 @@ const createSelectedAndContext = (
? undefined
: mode == "people"
? { mode, personID: activePersonID! }
: { mode, collectionID: activeCollectionID! };
: { mode, collectionID: activeCollectionID };
return { selected, newContext };
};

View File

@@ -1,16 +0,0 @@
import type { PublicAlbumsCredentials } from "ente-base/http";
import { createContext } from "react";
export interface PublicCollectionGalleryContextType {
/**
* The {@link PublicAlbumsCredentials} to use. These are guaranteed to be
* set if we are in the context of the public albums app, and will be
* undefined when we're in the default photos app context.
*/
credentials: PublicAlbumsCredentials | undefined;
}
export const PublicCollectionGalleryContext =
createContext<PublicCollectionGalleryContextType>({
credentials: undefined,
});

View File

@@ -1,4 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
// TODO: Audit this file
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-base-to-string */
/* eslint-disable @typescript-eslint/restrict-template-expressions */

View File

@@ -2,11 +2,7 @@
"extends": "ente-build-config/tsconfig-next.json",
"compilerOptions": {
/* Set the base directory from which to resolve bare module names. */
"baseUrl": "./src",
/* Override tsconfig-next.json (TODO: Remove all of us) */
"noImplicitAny": false,
"strictNullChecks": false
"baseUrl": "./src"
},
"include": [
"next-env.d.ts",

View File

@@ -202,7 +202,7 @@ const Page: React.FC = () => {
// generated interactive key attributes to verify password.
if (keyAttributes) {
if (!user.token && !user.encryptedToken) {
// TODO(RE): Why? For now, add a dev mode circuit breaker.
// TODO: Why? For now, add a dev mode circuit breaker.
if (isDevBuild) throw new Error("Unexpected case reached");
clearLocalStorage();
void router.replace("/");

View File

@@ -1,9 +1,5 @@
{
"extends": "ente-build-config/tsconfig-next.json",
"compilerOptions": {
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
"exactOptionalPropertyTypes": false
},
"include": [
".",
"../base/global-electron.d.ts",

View File

@@ -198,7 +198,9 @@ class DownloadManager {
* Set the credentials that should be used for download files when we're
* running in the context of the public albums app.
*/
setPublicAlbumsCredentials(credentials: PublicAlbumsCredentials) {
setPublicAlbumsCredentials(
credentials: PublicAlbumsCredentials | undefined,
) {
this.publicAlbumsCredentials = credentials;
}