mirror of
https://github.com/ente-io/ente.git
synced 2025-07-17 03:58:14 +00:00
commit
bc0980eb8d
@ -1,2 +1 @@
|
||||
thirdparty/
|
||||
public/
|
||||
|
@ -1,26 +1,20 @@
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "photos-web",
|
||||
"site": "https://web.ente.io"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "auth-web",
|
||||
"site": "https://auth.ente.io"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos.independent",
|
||||
@ -30,9 +24,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos",
|
||||
@ -42,9 +34,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.auth",
|
||||
|
@ -9,14 +9,8 @@
|
||||
"@/media": "*",
|
||||
"@/new": "*",
|
||||
"@ente/shared": "*",
|
||||
"@stripe/stripe-js": "^1.13.2",
|
||||
"bip39": "^3.0.4",
|
||||
"chrono-node": "^2.7.8",
|
||||
"debounce": "^2.2.0",
|
||||
"exifreader": "^4.26.1",
|
||||
"fast-srp-hap": "^2.0.4",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-defaulticon-compatibility": "^0.1.2",
|
||||
"localforage": "^1.9.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"ml-matrix": "^6.12.0",
|
||||
@ -24,7 +18,6 @@
|
||||
"photoswipe": "file:./thirdparty/photoswipe",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "14.2.10",
|
||||
"react-select": "^5.10.0",
|
||||
"react-top-loading-bar": "^3.0.2",
|
||||
"react-virtualized-auto-sizer": "^1.0.25",
|
||||
@ -35,7 +28,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@/build-config": "*",
|
||||
"@types/leaflet": "^1.9.16",
|
||||
"@types/node": "^20",
|
||||
"@types/photoswipe": "^4.1.1",
|
||||
"@types/react": "^19.0.8",
|
||||
|
@ -1,26 +1,20 @@
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "photos-web",
|
||||
"site": "https://web.ente.io"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
{
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "auth-web",
|
||||
"site": "https://auth.ente.io"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos.independent",
|
||||
@ -30,9 +24,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos",
|
||||
@ -44,9 +36,9 @@
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos",
|
||||
"sha256_cert_fingerprints": [
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos",
|
||||
"sha256_cert_fingerprints": [
|
||||
"37:D4:0B:10:3B:BF:86:43:EB:AE:23:B3:BB:73:F8:65:B4:E9:3A:BF:65:45:EF:37:12:8A:4C:EA:5B:C2:7E:2E"
|
||||
]
|
||||
}
|
||||
@ -54,17 +46,15 @@
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos.independent",
|
||||
"sha256_cert_fingerprints": [
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.photos.independent",
|
||||
"sha256_cert_fingerprints": [
|
||||
"37:D4:0B:10:3B:BF:86:43:EB:AE:23:B3:BB:73:F8:65:B4:E9:3A:BF:65:45:EF:37:12:8A:4C:EA:5B:C2:7E:2E"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "io.ente.auth",
|
||||
|
@ -42,13 +42,13 @@ import ExportInProgress from "./ExportInProgress";
|
||||
import ExportInit from "./ExportInit";
|
||||
|
||||
type ExportProps = ModalVisibilityProps & {
|
||||
collectionNameMap: Map<number, string>;
|
||||
allCollectionsNameByID: Map<number, string>;
|
||||
};
|
||||
|
||||
export const Export: React.FC<ExportProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
collectionNameMap,
|
||||
allCollectionsNameByID,
|
||||
}) => {
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const [exportStage, setExportStage] = useState(ExportStage.INIT);
|
||||
@ -202,7 +202,7 @@ export const Export: React.FC<ExportProps> = ({
|
||||
lastExportTime={lastExportTime}
|
||||
exportProgress={exportProgress}
|
||||
pendingExports={pendingExports}
|
||||
collectionNameMap={collectionNameMap}
|
||||
allCollectionsNameByID={allCollectionsNameByID}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
@ -291,7 +291,7 @@ const ExportDynamicContent = ({
|
||||
lastExportTime,
|
||||
exportProgress,
|
||||
pendingExports,
|
||||
collectionNameMap,
|
||||
allCollectionsNameByID,
|
||||
}: {
|
||||
exportStage: ExportStage;
|
||||
startExport: (opts?: ExportOpts) => void;
|
||||
@ -300,7 +300,7 @@ const ExportDynamicContent = ({
|
||||
lastExportTime: number;
|
||||
exportProgress: ExportProgress;
|
||||
pendingExports: EnteFile[];
|
||||
collectionNameMap: Map<number, string>;
|
||||
allCollectionsNameByID: Map<number, string>;
|
||||
}) => {
|
||||
switch (exportStage) {
|
||||
case ExportStage.INIT:
|
||||
@ -326,7 +326,7 @@ const ExportDynamicContent = ({
|
||||
onHide={onHide}
|
||||
lastExportTime={lastExportTime}
|
||||
pendingExports={pendingExports}
|
||||
collectionNameMap={collectionNameMap}
|
||||
allCollectionsNameByID={allCollectionsNameByID}
|
||||
onResync={() => startExport({ resync: true })}
|
||||
/>
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ import ExportPendingList from "./ExportPendingList";
|
||||
|
||||
interface Props {
|
||||
pendingExports: EnteFile[];
|
||||
collectionNameMap: Map<number, string>;
|
||||
allCollectionsNameByID: Map<number, string>;
|
||||
onHide: () => void;
|
||||
lastExportTime: number;
|
||||
/** Called when the user presses the "Resync" button. */
|
||||
@ -73,7 +73,7 @@ export default function ExportFinished(props: Props) {
|
||||
</DialogActions>
|
||||
<ExportPendingList
|
||||
pendingExports={props.pendingExports}
|
||||
collectionNameMap={props.collectionNameMap}
|
||||
allCollectionsNameByID={props.allCollectionsNameByID}
|
||||
isOpen={pendingFileListView}
|
||||
onClose={closePendingFileList}
|
||||
/>
|
||||
|
@ -10,7 +10,7 @@ import { t } from "i18next";
|
||||
interface Iprops {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
collectionNameMap: Map<number, string>;
|
||||
allCollectionsNameByID: Map<number, string>;
|
||||
pendingExports: EnteFile[];
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ const ExportPendingList = (props: Iprops) => {
|
||||
/>
|
||||
</Box>
|
||||
<ItemContainer>
|
||||
{`${props.collectionNameMap.get(file.collectionID)} / ${
|
||||
{`${props.allCollectionsNameByID.get(file.collectionID)} / ${
|
||||
file.metadata.title
|
||||
}`}
|
||||
</ItemContainer>
|
||||
@ -45,7 +45,7 @@ const ExportPendingList = (props: Iprops) => {
|
||||
};
|
||||
|
||||
const getItemTitle = (file: EnteFile) => {
|
||||
return `${props.collectionNameMap.get(file.collectionID)} / ${
|
||||
return `${props.allCollectionsNameByID.get(file.collectionID)} / ${
|
||||
file.metadata.title
|
||||
}`;
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
|
||||
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
|
||||
import log from "@/base/log";
|
||||
import { downloadManager } from "@/gallery/services/download";
|
||||
import { extractExifDates } from "@/gallery/services/exif";
|
||||
import { fileLogID, type EnteFile } from "@/media/file";
|
||||
import {
|
||||
decryptPublicMagicMetadata,
|
||||
@ -11,7 +12,6 @@ import {
|
||||
} from "@/media/file-metadata";
|
||||
import { FileType } from "@/media/file-type";
|
||||
import { PhotoDateTimePicker } from "@/new/photos/components/PhotoDateTimePicker";
|
||||
import { extractExifDates } from "@/new/photos/services/exif";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
@ -112,8 +112,8 @@ export interface PhotoFrameProps {
|
||||
/** This will be set if mode is "people". */
|
||||
activePersonID?: string | undefined;
|
||||
enableDownload?: boolean;
|
||||
fileToCollectionsMap?: Map<number, number[]>;
|
||||
collectionNameMap?: Map<number, string>;
|
||||
fileCollectionIDs?: Map<number, number[]>;
|
||||
allCollectionsNameByID?: Map<number, string>;
|
||||
showAppDownloadBanner?: boolean;
|
||||
setIsPhotoSwipeOpen?: (value: boolean) => void;
|
||||
isInHiddenSection?: boolean;
|
||||
@ -138,8 +138,8 @@ const PhotoFrame = ({
|
||||
activeCollectionID,
|
||||
activePersonID,
|
||||
enableDownload,
|
||||
fileToCollectionsMap,
|
||||
collectionNameMap,
|
||||
fileCollectionIDs,
|
||||
allCollectionsNameByID,
|
||||
showAppDownloadBanner,
|
||||
setIsPhotoSwipeOpen,
|
||||
isInHiddenSection,
|
||||
@ -555,8 +555,8 @@ const PhotoFrame = ({
|
||||
favoriteFileIDs,
|
||||
markUnsyncedFavoriteUpdate,
|
||||
markTempDeleted,
|
||||
collectionNameMap,
|
||||
fileToCollectionsMap,
|
||||
allCollectionsNameByID,
|
||||
fileCollectionIDs,
|
||||
setFilesDownloadProgressAttributesCreator,
|
||||
onSelectPerson,
|
||||
}}
|
||||
|
@ -12,6 +12,7 @@ import log from "@/base/log";
|
||||
import { FileInfo, type FileInfoProps } from "@/gallery/components/FileInfo";
|
||||
import { type FileInfoExif } from "@/gallery/components/viewer/data-source";
|
||||
import { downloadManager } from "@/gallery/services/download";
|
||||
import { extractRawExif, parseExif } from "@/gallery/services/exif";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { fileLogID, type EnteFile } from "@/media/file";
|
||||
import { FileType } from "@/media/file-type";
|
||||
@ -19,7 +20,6 @@ import { isHEICExtension, needsJPEGConversion } from "@/media/formats";
|
||||
import { ConfirmDeleteFileDialog } from "@/new/photos/components/FileViewerComponents";
|
||||
import { ImageEditorOverlay } from "@/new/photos/components/ImageEditorOverlay";
|
||||
import { moveToTrash } from "@/new/photos/services/collection";
|
||||
import { extractRawExif, parseExif } from "@/new/photos/services/exif";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
@ -102,8 +102,8 @@ export type PhotoViewerProps = Pick<
|
||||
isInHiddenSection: boolean;
|
||||
enableDownload: boolean;
|
||||
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
|
||||
fileToCollectionsMap?: Map<number, number[]>;
|
||||
collectionNameMap?: Map<number, string>;
|
||||
fileCollectionIDs?: Map<number, number[]>;
|
||||
allCollectionsNameByID?: Map<number, string>;
|
||||
onSelectPerson?: FileInfoProps["onSelectPerson"];
|
||||
};
|
||||
|
||||
@ -136,8 +136,8 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
isInHiddenSection,
|
||||
enableDownload,
|
||||
setFilesDownloadProgressAttributesCreator,
|
||||
fileToCollectionsMap,
|
||||
collectionNameMap,
|
||||
fileCollectionIDs,
|
||||
allCollectionsNameByID,
|
||||
onSelectPerson,
|
||||
}) => {
|
||||
const { showLoadingBar, hideLoadingBar } = usePhotosAppContext();
|
||||
@ -993,15 +993,15 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
onClose={handleCloseInfo}
|
||||
file={photoSwipe?.currItem as EnteFile}
|
||||
exif={exif?.value}
|
||||
shouldDisableEdits={!isOwnFile}
|
||||
allowEdits={isOwnFile}
|
||||
allowMap={!publicCollectionGalleryContext.credentials}
|
||||
showCollectionChips={
|
||||
showCollections={
|
||||
!isTrashCollection && isOwnFile && !isInHiddenSection
|
||||
}
|
||||
fileCollectionIDs={fileCollectionIDs}
|
||||
allCollectionsNameByID={allCollectionsNameByID}
|
||||
scheduleUpdate={scheduleUpdate}
|
||||
refreshPhotoswipe={refreshPhotoswipe}
|
||||
fileToCollectionsMap={fileToCollectionsMap}
|
||||
collectionNameMap={collectionNameMap}
|
||||
onSelectCollection={handleSelectCollection}
|
||||
onSelectPerson={handleSelectPerson}
|
||||
/>
|
||||
|
@ -1102,8 +1102,8 @@ const Page: React.FC = () => {
|
||||
activeCollectionID={activeCollectionID}
|
||||
activePersonID={activePerson?.id}
|
||||
enableDownload={true}
|
||||
fileToCollectionsMap={state.fileCollectionIDs}
|
||||
collectionNameMap={state.allCollectionNameByID}
|
||||
fileCollectionIDs={state.fileCollectionIDs}
|
||||
allCollectionsNameByID={state.allCollectionsNameByID}
|
||||
showAppDownloadBanner={
|
||||
files.length < 30 && !isInSearchMode
|
||||
}
|
||||
@ -1119,7 +1119,7 @@ const Page: React.FC = () => {
|
||||
)}
|
||||
<Export
|
||||
{...exportVisibilityProps}
|
||||
collectionNameMap={state.allCollectionNameByID}
|
||||
allCollectionsNameByID={state.allCollectionsNameByID}
|
||||
/>
|
||||
<AuthenticateUserModal
|
||||
open={authenticateUserModalView}
|
||||
|
@ -548,8 +548,8 @@ export default function PublicCollectionGallery() {
|
||||
selected={selected}
|
||||
activeCollectionID={ALL_SECTION}
|
||||
enableDownload={downloadEnabled}
|
||||
fileToCollectionsMap={undefined}
|
||||
collectionNameMap={undefined}
|
||||
fileCollectionIDs={undefined}
|
||||
allCollectionsNameByID={undefined}
|
||||
setFilesDownloadProgressAttributesCreator={
|
||||
setFilesDownloadProgressAttributesCreator
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { ensureElectron } from "@/base/electron";
|
||||
import { basename, nameAndExtension } from "@/base/file-name";
|
||||
import type { PublicAlbumsCredentials } from "@/base/http";
|
||||
import log from "@/base/log";
|
||||
import { extractExif } from "@/gallery/services/exif";
|
||||
import { extractVideoMetadata } from "@/gallery/services/ffmpeg";
|
||||
import {
|
||||
getNonEmptyMagicMetadataProps,
|
||||
@ -38,7 +39,6 @@ import {
|
||||
import { FileType, type FileTypeInfo } from "@/media/file-type";
|
||||
import { encodeLivePhoto } from "@/media/live-photo";
|
||||
import { addToCollection } from "@/new/photos/services/collection";
|
||||
import { extractExif } from "@/new/photos/services/exif";
|
||||
import { mergeUint8Arrays } from "@/utils/array";
|
||||
import { ensureInteger, ensureNumber } from "@/utils/ensure";
|
||||
import { CustomError, handleUploadError } from "@ente/shared/error";
|
||||
|
@ -348,14 +348,6 @@ export function isValidReplacementAlbum(
|
||||
);
|
||||
}
|
||||
|
||||
export function getCollectionNameMap(
|
||||
collections: Collection[],
|
||||
): Map<number, string> {
|
||||
return new Map<number, string>(
|
||||
collections.map((collection) => [collection.id, collection.name]),
|
||||
);
|
||||
}
|
||||
|
||||
export const getOrCreateAlbum = async (
|
||||
albumName: string,
|
||||
existingCollections: Collection[],
|
||||
|
@ -34,14 +34,6 @@ 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
|
||||
|
@ -21,13 +21,7 @@
|
||||
"dev:payments": "yarn workspace payments dev",
|
||||
"dev:photos": "yarn workspace photos next dev -p 3000",
|
||||
"lint": "concurrently --names 'prettier,eslint,tsc' \"yarn prettier --check --log-level warn .\" \"yarn workspaces run eslint\" \"yarn workspaces run tsc\"",
|
||||
"lint-fix": "concurrently --names 'prettier,eslint,tsc' \"yarn prettier --write --log-level warn .\" \"yarn workspaces run eslint --fix\" \"yarn workspaces run tsc\"",
|
||||
"preview": "yarn preview:photos",
|
||||
"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:payments": "yarn workspace payments preview",
|
||||
"preview:photos": "yarn build:photos && python3 -m http.server -d apps/photos/out 3000"
|
||||
"lint-fix": "concurrently --names 'prettier,eslint,tsc' \"yarn prettier --write --log-level warn .\" \"yarn workspaces run eslint --fix\" \"yarn workspaces run tsc\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.1",
|
||||
|
@ -6,6 +6,8 @@
|
||||
"@/base": "*",
|
||||
"@ente/shared": "*",
|
||||
"@types/zxcvbn": "^4.4.5",
|
||||
"bip39": "^3.0.4",
|
||||
"fast-srp-hap": "^2.0.4",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"uuid": "^11.0.5",
|
||||
"zxcvbn": "^4.4.2"
|
||||
|
@ -1,7 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
/* TODO: Audit this file
|
||||
Plan of action:
|
||||
- Move common components into FileInfoComponents.tsx
|
||||
@ -9,7 +6,6 @@ Plan of action:
|
||||
- Move the rest out to files in the apps themeselves: albums/SharedFileInfo
|
||||
and photos/FileInfo to deal with the @/new/photos imports here.
|
||||
*/
|
||||
/* @ts-nocheck */
|
||||
|
||||
import { LinkButtonUndecorated } from "@/base/components/LinkButton";
|
||||
import { TitledMiniDialog } from "@/base/components/MiniDialog";
|
||||
@ -28,6 +24,7 @@ import { nameAndExtension } from "@/base/file-name";
|
||||
import log from "@/base/log";
|
||||
import type { Location } from "@/base/types";
|
||||
import { CopyButton } from "@/gallery/components/FileInfoComponents";
|
||||
import { tagNumericValue, type RawExifTags } from "@/gallery/services/exif";
|
||||
import {
|
||||
changeCaption,
|
||||
changeFileName,
|
||||
@ -53,7 +50,6 @@ import {
|
||||
aboveFileViewerContentZ,
|
||||
fileInfoDrawerZ,
|
||||
} from "@/new/photos/components/utils/z-index";
|
||||
import { tagNumericValue, type RawExifTags } from "@/new/photos/services/exif";
|
||||
import {
|
||||
getAnnotatedFacesForFile,
|
||||
isMLEnabled,
|
||||
@ -98,7 +94,7 @@ import type { FileInfoExif } from "./viewer/data-source";
|
||||
// Re-uses images from ~leaflet package.
|
||||
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-expressions
|
||||
haveWindow() && require("leaflet-defaulticon-compatibility");
|
||||
const leaflet = haveWindow()
|
||||
? // eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
@ -110,21 +106,42 @@ export type FileInfoProps = ModalVisibilityProps & {
|
||||
* The file whose information we are showing.
|
||||
*/
|
||||
file: EnteFile | undefined;
|
||||
/**
|
||||
* Exif information for {@link file}.
|
||||
*/
|
||||
exif: FileInfoExif | undefined;
|
||||
/**
|
||||
* TODO: Rename and flip to allowEdits.
|
||||
* If set, then controls to edit the file's metadata (name, date, caption)
|
||||
* will be shown.
|
||||
*/
|
||||
shouldDisableEdits: boolean;
|
||||
allowEdits?: boolean;
|
||||
/**
|
||||
* If `true`, an inline map will be shown (if the user has enabled it) using
|
||||
* the file's location.
|
||||
* If set, then an inline map will be shown (if the user has enabled it)
|
||||
* using the file's location.
|
||||
*/
|
||||
allowMap: boolean;
|
||||
allowMap?: boolean;
|
||||
/**
|
||||
* If set, then a clickable chip will be shown for each collection that this
|
||||
* file is a part of.
|
||||
*
|
||||
* Uses {@link fileCollectionIDs} and {@link allCollectionsNameByID}, so
|
||||
* both of those props should also be set for this to have an effect.
|
||||
*/
|
||||
showCollections?: boolean;
|
||||
/**
|
||||
* A map from file IDs to the IDs of the collections that they're a part of.
|
||||
*
|
||||
* Used when {@link showCollections} is set.
|
||||
*/
|
||||
fileCollectionIDs?: Map<number, number[]>;
|
||||
/**
|
||||
* A map from collection IDs to their name.
|
||||
*
|
||||
* Used when {@link showCollections} is set.
|
||||
*/
|
||||
allCollectionsNameByID?: Map<number, string>;
|
||||
scheduleUpdate: () => void;
|
||||
refreshPhotoswipe: () => void;
|
||||
fileToCollectionsMap?: Map<number, number[]>;
|
||||
collectionNameMap?: Map<number, string>;
|
||||
showCollectionChips: boolean;
|
||||
/**
|
||||
* Called when the user selects a collection from among the collections that
|
||||
* the file belongs to.
|
||||
@ -140,14 +157,14 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
file,
|
||||
shouldDisableEdits,
|
||||
allowMap,
|
||||
exif,
|
||||
allowEdits,
|
||||
allowMap,
|
||||
showCollections,
|
||||
fileCollectionIDs,
|
||||
allCollectionsNameByID,
|
||||
scheduleUpdate,
|
||||
refreshPhotoswipe,
|
||||
fileToCollectionsMap,
|
||||
collectionNameMap,
|
||||
showCollectionChips,
|
||||
onSelectCollection,
|
||||
onSelectPerson,
|
||||
}) => {
|
||||
@ -155,42 +172,33 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
|
||||
const { mapEnabled } = useSettingsSnapshot();
|
||||
|
||||
const [exifInfo, setExifInfo] = useState<ExifInfo | undefined>();
|
||||
const { show: showRawExif, props: rawExifVisibilityProps } =
|
||||
useModalVisibility();
|
||||
const [annotatedFaces, setAnnotatedFaces] = useState<AnnotatedFaceID[]>([]);
|
||||
|
||||
const location = useMemo(() => {
|
||||
if (file) {
|
||||
const location = fileLocation(file);
|
||||
if (location) return location;
|
||||
}
|
||||
return exif?.parsed?.location;
|
||||
}, [file, exif]);
|
||||
const { show: showRawExif, props: rawExifVisibilityProps } =
|
||||
useModalVisibility();
|
||||
|
||||
const location = useMemo(
|
||||
// Prefer the location in the EnteFile, then fall back to Exif.
|
||||
() => (file ? fileLocation(file) : undefined) ?? exif?.parsed?.location,
|
||||
[file, exif],
|
||||
);
|
||||
|
||||
const annotatedExif = useMemo(() => annotateExif(exif), [exif]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!file) return;
|
||||
|
||||
let didCancel = false;
|
||||
|
||||
void (async () => {
|
||||
const result = await getAnnotatedFacesForFile(file);
|
||||
!didCancel && setAnnotatedFaces(result);
|
||||
})();
|
||||
void getAnnotatedFacesForFile(file).then(
|
||||
(faces) => !didCancel && setAnnotatedFaces(faces),
|
||||
);
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
};
|
||||
}, [file]);
|
||||
|
||||
useEffect(() => {
|
||||
setExifInfo(parseExifInfo(exif));
|
||||
}, [exif]);
|
||||
|
||||
if (!file) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const openEnableMapConfirmationDialog = () =>
|
||||
showMiniDialog(
|
||||
confirmEnableMapsDialogAttributes(() => updateMapEnabled(true)),
|
||||
@ -204,39 +212,37 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
const handleSelectFace = ({ personID }: AnnotatedFaceID) =>
|
||||
onSelectPerson?.(personID);
|
||||
|
||||
if (!file) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<FileInfoSidebar open={open} onClose={onClose}>
|
||||
<Titlebar onClose={onClose} title={t("info")} backIsClose />
|
||||
<Stack sx={{ pt: 1, pb: 3, gap: "20px" }}>
|
||||
<RenderCaption
|
||||
<Caption
|
||||
{...{
|
||||
file,
|
||||
shouldDisableEdits,
|
||||
allowEdits,
|
||||
scheduleUpdate,
|
||||
refreshPhotoswipe,
|
||||
}}
|
||||
/>
|
||||
|
||||
<CreationTime
|
||||
{...{ file, shouldDisableEdits, scheduleUpdate }}
|
||||
/>
|
||||
|
||||
<RenderFileName
|
||||
<CreationTime {...{ file, allowEdits, scheduleUpdate }} />
|
||||
<FileName
|
||||
{...{
|
||||
file,
|
||||
exifInfo: exifInfo,
|
||||
shouldDisableEdits,
|
||||
exifInfo: annotatedExif,
|
||||
allowEdits,
|
||||
scheduleUpdate,
|
||||
}}
|
||||
/>
|
||||
|
||||
{exifInfo?.takenOnDevice && (
|
||||
{annotatedExif?.takenOnDevice && (
|
||||
<InfoItem
|
||||
icon={<CameraOutlinedIcon />}
|
||||
title={exifInfo?.takenOnDevice}
|
||||
caption={
|
||||
<BasicDeviceCamera {...{ parsedExif: exifInfo }} />
|
||||
}
|
||||
title={annotatedExif.takenOnDevice}
|
||||
caption={<BasicDeviceCamera {...{ annotatedExif }} />}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -307,35 +313,41 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
/>
|
||||
</InfoItem>
|
||||
)}
|
||||
{showCollectionChips && collectionNameMap && (
|
||||
<InfoItem icon={<FolderOutlinedIcon />}>
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
gap: 1,
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{fileToCollectionsMap
|
||||
?.get(file.id)
|
||||
?.filter((collectionID) =>
|
||||
collectionNameMap.has(collectionID),
|
||||
)
|
||||
?.map((collectionID) => (
|
||||
<ChipButton
|
||||
key={collectionID}
|
||||
onClick={() =>
|
||||
onSelectCollection(collectionID)
|
||||
}
|
||||
>
|
||||
{collectionNameMap.get(collectionID)}
|
||||
</ChipButton>
|
||||
))}
|
||||
</Stack>
|
||||
</InfoItem>
|
||||
)}
|
||||
{showCollections &&
|
||||
fileCollectionIDs &&
|
||||
allCollectionsNameByID && (
|
||||
<InfoItem icon={<FolderOutlinedIcon />}>
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
gap: 1,
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{fileCollectionIDs
|
||||
.get(file.id)
|
||||
?.filter((collectionID) =>
|
||||
allCollectionsNameByID.has(
|
||||
collectionID,
|
||||
),
|
||||
)
|
||||
.map((collectionID) => (
|
||||
<ChipButton
|
||||
key={collectionID}
|
||||
onClick={() =>
|
||||
onSelectCollection(collectionID)
|
||||
}
|
||||
>
|
||||
{allCollectionsNameByID.get(
|
||||
collectionID,
|
||||
)}
|
||||
</ChipButton>
|
||||
))}
|
||||
</Stack>
|
||||
</InfoItem>
|
||||
)}
|
||||
</Stack>
|
||||
<RawExif
|
||||
{...rawExifVisibilityProps}
|
||||
@ -351,7 +363,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
* Some immediate fields of interest, in the form that we want to display on the
|
||||
* info panel for a file.
|
||||
*/
|
||||
type ExifInfo = Required<FileInfoExif> & {
|
||||
type AnnotatedExif = Required<FileInfoExif> & {
|
||||
resolution?: string;
|
||||
megaPixels?: string;
|
||||
takenOnDevice?: string;
|
||||
@ -360,13 +372,13 @@ type ExifInfo = Required<FileInfoExif> & {
|
||||
iso?: string;
|
||||
};
|
||||
|
||||
const parseExifInfo = (
|
||||
const annotateExif = (
|
||||
fileInfoExif: FileInfoExif | undefined,
|
||||
): ExifInfo | undefined => {
|
||||
): AnnotatedExif | undefined => {
|
||||
if (!fileInfoExif || !fileInfoExif.tags || !fileInfoExif.parsed)
|
||||
return undefined;
|
||||
|
||||
const info: ExifInfo = { ...fileInfoExif };
|
||||
const info: AnnotatedExif = { ...fileInfoExif };
|
||||
|
||||
const { width, height } = fileInfoExif.parsed;
|
||||
if (width && height) {
|
||||
@ -391,6 +403,7 @@ const parseExifInfo = (
|
||||
if (exif.ISOSpeedRatings)
|
||||
info.iso = `ISO${tagNumericValue(exif.ISOSpeedRatings)}`;
|
||||
}
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
@ -466,9 +479,7 @@ const InfoItem: React.FC<React.PropsWithChildren<InfoItemProps>> = ({
|
||||
>
|
||||
<InfoItemIconContainer>{icon}</InfoItemIconContainer>
|
||||
<Box sx={{ flex: 1, mt: "4px" }}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
{children ?? (
|
||||
<>
|
||||
<Typography sx={{ wordBreak: "break-all" }}>
|
||||
{title}
|
||||
@ -517,51 +528,49 @@ const EditButton: React.FC<EditButtonProps> = ({ onClick, loading }) => (
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
interface RenderCaptionFormValues {
|
||||
interface CaptionFormValues {
|
||||
caption: string;
|
||||
}
|
||||
|
||||
function RenderCaption({
|
||||
file,
|
||||
scheduleUpdate,
|
||||
refreshPhotoswipe,
|
||||
shouldDisableEdits,
|
||||
}: {
|
||||
shouldDisableEdits: boolean;
|
||||
/* TODO: This is DisplayFile, but that's meant to be deprecated */
|
||||
type CaptionProps = Pick<
|
||||
FileInfoProps,
|
||||
"allowEdits" | "scheduleUpdate" | "refreshPhotoswipe"
|
||||
> & {
|
||||
/* TODO(PS): This is DisplayFile, but that's meant to be removed */
|
||||
file: EnteFile & {
|
||||
title?: string;
|
||||
};
|
||||
scheduleUpdate: () => void;
|
||||
refreshPhotoswipe: () => void;
|
||||
}) {
|
||||
const [caption, setCaption] = useState(
|
||||
file?.pubMagicMetadata?.data.caption,
|
||||
);
|
||||
};
|
||||
|
||||
const Caption: React.FC<CaptionProps> = ({
|
||||
file,
|
||||
allowEdits,
|
||||
scheduleUpdate,
|
||||
refreshPhotoswipe,
|
||||
}) => {
|
||||
const [caption, setCaption] = useState(file.pubMagicMetadata?.data.caption);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const saveEdits = async (newCaption: string) => {
|
||||
try {
|
||||
if (file) {
|
||||
if (caption === newCaption) {
|
||||
return;
|
||||
}
|
||||
setCaption(newCaption);
|
||||
|
||||
const updatedFile = await changeCaption(file, newCaption);
|
||||
updateExistingFilePubMetadata(file, updatedFile);
|
||||
// @ts-ignore
|
||||
file.title = file.pubMagicMetadata.data.caption;
|
||||
refreshPhotoswipe();
|
||||
scheduleUpdate();
|
||||
if (caption === newCaption) {
|
||||
return;
|
||||
}
|
||||
setCaption(newCaption);
|
||||
|
||||
const updatedFile = await changeCaption(file, newCaption);
|
||||
updateExistingFilePubMetadata(file, updatedFile);
|
||||
// @ts-ignore
|
||||
file.title = file.pubMagicMetadata.data.caption;
|
||||
refreshPhotoswipe();
|
||||
scheduleUpdate();
|
||||
} catch (e) {
|
||||
log.error("failed to update caption", e);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (values: RenderCaptionFormValues) => {
|
||||
const onSubmit = async (values: CaptionFormValues) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await saveEdits(values.caption);
|
||||
@ -569,12 +578,14 @@ function RenderCaption({
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (!caption?.length && shouldDisableEdits) {
|
||||
|
||||
if (!caption?.length && !allowEdits) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Formik<RenderCaptionFormValues>
|
||||
<Formik<CaptionFormValues>
|
||||
// @ts-ignore
|
||||
initialValues={{ caption }}
|
||||
validationSchema={Yup.object().shape({
|
||||
@ -606,7 +617,7 @@ function RenderCaption({
|
||||
onChange={handleChange("caption")}
|
||||
error={Boolean(errors.caption)}
|
||||
helperText={errors.caption}
|
||||
disabled={loading || shouldDisableEdits}
|
||||
disabled={!allowEdits || loading}
|
||||
/>
|
||||
{values.caption !== caption && (
|
||||
<FlexWrapper justifyContent={"flex-end"}>
|
||||
@ -638,17 +649,18 @@ function RenderCaption({
|
||||
</Formik>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
interface CreationTimeProps {
|
||||
type CreationTimeProps = Pick<
|
||||
FileInfoProps,
|
||||
"allowEdits" | "scheduleUpdate"
|
||||
> & {
|
||||
file: EnteFile;
|
||||
shouldDisableEdits: boolean;
|
||||
scheduleUpdate: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
const CreationTime: React.FC<CreationTimeProps> = ({
|
||||
file,
|
||||
shouldDisableEdits,
|
||||
allowEdits,
|
||||
scheduleUpdate,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -663,7 +675,7 @@ const CreationTime: React.FC<CreationTimeProps> = ({
|
||||
const saveEdits = async (pickedTime: ParsedMetadataDate) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (isInEditMode && file) {
|
||||
if (isInEditMode) {
|
||||
// [Note: Don't modify offsetTime when editing date via picker]
|
||||
//
|
||||
// Use the updated date time (both in its canonical dateTime
|
||||
@ -706,7 +718,7 @@ const CreationTime: React.FC<CreationTimeProps> = ({
|
||||
title={formatDate(originalDate)}
|
||||
caption={formatTime(originalDate)}
|
||||
trailingButton={
|
||||
shouldDisableEdits || (
|
||||
allowEdits && (
|
||||
<EditButton
|
||||
onClick={openEditMode}
|
||||
loading={loading}
|
||||
@ -727,17 +739,15 @@ const CreationTime: React.FC<CreationTimeProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface RenderFileNameProps {
|
||||
type FileNameProps = Pick<FileInfoProps, "allowEdits" | "scheduleUpdate"> & {
|
||||
file: EnteFile;
|
||||
shouldDisableEdits: boolean;
|
||||
exifInfo: ExifInfo | undefined;
|
||||
scheduleUpdate: () => void;
|
||||
}
|
||||
exifInfo: AnnotatedExif | undefined;
|
||||
};
|
||||
|
||||
const RenderFileName: React.FC<RenderFileNameProps> = ({
|
||||
const FileName: React.FC<FileNameProps> = ({
|
||||
file,
|
||||
shouldDisableEdits,
|
||||
exifInfo,
|
||||
allowEdits,
|
||||
scheduleUpdate,
|
||||
}) => {
|
||||
const [isInEditMode, setIsInEditMode] = useState(false);
|
||||
@ -753,7 +763,6 @@ const RenderFileName: React.FC<RenderFileNameProps> = ({
|
||||
}, [file]);
|
||||
|
||||
const saveEdits = async (newFilename: string) => {
|
||||
if (!file) return;
|
||||
if (fileName === newFilename) {
|
||||
closeEditMode();
|
||||
return;
|
||||
@ -778,7 +787,7 @@ const RenderFileName: React.FC<RenderFileNameProps> = ({
|
||||
title={[fileName, extension].join(".")}
|
||||
caption={getCaption(file, exifInfo)}
|
||||
trailingButton={
|
||||
shouldDisableEdits || <EditButton onClick={openEditMode} />
|
||||
allowEdits && <EditButton onClick={openEditMode} />
|
||||
}
|
||||
/>
|
||||
<FileNameEditDialog
|
||||
@ -792,7 +801,7 @@ const RenderFileName: React.FC<RenderFileNameProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getCaption = (file: EnteFile, exifInfo: ExifInfo | undefined) => {
|
||||
const getCaption = (file: EnteFile, exifInfo: AnnotatedExif | undefined) => {
|
||||
const megaPixels = exifInfo?.megaPixels;
|
||||
const resolution = exifInfo?.resolution;
|
||||
const fileSize = file.info?.fileSize;
|
||||
@ -864,17 +873,15 @@ const FileNameEditDialog: React.FC<FileNameEditDialogProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const BasicDeviceCamera: React.FC<{ parsedExif: ExifInfo }> = ({
|
||||
parsedExif,
|
||||
}) => {
|
||||
return (
|
||||
<FlexWrapper gap={1}>
|
||||
<Box>{parsedExif.fNumber}</Box>
|
||||
<Box>{parsedExif.exposureTime}</Box>
|
||||
<Box>{parsedExif.iso}</Box>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
const BasicDeviceCamera: React.FC<{ annotatedExif: AnnotatedExif }> = ({
|
||||
annotatedExif,
|
||||
}) => (
|
||||
<Stack direction="row" sx={{ gap: 1 }}>
|
||||
<Box>{annotatedExif.fNumber}</Box>
|
||||
<Box>{annotatedExif.exposureTime}</Box>
|
||||
<Box>{annotatedExif.iso}</Box>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const openStreetMapLink = ({ latitude, longitude }: Location) =>
|
||||
`https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
extractRawExif,
|
||||
parseExif,
|
||||
type RawExifTags,
|
||||
} from "@/new/photos/services/exif";
|
||||
} from "@/gallery/services/exif";
|
||||
|
||||
/**
|
||||
* This is a subset of the fields expected by PhotoSwipe itself (see the
|
||||
|
@ -400,7 +400,7 @@ export class FileViewerPhotoSwipe {
|
||||
private autoHideIfInactive() {
|
||||
if (this.lastActivityDate == "already-hidden") return;
|
||||
if (this.lastActivityDate == "auto-hidden") return;
|
||||
if (Date.now() - this.lastActivityDate.getTime() > 3000) {
|
||||
if (Date.now() - this.lastActivityDate.getTime() > 5000 /* 5s */) {
|
||||
if (this.areUIControlsVisible()) {
|
||||
this.hideUIControlsIfNotFocused();
|
||||
this.lastActivityDate = "auto-hidden";
|
||||
|
@ -6,9 +6,14 @@
|
||||
"@/base": "*",
|
||||
"@/utils": "*",
|
||||
"@ffmpeg/ffmpeg": "^0.12.10",
|
||||
"bs58": "^6.0.0"
|
||||
"bs58": "^6.0.0",
|
||||
"exifreader": "^4.26.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-defaulticon-compatibility": "^0.1.2",
|
||||
"react-dropzone": "14.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@/build-config": "*"
|
||||
"@/build-config": "*",
|
||||
"@types/leaflet": "^1.9.16"
|
||||
}
|
||||
}
|
||||
|
@ -195,14 +195,13 @@ export interface GalleryState {
|
||||
*/
|
||||
favoriteFileIDs: Set<number>;
|
||||
/**
|
||||
* User visible collection names indexed by collection IDs for fast lookup.
|
||||
* A map from collection IDs to their user visible name.
|
||||
*
|
||||
* This map will contain entries for all (both normal and hidden)
|
||||
* collections.
|
||||
* It will contain entries for all collections (both normal and hidden).
|
||||
*/
|
||||
allCollectionNameByID: Map<number, string>;
|
||||
allCollectionsNameByID: Map<number, string>;
|
||||
/**
|
||||
* A list of collection IDs to which a file belongs, indexed by file ID.
|
||||
* A map from file IDs to the IDs of the collections that they're a part of.
|
||||
*/
|
||||
fileCollectionIDs: Map<number, number[]>;
|
||||
|
||||
@ -420,7 +419,7 @@ const initialGalleryState: GalleryState = {
|
||||
hiddenFileIDs: new Set(),
|
||||
archivedFileIDs: new Set(),
|
||||
favoriteFileIDs: new Set(),
|
||||
allCollectionNameByID: new Map(),
|
||||
allCollectionsNameByID: new Map(),
|
||||
fileCollectionIDs: new Map(),
|
||||
collectionSummaries: new Map(),
|
||||
hiddenCollectionSummaries: new Map(),
|
||||
@ -481,7 +480,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
action.files,
|
||||
state.unsyncedFavoriteUpdates,
|
||||
),
|
||||
allCollectionNameByID: createCollectionNameByID(
|
||||
allCollectionsNameByID: createCollectionNameByID(
|
||||
action.allCollections,
|
||||
),
|
||||
fileCollectionIDs: createFileCollectionIDs(action.files),
|
||||
@ -539,7 +538,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
state.files,
|
||||
state.unsyncedFavoriteUpdates,
|
||||
),
|
||||
allCollectionNameByID: createCollectionNameByID(
|
||||
allCollectionsNameByID: createCollectionNameByID(
|
||||
collections.concat(state.hiddenCollections),
|
||||
),
|
||||
collectionSummaries,
|
||||
@ -608,7 +607,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
state.files,
|
||||
state.unsyncedFavoriteUpdates,
|
||||
),
|
||||
allCollectionNameByID: createCollectionNameByID(
|
||||
allCollectionsNameByID: createCollectionNameByID(
|
||||
collections.concat(hiddenCollections),
|
||||
),
|
||||
collectionSummaries,
|
||||
|
@ -980,7 +980,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz#c82f04a09ba481e13857d6f2516e072aaa51b7f4"
|
||||
integrity sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==
|
||||
|
||||
"@stripe/stripe-js@^1.13.2", "@stripe/stripe-js@^1.17.0":
|
||||
"@stripe/stripe-js@^1.17.0":
|
||||
version "1.54.2"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.54.2.tgz#0665848e22cbda936cfd05256facdfbba121438d"
|
||||
integrity sha512-R1PwtDvUfs99cAjfuQ/WpwJ3c92+DAMy9xGApjqlWQMj0FKQabUAys2swfTRNzuYAYJh7NqK2dzcYVNkKLEKUg==
|
||||
|
Loading…
x
Reference in New Issue
Block a user