Pre release (#1213)
This commit is contained in:
commit
c3724b8bbe
|
@ -2,11 +2,6 @@ const cp = require('child_process');
|
||||||
const { getIsSentryEnabled } = require('./sentryConfigUtil');
|
const { getIsSentryEnabled } = require('./sentryConfigUtil');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
COOP_COEP_HEADERS: {
|
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
||||||
},
|
|
||||||
|
|
||||||
WEB_SECURITY_HEADERS: {
|
WEB_SECURITY_HEADERS: {
|
||||||
'Strict-Transport-Security': ' max-age=63072000',
|
'Strict-Transport-Security': ' max-age=63072000',
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-Content-Type-Options': 'nosniff',
|
||||||
|
@ -20,7 +15,7 @@ module.exports = {
|
||||||
// self is safe enough
|
// self is safe enough
|
||||||
'default-src': "'self'",
|
'default-src': "'self'",
|
||||||
// data to allow two factor qr code
|
// data to allow two factor qr code
|
||||||
'img-src': "'self' blob: data:",
|
'img-src': "'self' blob: data: https://*.openstreetmap.org",
|
||||||
'media-src': "'self' blob:",
|
'media-src': "'self' blob:",
|
||||||
'manifest-src': "'self'",
|
'manifest-src': "'self'",
|
||||||
'style-src': "'self' 'unsafe-inline'",
|
'style-src': "'self' 'unsafe-inline'",
|
||||||
|
|
|
@ -53,6 +53,8 @@
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"is-electron": "^2.2.0",
|
"is-electron": "^2.2.0",
|
||||||
"jszip": "3.8.0",
|
"jszip": "3.8.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||||
"libsodium-wrappers": "^0.7.8",
|
"libsodium-wrappers": "^0.7.8",
|
||||||
"localforage": "^1.9.0",
|
"localforage": "^1.9.0",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
|
@ -93,6 +95,7 @@
|
||||||
"@next/bundle-analyzer": "^13.1.6",
|
"@next/bundle-analyzer": "^13.1.6",
|
||||||
"@types/bs58": "^4.0.1",
|
"@types/bs58": "^4.0.1",
|
||||||
"@types/debounce-promise": "^3.1.3",
|
"@types/debounce-promise": "^3.1.3",
|
||||||
|
"@types/leaflet": "^1.9.3",
|
||||||
"@types/libsodium-wrappers": "^0.7.8",
|
"@types/libsodium-wrappers": "^0.7.8",
|
||||||
"@types/node": "^14.6.4",
|
"@types/node": "^14.6.4",
|
||||||
"@types/photoswipe": "^4.1.1",
|
"@types/photoswipe": "^4.1.1",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,14 @@
|
||||||
"CAPTION_PLACEHOLDER": "Add a description",
|
"CAPTION_PLACEHOLDER": "Add a description",
|
||||||
"LOCATION": "Location",
|
"LOCATION": "Location",
|
||||||
"SHOW_ON_MAP": "View on OpenStreetMap",
|
"SHOW_ON_MAP": "View on OpenStreetMap",
|
||||||
|
"MAP":"Map",
|
||||||
|
"MAP_SETTINGS":"Map Settings",
|
||||||
|
"ENABLE_MAPS":"Enable Maps?",
|
||||||
|
"ENABLE_MAP":"Enable map",
|
||||||
|
"DISABLE_MAPS":"Disable Maps?",
|
||||||
|
"ENABLE_MAP_DESCRIPTION":"<p>This will show your photos on a world map.</p> <p>The map is hosted by <a>OpenStreetMap</a>, and the exact locations of your photos are never shared.</p> <p>You can disable this feature anytime from Settings.</p>",
|
||||||
|
"DISABLE_MAP_DESCRIPTION":"<p>This will disable the display of your photos on a world map.</p> <p>You can enable this feature anytime from Settings.</p>",
|
||||||
|
"DISABLE_MAP":"Disable map",
|
||||||
"DETAILS": "Details",
|
"DETAILS": "Details",
|
||||||
"VIEW_EXIF": "View all EXIF data",
|
"VIEW_EXIF": "View all EXIF data",
|
||||||
"NO_EXIF": "No EXIF data",
|
"NO_EXIF": "No EXIF data",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "Añadir una descripción",
|
"CAPTION_PLACEHOLDER": "Añadir una descripción",
|
||||||
"LOCATION": "Localización",
|
"LOCATION": "Localización",
|
||||||
"SHOW_ON_MAP": "Ver en OpenStreetMap",
|
"SHOW_ON_MAP": "Ver en OpenStreetMap",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "Detalles",
|
"DETAILS": "Detalles",
|
||||||
"VIEW_EXIF": "Ver todos los datos de EXIF",
|
"VIEW_EXIF": "Ver todos los datos de EXIF",
|
||||||
"NO_EXIF": "No hay datos EXIF",
|
"NO_EXIF": "No hay datos EXIF",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "Ajouter une description",
|
"CAPTION_PLACEHOLDER": "Ajouter une description",
|
||||||
"LOCATION": "Emplacement",
|
"LOCATION": "Emplacement",
|
||||||
"SHOW_ON_MAP": "Visualiser sur OpenStreetMap",
|
"SHOW_ON_MAP": "Visualiser sur OpenStreetMap",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "Détails",
|
"DETAILS": "Détails",
|
||||||
"VIEW_EXIF": "Visualiser toutes les données EXIF",
|
"VIEW_EXIF": "Visualiser toutes les données EXIF",
|
||||||
"NO_EXIF": "Aucune donnée EXIF",
|
"NO_EXIF": "Aucune donnée EXIF",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "Voeg een beschrijving toe",
|
"CAPTION_PLACEHOLDER": "Voeg een beschrijving toe",
|
||||||
"LOCATION": "Locatie",
|
"LOCATION": "Locatie",
|
||||||
"SHOW_ON_MAP": "Bekijk op OpenStreetMap",
|
"SHOW_ON_MAP": "Bekijk op OpenStreetMap",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "Details",
|
"DETAILS": "Details",
|
||||||
"VIEW_EXIF": "Bekijk alle EXIF gegevens",
|
"VIEW_EXIF": "Bekijk alle EXIF gegevens",
|
||||||
"NO_EXIF": "Geen EXIF gegevens",
|
"NO_EXIF": "Geen EXIF gegevens",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "",
|
"CAPTION_PLACEHOLDER": "",
|
||||||
"LOCATION": "",
|
"LOCATION": "",
|
||||||
"SHOW_ON_MAP": "",
|
"SHOW_ON_MAP": "",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "",
|
"DETAILS": "",
|
||||||
"VIEW_EXIF": "",
|
"VIEW_EXIF": "",
|
||||||
"NO_EXIF": "",
|
"NO_EXIF": "",
|
||||||
|
|
|
@ -232,6 +232,15 @@
|
||||||
"CAPTION_PLACEHOLDER": "添加说明",
|
"CAPTION_PLACEHOLDER": "添加说明",
|
||||||
"LOCATION": "地理位置",
|
"LOCATION": "地理位置",
|
||||||
"SHOW_ON_MAP": "在 OpenStreetMap 上查看",
|
"SHOW_ON_MAP": "在 OpenStreetMap 上查看",
|
||||||
|
"MAP": "",
|
||||||
|
"MAP_SETTINGS": "",
|
||||||
|
"ENABLE_MAPS": "",
|
||||||
|
"DISABLE_MAPS": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_2": "",
|
||||||
|
"ENABLE_MAP_DESCRIPTION_3": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_1": "",
|
||||||
|
"DISABLE_MAP_DESCRIPTION_2": "",
|
||||||
"DETAILS": "详情",
|
"DETAILS": "详情",
|
||||||
"VIEW_EXIF": "查看所有 EXIF 数据",
|
"VIEW_EXIF": "查看所有 EXIF 数据",
|
||||||
"NO_EXIF": "无 EXIF 数据",
|
"NO_EXIF": "无 EXIF 数据",
|
||||||
|
|
80
apps/photos/src/components/PhotoViewer/FileInfo/MapBox.tsx
Normal file
80
apps/photos/src/components/PhotoViewer/FileInfo/MapBox.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { runningInBrowser } from 'utils/common';
|
||||||
|
import { MapButton } from './MapButton';
|
||||||
|
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'; // Re-uses images from ~leaflet package
|
||||||
|
import { t } from 'i18next';
|
||||||
|
runningInBrowser() && require('leaflet-defaulticon-compatibility');
|
||||||
|
const L = runningInBrowser()
|
||||||
|
? (require('leaflet') as typeof import('leaflet'))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const LAYER_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
|
const LAYER_TILE_ATTRIBUTION =
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
|
||||||
|
const ZOOM_LEVEL = 16;
|
||||||
|
|
||||||
|
const MapBoxContainer = styled('div')`
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
const MapBoxEnableContainer = styled(MapBoxContainer)`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(255, 255, 255, 0.09);
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface MapBoxProps {
|
||||||
|
location: { latitude: number; longitude: number };
|
||||||
|
mapEnabled: boolean;
|
||||||
|
openUpdateMapConfirmationDialog: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MapBox: React.FC<MapBoxProps> = ({
|
||||||
|
location,
|
||||||
|
mapEnabled,
|
||||||
|
openUpdateMapConfirmationDialog,
|
||||||
|
}) => {
|
||||||
|
const mapBoxContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mapContainer = mapBoxContainerRef.current;
|
||||||
|
if (mapEnabled) {
|
||||||
|
const position: L.LatLngTuple = [
|
||||||
|
location.latitude,
|
||||||
|
location.longitude,
|
||||||
|
];
|
||||||
|
if (mapContainer && !mapContainer.hasChildNodes()) {
|
||||||
|
const map = L.map(mapContainer).setView(position, ZOOM_LEVEL);
|
||||||
|
L.tileLayer(LAYER_TILE_URL, {
|
||||||
|
attribution: LAYER_TILE_ATTRIBUTION,
|
||||||
|
}).addTo(map);
|
||||||
|
L.marker(position).addTo(map).openPopup();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mapContainer && mapContainer.hasChildNodes()) {
|
||||||
|
if (mapContainer.firstChild) {
|
||||||
|
console.log('removing child');
|
||||||
|
mapContainer.removeChild(mapContainer.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [mapEnabled]);
|
||||||
|
|
||||||
|
return mapEnabled ? (
|
||||||
|
<MapBoxContainer ref={mapBoxContainerRef} />
|
||||||
|
) : (
|
||||||
|
<MapBoxEnableContainer>
|
||||||
|
<MapButton onClick={openUpdateMapConfirmationDialog}>
|
||||||
|
{' '}
|
||||||
|
{t('ENABLE_MAP')}
|
||||||
|
</MapButton>
|
||||||
|
</MapBoxEnableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MapBox;
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Button, ButtonProps, styled } from '@mui/material';
|
||||||
|
import { CSSProperties } from '@mui/material/styles/createTypography';
|
||||||
|
|
||||||
|
export const MapButton = styled((props: ButtonProps) => (
|
||||||
|
<Button color="secondary" {...props} />
|
||||||
|
))(({ theme }) => ({
|
||||||
|
...(theme.typography.small as CSSProperties),
|
||||||
|
padding: '8px',
|
||||||
|
}));
|
|
@ -7,6 +7,7 @@ import { RenderCaption } from './RenderCaption';
|
||||||
import CopyButton from 'components/CodeBlock/CopyButton';
|
import CopyButton from 'components/CodeBlock/CopyButton';
|
||||||
import { formatDate, formatTime } from 'utils/time/format';
|
import { formatDate, formatTime } from 'utils/time/format';
|
||||||
import Titlebar from 'components/Titlebar';
|
import Titlebar from 'components/Titlebar';
|
||||||
|
import MapBox from './MapBox';
|
||||||
import InfoItem from './InfoItem';
|
import InfoItem from './InfoItem';
|
||||||
import { FlexWrapper } from 'components/Container';
|
import { FlexWrapper } from 'components/Container';
|
||||||
import EnteSpinner from 'components/EnteSpinner';
|
import EnteSpinner from 'components/EnteSpinner';
|
||||||
|
@ -32,6 +33,10 @@ import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
|
||||||
import { AppContext } from 'pages/_app';
|
import { AppContext } from 'pages/_app';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { GalleryContext } from 'pages/gallery';
|
import { GalleryContext } from 'pages/gallery';
|
||||||
|
import {
|
||||||
|
getMapDisableConfirmationDialog,
|
||||||
|
getMapEnableConfirmationDialog,
|
||||||
|
} from 'utils/ui';
|
||||||
|
|
||||||
export const FileInfoSidebar = styled((props: DialogProps) => (
|
export const FileInfoSidebar = styled((props: DialogProps) => (
|
||||||
<EnteDrawer {...props} anchor="right" />
|
<EnteDrawer {...props} anchor="right" />
|
||||||
|
@ -170,9 +175,24 @@ export function FileInfo({
|
||||||
}
|
}
|
||||||
const onCollectionChipClick = (collectionID) => {
|
const onCollectionChipClick = (collectionID) => {
|
||||||
galleryContext.setActiveCollection(collectionID);
|
galleryContext.setActiveCollection(collectionID);
|
||||||
|
galleryContext.setIsInSearchMode(false);
|
||||||
closePhotoViewer();
|
closePhotoViewer();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openEnableMapConfirmationDialog = () =>
|
||||||
|
appContext.setDialogBoxAttributesV2(
|
||||||
|
getMapEnableConfirmationDialog(() =>
|
||||||
|
appContext.updateMapEnabled(true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const openDisableMapConfirmationDialog = () =>
|
||||||
|
appContext.setDialogBoxAttributesV2(
|
||||||
|
getMapDisableConfirmationDialog(() =>
|
||||||
|
appContext.updateMapEnabled(false)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
|
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
|
||||||
<Titlebar onClose={handleCloseInfo} title={t('INFO')} backIsClose />
|
<Titlebar onClose={handleCloseInfo} title={t('INFO')} backIsClose />
|
||||||
|
@ -210,25 +230,48 @@ export function FileInfo({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{location && (
|
{location && (
|
||||||
<InfoItem
|
<>
|
||||||
icon={<LocationOnOutlined />}
|
<InfoItem
|
||||||
title={t('LOCATION')}
|
icon={<LocationOnOutlined />}
|
||||||
caption={
|
title={t('LOCATION')}
|
||||||
<Link
|
caption={
|
||||||
href={getOpenStreetMapLink(location)}
|
!appContext.mapEnabled ? (
|
||||||
target="_blank"
|
<Link
|
||||||
sx={{ fontWeight: 'bold' }}>
|
href={getOpenStreetMapLink(location)}
|
||||||
{t('SHOW_ON_MAP')}
|
target="_blank"
|
||||||
</Link>
|
sx={{ fontWeight: 'bold' }}>
|
||||||
}
|
{t('SHOW_ON_MAP')}
|
||||||
customEndButton={
|
</Link>
|
||||||
<CopyButton
|
) : (
|
||||||
code={getOpenStreetMapLink(location)}
|
<LinkButton
|
||||||
color="secondary"
|
onClick={
|
||||||
size="medium"
|
openDisableMapConfirmationDialog
|
||||||
/>
|
}
|
||||||
}
|
sx={{
|
||||||
/>
|
textDecoration: 'none',
|
||||||
|
color: 'text.muted',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}>
|
||||||
|
{t('DISABLE_MAP')}
|
||||||
|
</LinkButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
customEndButton={
|
||||||
|
<CopyButton
|
||||||
|
code={getOpenStreetMapLink(location)}
|
||||||
|
color="secondary"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<MapBox
|
||||||
|
location={location}
|
||||||
|
mapEnabled={appContext.mapEnabled}
|
||||||
|
openUpdateMapConfirmationDialog={
|
||||||
|
openEnableMapConfirmationDialog
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<InfoItem
|
<InfoItem
|
||||||
icon={<TextSnippetOutlined />}
|
icon={<TextSnippetOutlined />}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
|
||||||
import Titlebar from 'components/Titlebar';
|
import Titlebar from 'components/Titlebar';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
||||||
import { MenuItemGroup } from 'components/Menu/MenuItemGroup';
|
import { MenuItemGroup } from 'components/Menu/MenuItemGroup';
|
||||||
|
|
||||||
|
|
34
apps/photos/src/components/Sidebar/DisableMap.tsx
Normal file
34
apps/photos/src/components/Sidebar/DisableMap.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { Stack, Box, Button, Typography } from '@mui/material';
|
||||||
|
import Titlebar from 'components/Titlebar';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export default function EnableMap({ onClose, disableMap, onRootClose }) {
|
||||||
|
return (
|
||||||
|
<Stack spacing={'4px'} py={'12px'}>
|
||||||
|
<Titlebar
|
||||||
|
onClose={onClose}
|
||||||
|
title={t('DISABLE_MAPS')}
|
||||||
|
onRootClose={onRootClose}
|
||||||
|
/>
|
||||||
|
<Stack py={'20px'} px={'8px'} spacing={'32px'}>
|
||||||
|
<Box px={'8px'}>
|
||||||
|
<Typography color="text.muted">
|
||||||
|
<Trans i18nKey={'DISABLE_MAP_DESCRIPTION'} />
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack px={'8px'} spacing={'8px'}>
|
||||||
|
<Button
|
||||||
|
color={'critical'}
|
||||||
|
size="large"
|
||||||
|
onClick={disableMap}>
|
||||||
|
{t('DISABLE')}
|
||||||
|
</Button>
|
||||||
|
<Button color={'secondary'} size="large" onClick={onClose}>
|
||||||
|
{t('CANCEL')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
43
apps/photos/src/components/Sidebar/EnableMap.tsx
Normal file
43
apps/photos/src/components/Sidebar/EnableMap.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { Stack, Box, Button, Typography, Link } from '@mui/material';
|
||||||
|
import Titlebar from 'components/Titlebar';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export const OPEN_STREET_MAP_LINK = 'https://www.openstreetmap.org/';
|
||||||
|
export default function EnableMap({ onClose, enableMap, onRootClose }) {
|
||||||
|
return (
|
||||||
|
<Stack spacing={'4px'} py={'12px'}>
|
||||||
|
<Titlebar
|
||||||
|
onClose={onClose}
|
||||||
|
title={t('ENABLE_MAPS')}
|
||||||
|
onRootClose={onRootClose}
|
||||||
|
/>
|
||||||
|
<Stack py={'20px'} px={'8px'} spacing={'32px'}>
|
||||||
|
<Box px={'8px'}>
|
||||||
|
{' '}
|
||||||
|
<Typography color="text.muted">
|
||||||
|
<Trans
|
||||||
|
i18nKey={'ENABLE_MAP_DESCRIPTION'}
|
||||||
|
components={{
|
||||||
|
a: (
|
||||||
|
<Link
|
||||||
|
target="_blank"
|
||||||
|
href={OPEN_STREET_MAP_LINK}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack px={'8px'} spacing={'8px'}>
|
||||||
|
<Button color={'accent'} size="large" onClick={enableMap}>
|
||||||
|
{t('ENABLE')}
|
||||||
|
</Button>
|
||||||
|
<Button color={'secondary'} size="large" onClick={onClose}>
|
||||||
|
{t('CANCEL')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Box, DialogProps } from '@mui/material';
|
||||||
|
import { EnteDrawer } from 'components/EnteDrawer';
|
||||||
|
import { AppContext } from 'pages/_app';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { logError } from 'utils/sentry';
|
||||||
|
import EnableMap from '../EnableMap';
|
||||||
|
import DisableMap from '../DisableMap';
|
||||||
|
|
||||||
|
const ModifyMapEnabled = ({ open, onClose, onRootClose, mapEnabled }) => {
|
||||||
|
const { somethingWentWrong, updateMapEnabled } = useContext(AppContext);
|
||||||
|
|
||||||
|
const disableMap = async () => {
|
||||||
|
try {
|
||||||
|
await updateMapEnabled(false);
|
||||||
|
onClose();
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'Disable Map failed');
|
||||||
|
somethingWentWrong();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableMap = async () => {
|
||||||
|
try {
|
||||||
|
await updateMapEnabled(true);
|
||||||
|
onClose();
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'Enable Map failed');
|
||||||
|
somethingWentWrong();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRootClose = () => {
|
||||||
|
onClose();
|
||||||
|
onRootClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerClose: DialogProps['onClose'] = (_, reason) => {
|
||||||
|
if (reason === 'backdropClick') {
|
||||||
|
handleRootClose();
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<EnteDrawer
|
||||||
|
anchor="left"
|
||||||
|
transitionDuration={0}
|
||||||
|
open={open}
|
||||||
|
onClose={handleDrawerClose}
|
||||||
|
slotProps={{
|
||||||
|
backdrop: {
|
||||||
|
sx: { '&&&': { backgroundColor: 'transparent' } },
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
{mapEnabled ? (
|
||||||
|
<DisableMap
|
||||||
|
onClose={onClose}
|
||||||
|
disableMap={disableMap}
|
||||||
|
onRootClose={handleRootClose}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EnableMap
|
||||||
|
onClose={onClose}
|
||||||
|
enableMap={enableMap}
|
||||||
|
onRootClose={handleRootClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</EnteDrawer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModifyMapEnabled;
|
81
apps/photos/src/components/Sidebar/MapSetting/index.tsx
Normal file
81
apps/photos/src/components/Sidebar/MapSetting/index.tsx
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import { Box, DialogProps, Stack } from '@mui/material';
|
||||||
|
import { EnteDrawer } from 'components/EnteDrawer';
|
||||||
|
import Titlebar from 'components/Titlebar';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
||||||
|
import { MenuItemGroup } from 'components/Menu/MenuItemGroup';
|
||||||
|
import ModifyMapEnabled from './ModifyMapEnabled';
|
||||||
|
import { getMapEnabledStatus } from 'services/userService';
|
||||||
|
import { AppContext } from 'pages/_app';
|
||||||
|
|
||||||
|
export default function MapSettings({ open, onClose, onRootClose }) {
|
||||||
|
const { mapEnabled, updateMapEnabled } = useContext(AppContext);
|
||||||
|
const [modifyMapEnabledView, setModifyMapEnabledView] = useState(false);
|
||||||
|
|
||||||
|
const openModifyMapEnabled = () => setModifyMapEnabledView(true);
|
||||||
|
const closeModifyMapEnabled = () => setModifyMapEnabledView(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const main = async () => {
|
||||||
|
const remoteMapValue = await getMapEnabledStatus();
|
||||||
|
updateMapEnabled(remoteMapValue);
|
||||||
|
};
|
||||||
|
main();
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleRootClose = () => {
|
||||||
|
onClose();
|
||||||
|
onRootClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerClose: DialogProps['onClose'] = (_, reason) => {
|
||||||
|
if (reason === 'backdropClick') {
|
||||||
|
handleRootClose();
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EnteDrawer
|
||||||
|
transitionDuration={0}
|
||||||
|
open={open}
|
||||||
|
onClose={handleDrawerClose}
|
||||||
|
BackdropProps={{
|
||||||
|
sx: { '&&&': { backgroundColor: 'transparent' } },
|
||||||
|
}}>
|
||||||
|
<Stack spacing={'4px'} py={'12px'}>
|
||||||
|
<Titlebar
|
||||||
|
onClose={onClose}
|
||||||
|
title={t('MAP')}
|
||||||
|
onRootClose={handleRootClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box px={'8px'}>
|
||||||
|
<Stack py="20px" spacing="24px">
|
||||||
|
<Box>
|
||||||
|
<MenuItemGroup>
|
||||||
|
<EnteMenuItem
|
||||||
|
onClick={openModifyMapEnabled}
|
||||||
|
variant="toggle"
|
||||||
|
checked={mapEnabled}
|
||||||
|
label={t('MAP_SETTINGS')}
|
||||||
|
/>
|
||||||
|
</MenuItemGroup>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<ModifyMapEnabled
|
||||||
|
open={modifyMapEnabledView}
|
||||||
|
mapEnabled={mapEnabled}
|
||||||
|
onClose={closeModifyMapEnabled}
|
||||||
|
onRootClose={handleRootClose}
|
||||||
|
/>
|
||||||
|
</EnteDrawer>
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,15 +7,20 @@ import { useState } from 'react';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
import AdvancedSettings from '../AdvancedSettings';
|
import AdvancedSettings from '../AdvancedSettings';
|
||||||
|
import MapSettings from '../MapSetting';
|
||||||
import { LanguageSelector } from './LanguageSelector';
|
import { LanguageSelector } from './LanguageSelector';
|
||||||
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
||||||
|
|
||||||
export default function Preferences({ open, onClose, onRootClose }) {
|
export default function Preferences({ open, onClose, onRootClose }) {
|
||||||
const [advancedSettingsView, setAdvancedSettingsView] = useState(false);
|
const [advancedSettingsView, setAdvancedSettingsView] = useState(false);
|
||||||
|
const [mapSettingsView, setMapSettingsView] = useState(false);
|
||||||
|
|
||||||
const openAdvancedSettings = () => setAdvancedSettingsView(true);
|
const openAdvancedSettings = () => setAdvancedSettingsView(true);
|
||||||
const closeAdvancedSettings = () => setAdvancedSettingsView(false);
|
const closeAdvancedSettings = () => setAdvancedSettingsView(false);
|
||||||
|
|
||||||
|
const openMapSettings = () => setMapSettingsView(true);
|
||||||
|
const closeMapSettings = () => setMapSettingsView(false);
|
||||||
|
|
||||||
const handleRootClose = () => {
|
const handleRootClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
onRootClose();
|
onRootClose();
|
||||||
|
@ -53,6 +58,11 @@ export default function Preferences({ open, onClose, onRootClose }) {
|
||||||
label={t('ADVANCED')}
|
label={t('ADVANCED')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<EnteMenuItem
|
||||||
|
onClick={openMapSettings}
|
||||||
|
endIcon={<ChevronRight />}
|
||||||
|
label={t('MAP')}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -61,6 +71,11 @@ export default function Preferences({ open, onClose, onRootClose }) {
|
||||||
onClose={closeAdvancedSettings}
|
onClose={closeAdvancedSettings}
|
||||||
onRootClose={onRootClose}
|
onRootClose={onRootClose}
|
||||||
/>
|
/>
|
||||||
|
<MapSettings
|
||||||
|
open={mapSettingsView}
|
||||||
|
onClose={closeMapSettings}
|
||||||
|
onRootClose={onRootClose}
|
||||||
|
/>
|
||||||
</EnteDrawer>
|
</EnteDrawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
76
apps/photos/src/components/pages/gallery/Avatar.tsx
Normal file
76
apps/photos/src/components/pages/gallery/Avatar.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import React, { useState, useContext, useLayoutEffect } from 'react';
|
||||||
|
import { EnteFile } from 'types/file';
|
||||||
|
import { GalleryContext } from 'pages/gallery';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { logError } from 'utils/sentry';
|
||||||
|
|
||||||
|
interface AvatarProps {
|
||||||
|
file: EnteFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PUBLIC_COLLECTED_FILES_AVATAR_COLOR_CODE = '#000000';
|
||||||
|
|
||||||
|
const AvatarBase = styled('div')<{ colorCode: string; size: number }>`
|
||||||
|
width: ${({ size }) => `${size}px`};
|
||||||
|
height: ${({ size }) => `${size}px`};
|
||||||
|
background-color: ${({ colorCode }) => `${colorCode}95`};
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: ${({ size }) => `${Math.floor(size / 2)}px`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Avatar: React.FC<AvatarProps> = ({ file }) => {
|
||||||
|
const { userIDToEmailMap, user } = useContext(GalleryContext);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [colorCode, setColorCode] = useState('');
|
||||||
|
const [userLetter, setUserLetter] = useState('');
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
try {
|
||||||
|
if (file.ownerID !== user.id) {
|
||||||
|
// getting email from in-memory id-email map
|
||||||
|
const email = userIDToEmailMap.get(file.ownerID);
|
||||||
|
if (!email) {
|
||||||
|
logError(Error(), 'email not found in userIDToEmailMap');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const colorIndex =
|
||||||
|
file.ownerID % theme.colors.avatarColors.length;
|
||||||
|
const colorCode = theme.colors.avatarColors[colorIndex];
|
||||||
|
setUserLetter(email[0].toUpperCase());
|
||||||
|
setColorCode(colorCode);
|
||||||
|
} else if (file.ownerID === user.id) {
|
||||||
|
const uploaderName = file.pubMagicMetadata.data.uploaderName;
|
||||||
|
if (!uploaderName) {
|
||||||
|
logError(
|
||||||
|
Error(),
|
||||||
|
'uploaderName not found in file.pubMagicMetadata.data'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUserLetter(uploaderName[0].toUpperCase());
|
||||||
|
setColorCode(PUBLIC_COLLECTED_FILES_AVATAR_COLOR_CODE);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logError(err, 'AvatarIcon.tsx - useLayoutEffect failed');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!colorCode || !userLetter) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AvatarBase size={18} colorCode={colorCode}>
|
||||||
|
{userLetter}
|
||||||
|
</AvatarBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar;
|
|
@ -19,6 +19,9 @@ import {
|
||||||
} from 'components/PlaceholderThumbnails';
|
} from 'components/PlaceholderThumbnails';
|
||||||
import { FILE_TYPE } from 'constants/file';
|
import { FILE_TYPE } from 'constants/file';
|
||||||
import AlbumOutlined from '@mui/icons-material/AlbumOutlined';
|
import AlbumOutlined from '@mui/icons-material/AlbumOutlined';
|
||||||
|
import Avatar from './Avatar';
|
||||||
|
import { User } from 'types/user';
|
||||||
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
file: EnteFile;
|
file: EnteFile;
|
||||||
|
@ -108,6 +111,14 @@ export const HoverOverlay = styled('div')<{ checked: boolean }>`
|
||||||
'background:linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0))'};
|
'background:linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0))'};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const AvatarOverlay = styled(Overlay)`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const InSelectRangeOverLay = styled('div')<{ $active: boolean }>`
|
export const InSelectRangeOverLay = styled('div')<{ $active: boolean }>`
|
||||||
opacity: ${(props) => (!props.$active ? 0 : 1)};
|
opacity: ${(props) => (!props.$active ? 0 : 1)};
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -323,6 +334,8 @@ export default function PreviewCard(props: IProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const user: User = getData(LS_KEYS.USER);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cont
|
<Cont
|
||||||
key={`thumb-${file.id}-${props.showPlaceholder}`}
|
key={`thumb-${file.id}-${props.showPlaceholder}`}
|
||||||
|
@ -358,6 +371,14 @@ export default function PreviewCard(props: IProps) {
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<SelectedOverlay selected={selected} />
|
<SelectedOverlay selected={selected} />
|
||||||
|
{(file.ownerID !== user.id ||
|
||||||
|
(file.ownerID === user.id &&
|
||||||
|
file.pubMagicMetadata?.data?.uploaderName)) && (
|
||||||
|
<AvatarOverlay>
|
||||||
|
<Avatar file={file} />
|
||||||
|
</AvatarOverlay>
|
||||||
|
)}
|
||||||
|
|
||||||
<HoverOverlay checked={selected} />
|
<HoverOverlay checked={selected} />
|
||||||
<InSelectRangeOverLay
|
<InSelectRangeOverLay
|
||||||
$active={isRangeSelectActive && isInsSelectRange}
|
$active={isRangeSelectActive && isInsSelectRange}
|
||||||
|
|
|
@ -91,8 +91,6 @@ export const NULL_EXTRACTED_METADATA: ParsedExtractedMetadata = {
|
||||||
|
|
||||||
export const A_SEC_IN_MICROSECONDS = 1e6;
|
export const A_SEC_IN_MICROSECONDS = 1e6;
|
||||||
|
|
||||||
export const USE_CF_PROXY = false;
|
|
||||||
|
|
||||||
export const DEFAULT_IMPORT_SUGGESTION: ImportSuggestion = {
|
export const DEFAULT_IMPORT_SUGGESTION: ImportSuggestion = {
|
||||||
rootFolderName: '',
|
rootFolderName: '',
|
||||||
hasNestedFolders: false,
|
hasNestedFolders: false,
|
||||||
|
|
|
@ -36,6 +36,7 @@ import {
|
||||||
import {
|
import {
|
||||||
getFamilyPortalRedirectURL,
|
getFamilyPortalRedirectURL,
|
||||||
getRoadmapRedirectURL,
|
getRoadmapRedirectURL,
|
||||||
|
updateMapEnabledStatus,
|
||||||
} from 'services/userService';
|
} from 'services/userService';
|
||||||
import { CustomError } from 'utils/error';
|
import { CustomError } from 'utils/error';
|
||||||
import {
|
import {
|
||||||
|
@ -76,6 +77,7 @@ import {
|
||||||
import exportService from 'services/export';
|
import exportService from 'services/export';
|
||||||
import { ExportStage } from 'constants/export';
|
import { ExportStage } from 'constants/export';
|
||||||
import { REDIRECTS } from 'constants/redirects';
|
import { REDIRECTS } from 'constants/redirects';
|
||||||
|
import { getLocalMapEnabled, setLocalMapEnabled } from 'utils/storage';
|
||||||
|
|
||||||
const redirectMap = new Map([
|
const redirectMap = new Map([
|
||||||
[REDIRECTS.ROADMAP, getRoadmapRedirectURL],
|
[REDIRECTS.ROADMAP, getRoadmapRedirectURL],
|
||||||
|
@ -102,7 +104,9 @@ type AppContextType = {
|
||||||
redirectURL: string;
|
redirectURL: string;
|
||||||
setRedirectURL: (url: string) => void;
|
setRedirectURL: (url: string) => void;
|
||||||
mlSearchEnabled: boolean;
|
mlSearchEnabled: boolean;
|
||||||
|
mapEnabled: boolean;
|
||||||
updateMlSearchEnabled: (enabled: boolean) => Promise<void>;
|
updateMlSearchEnabled: (enabled: boolean) => Promise<void>;
|
||||||
|
updateMapEnabled: (enabled: boolean) => Promise<void>;
|
||||||
startLoading: () => void;
|
startLoading: () => void;
|
||||||
finishLoading: () => void;
|
finishLoading: () => void;
|
||||||
closeMessageDialog: () => void;
|
closeMessageDialog: () => void;
|
||||||
|
@ -147,6 +151,7 @@ export default function App(props) {
|
||||||
const [redirectName, setRedirectName] = useState<string>(null);
|
const [redirectName, setRedirectName] = useState<string>(null);
|
||||||
const [redirectURL, setRedirectURL] = useState(null);
|
const [redirectURL, setRedirectURL] = useState(null);
|
||||||
const [mlSearchEnabled, setMlSearchEnabled] = useState(false);
|
const [mlSearchEnabled, setMlSearchEnabled] = useState(false);
|
||||||
|
const [mapEnabled, setMapEnabled] = useState(false);
|
||||||
const isLoadingBarRunning = useRef(false);
|
const isLoadingBarRunning = useRef(false);
|
||||||
const loadingBar = useRef(null);
|
const loadingBar = useRef(null);
|
||||||
const [dialogMessage, setDialogMessage] = useState<DialogBoxAttributes>();
|
const [dialogMessage, setDialogMessage] = useState<DialogBoxAttributes>();
|
||||||
|
@ -249,6 +254,10 @@ export default function App(props) {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMapEnabled(getLocalMapEnabled());
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron()) {
|
if (!isElectron()) {
|
||||||
return;
|
return;
|
||||||
|
@ -394,6 +403,16 @@ export default function App(props) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateMapEnabled = async (enabled: boolean) => {
|
||||||
|
try {
|
||||||
|
await updateMapEnabledStatus(enabled);
|
||||||
|
setLocalMapEnabled(enabled);
|
||||||
|
setMapEnabled(enabled);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'Error while updating mapEnabled');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const startLoading = () => {
|
const startLoading = () => {
|
||||||
!isLoadingBarRunning.current && loadingBar.current?.continuousStart();
|
!isLoadingBarRunning.current && loadingBar.current?.continuousStart();
|
||||||
isLoadingBarRunning.current = true;
|
isLoadingBarRunning.current = true;
|
||||||
|
@ -493,6 +512,8 @@ export default function App(props) {
|
||||||
setThemeColor,
|
setThemeColor,
|
||||||
somethingWentWrong,
|
somethingWentWrong,
|
||||||
setDialogBoxAttributesV2,
|
setDialogBoxAttributesV2,
|
||||||
|
mapEnabled,
|
||||||
|
updateMapEnabled,
|
||||||
}}>
|
}}>
|
||||||
{(loading || !isI18nReady) && (
|
{(loading || !isI18nReady) && (
|
||||||
<Overlay
|
<Overlay
|
||||||
|
|
|
@ -38,7 +38,11 @@ import {
|
||||||
setIsFirstLogin,
|
setIsFirstLogin,
|
||||||
setJustSignedUp,
|
setJustSignedUp,
|
||||||
} from 'utils/storage';
|
} from 'utils/storage';
|
||||||
import { isTokenValid, validateKey } from 'services/userService';
|
import {
|
||||||
|
isTokenValid,
|
||||||
|
syncMapEnabled,
|
||||||
|
validateKey,
|
||||||
|
} from 'services/userService';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import EnteSpinner from 'components/EnteSpinner';
|
import EnteSpinner from 'components/EnteSpinner';
|
||||||
import { LoadingOverlay } from 'components/LoadingOverlay';
|
import { LoadingOverlay } from 'components/LoadingOverlay';
|
||||||
|
@ -120,6 +124,7 @@ import { IsArchived } from 'utils/magicMetadata';
|
||||||
import { isSameDayAnyYear, isInsideLocationTag } from 'utils/search';
|
import { isSameDayAnyYear, isInsideLocationTag } from 'utils/search';
|
||||||
import { getSessionExpiredMessage } from 'utils/ui';
|
import { getSessionExpiredMessage } from 'utils/ui';
|
||||||
import { syncEntities } from 'services/entityService';
|
import { syncEntities } from 'services/entityService';
|
||||||
|
import { constructUserIDToEmailMap } from 'services/collectionService';
|
||||||
|
|
||||||
export const DeadCenter = styled('div')`
|
export const DeadCenter = styled('div')`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -137,10 +142,12 @@ const defaultGalleryContext: GalleryContextType = {
|
||||||
setActiveCollection: () => null,
|
setActiveCollection: () => null,
|
||||||
syncWithRemote: () => null,
|
syncWithRemote: () => null,
|
||||||
setBlockingLoad: () => null,
|
setBlockingLoad: () => null,
|
||||||
|
setIsInSearchMode: () => null,
|
||||||
photoListHeader: null,
|
photoListHeader: null,
|
||||||
openExportModal: () => null,
|
openExportModal: () => null,
|
||||||
authenticateUser: () => null,
|
authenticateUser: () => null,
|
||||||
user: null,
|
user: null,
|
||||||
|
userIDToEmailMap: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GalleryContext = createContext<GalleryContextType>(
|
export const GalleryContext = createContext<GalleryContextType>(
|
||||||
|
@ -216,6 +223,8 @@ export default function Gallery() {
|
||||||
useContext(AppContext);
|
useContext(AppContext);
|
||||||
const [collectionSummaries, setCollectionSummaries] =
|
const [collectionSummaries, setCollectionSummaries] =
|
||||||
useState<CollectionSummaries>();
|
useState<CollectionSummaries>();
|
||||||
|
const [userIDToEmailMap, setUserIDToEmailMap] =
|
||||||
|
useState<Map<number, string>>(null);
|
||||||
const [activeCollection, setActiveCollection] = useState<number>(undefined);
|
const [activeCollection, setActiveCollection] = useState<number>(undefined);
|
||||||
const [fixCreationTimeView, setFixCreationTimeView] = useState(false);
|
const [fixCreationTimeView, setFixCreationTimeView] = useState(false);
|
||||||
const [fixCreationTimeAttributes, setFixCreationTimeAttributes] =
|
const [fixCreationTimeAttributes, setFixCreationTimeAttributes] =
|
||||||
|
@ -312,6 +321,17 @@ export default function Gallery() {
|
||||||
setDerivativeState(user, collections, files, trashedFiles, hiddenFiles);
|
setDerivativeState(user, collections, files, trashedFiles, hiddenFiles);
|
||||||
}, [collections, files, hiddenFiles, trashedFiles, user]);
|
}, [collections, files, hiddenFiles, trashedFiles, user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!collections) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userIdToEmailMap = await constructUserIDToEmailMap();
|
||||||
|
setUserIDToEmailMap(userIdToEmailMap);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, [collections]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
collectionSelectorAttributes && setCollectionSelectorView(true);
|
collectionSelectorAttributes && setCollectionSelectorView(true);
|
||||||
}, [collectionSelectorAttributes]);
|
}, [collectionSelectorAttributes]);
|
||||||
|
@ -564,6 +584,7 @@ export default function Gallery() {
|
||||||
await syncHiddenFiles(hiddenCollections, setHiddenFiles);
|
await syncHiddenFiles(hiddenCollections, setHiddenFiles);
|
||||||
await syncTrash(collections, setTrashedFiles);
|
await syncTrash(collections, setTrashedFiles);
|
||||||
await syncEntities();
|
await syncEntities();
|
||||||
|
await syncMapEnabled();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
switch (e.message) {
|
switch (e.message) {
|
||||||
case ServerErrorCodes.SESSION_EXPIRED:
|
case ServerErrorCodes.SESSION_EXPIRED:
|
||||||
|
@ -602,7 +623,6 @@ export default function Gallery() {
|
||||||
setFavItemIds(favItemIds);
|
setFavItemIds(favItemIds);
|
||||||
const archivedCollections = getArchivedCollections(collections);
|
const archivedCollections = getArchivedCollections(collections);
|
||||||
setArchivedCollections(archivedCollections);
|
setArchivedCollections(archivedCollections);
|
||||||
|
|
||||||
const collectionSummaries = await getCollectionSummaries(
|
const collectionSummaries = await getCollectionSummaries(
|
||||||
user,
|
user,
|
||||||
collections,
|
collections,
|
||||||
|
@ -849,9 +869,11 @@ export default function Gallery() {
|
||||||
setActiveCollection,
|
setActiveCollection,
|
||||||
syncWithRemote,
|
syncWithRemote,
|
||||||
setBlockingLoad,
|
setBlockingLoad,
|
||||||
|
setIsInSearchMode,
|
||||||
photoListHeader,
|
photoListHeader,
|
||||||
openExportModal,
|
openExportModal,
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
|
userIDToEmailMap,
|
||||||
user,
|
user,
|
||||||
}}>
|
}}>
|
||||||
<FullScreenDropZone
|
<FullScreenDropZone
|
||||||
|
|
|
@ -1260,3 +1260,30 @@ export async function unhideToCollection(
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const constructUserIDToEmailMap = async (): Promise<
|
||||||
|
Map<number, string>
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const collection = await getLocalCollections();
|
||||||
|
const user: User = getData(LS_KEYS.USER);
|
||||||
|
const userIDToEmailMap = new Map<number, string>();
|
||||||
|
collection.map((item) => {
|
||||||
|
const { owner, sharees } = item;
|
||||||
|
if (user.id !== owner.id && owner.email) {
|
||||||
|
userIDToEmailMap.set(owner.id, owner.email);
|
||||||
|
}
|
||||||
|
if (sharees) {
|
||||||
|
sharees.map((item) => {
|
||||||
|
if (item.id !== user.id)
|
||||||
|
userIDToEmailMap.set(item.id, item.email);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return userIDToEmailMap;
|
||||||
|
} catch (e) {
|
||||||
|
logError('Error Mapping UserId to email:', e);
|
||||||
|
return new Map<number, string>();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { getFileType } from 'services/typeDetectionService';
|
||||||
import { getLocalTrashedFiles } from './trashService';
|
import { getLocalTrashedFiles } from './trashService';
|
||||||
import { UploadURL } from 'types/upload';
|
import { UploadURL } from 'types/upload';
|
||||||
import { S3FileAttributes } from 'types/file';
|
import { S3FileAttributes } from 'types/file';
|
||||||
import { USE_CF_PROXY } from 'constants/upload';
|
|
||||||
import { Remote } from 'comlink';
|
import { Remote } from 'comlink';
|
||||||
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
|
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
|
||||||
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
|
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
|
||||||
|
@ -112,20 +111,12 @@ export async function uploadThumbnail(
|
||||||
updatedThumbnail,
|
updatedThumbnail,
|
||||||
fileKey
|
fileKey
|
||||||
);
|
);
|
||||||
let thumbnailObjectKey: string = null;
|
const thumbnailObjectKey = await uploadHttpClient.putFile(
|
||||||
if (USE_CF_PROXY) {
|
uploadURL,
|
||||||
thumbnailObjectKey = await uploadHttpClient.putFileV2(
|
encryptedThumbnail.encryptedData,
|
||||||
uploadURL,
|
() => {}
|
||||||
encryptedThumbnail.encryptedData,
|
);
|
||||||
() => {}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
thumbnailObjectKey = await uploadHttpClient.putFile(
|
|
||||||
uploadURL,
|
|
||||||
encryptedThumbnail.encryptedData,
|
|
||||||
() => {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
objectKey: thumbnailObjectKey,
|
objectKey: thumbnailObjectKey,
|
||||||
decryptionHeader: encryptedThumbnail.decryptionHeader,
|
decryptionHeader: encryptedThumbnail.decryptionHeader,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART,
|
FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART,
|
||||||
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
|
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
|
||||||
USE_CF_PROXY,
|
|
||||||
} from 'constants/upload';
|
} from 'constants/upload';
|
||||||
import UIService from './uiService';
|
import UIService from './uiService';
|
||||||
import UploadHttpClient from './uploadHttpClient';
|
import UploadHttpClient from './uploadHttpClient';
|
||||||
|
@ -63,7 +62,7 @@ export async function uploadStreamInParts(
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
let eTag = null;
|
let eTag = null;
|
||||||
if (USE_CF_PROXY) {
|
if (!uploadService.getIsCFUploadProxyDisabled()) {
|
||||||
eTag = await UploadHttpClient.putFilePartV2(
|
eTag = await UploadHttpClient.putFilePartV2(
|
||||||
fileUploadURL,
|
fileUploadURL,
|
||||||
uploadChunk,
|
uploadChunk,
|
||||||
|
@ -117,7 +116,7 @@ async function completeMultipartUpload(
|
||||||
{ CompleteMultipartUpload: { Part: partEtags } },
|
{ CompleteMultipartUpload: { Part: partEtags } },
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
if (USE_CF_PROXY) {
|
if (!uploadService.getIsCFUploadProxyDisabled()) {
|
||||||
await UploadHttpClient.completeMultipartUploadV2(completeURL, body);
|
await UploadHttpClient.completeMultipartUploadV2(completeURL, body);
|
||||||
} else {
|
} else {
|
||||||
await UploadHttpClient.completeMultipartUpload(completeURL, body);
|
await UploadHttpClient.completeMultipartUpload(completeURL, body);
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {
|
||||||
getPublicCollectionUID,
|
getPublicCollectionUID,
|
||||||
} from 'services/publicCollectionService';
|
} from 'services/publicCollectionService';
|
||||||
import { getDedicatedCryptoWorker } from 'utils/comlink/ComlinkCryptoWorker';
|
import { getDedicatedCryptoWorker } from 'utils/comlink/ComlinkCryptoWorker';
|
||||||
|
import { getDisableCFUploadProxyFlag } from 'services/userService';
|
||||||
|
|
||||||
const MAX_CONCURRENT_UPLOADS = 4;
|
const MAX_CONCURRENT_UPLOADS = 4;
|
||||||
|
|
||||||
|
@ -61,7 +62,8 @@ class UploadManager {
|
||||||
publicCollectProps: PublicUploadProps
|
publicCollectProps: PublicUploadProps
|
||||||
) {
|
) {
|
||||||
UIService.init(progressUpdater);
|
UIService.init(progressUpdater);
|
||||||
UploadService.init(publicCollectProps);
|
const isCFUploadProxyDisabled = await getDisableCFUploadProxyFlag();
|
||||||
|
UploadService.init(publicCollectProps, isCFUploadProxyDisabled);
|
||||||
this.setFiles = setFiles;
|
this.setFiles = setFiles;
|
||||||
this.publicUploadProps = publicCollectProps;
|
this.publicUploadProps = publicCollectProps;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import {
|
||||||
import { encryptFile, getFileSize, readFile } from './fileService';
|
import { encryptFile, getFileSize, readFile } from './fileService';
|
||||||
import { uploadStreamUsingMultipart } from './multiPartUploadService';
|
import { uploadStreamUsingMultipart } from './multiPartUploadService';
|
||||||
import UIService from './uiService';
|
import UIService from './uiService';
|
||||||
import { USE_CF_PROXY } from 'constants/upload';
|
|
||||||
import { Remote } from 'comlink';
|
import { Remote } from 'comlink';
|
||||||
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
|
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
|
||||||
import publicUploadHttpClient from './publicUploadHttpClient';
|
import publicUploadHttpClient from './publicUploadHttpClient';
|
||||||
|
@ -52,8 +51,14 @@ class UploadService {
|
||||||
|
|
||||||
private publicUploadProps: PublicUploadProps = undefined;
|
private publicUploadProps: PublicUploadProps = undefined;
|
||||||
|
|
||||||
init(publicUploadProps: PublicUploadProps) {
|
private isCFUploadProxyDisabled: boolean = false;
|
||||||
|
|
||||||
|
init(
|
||||||
|
publicUploadProps: PublicUploadProps,
|
||||||
|
isCFUploadProxyDisabled: boolean
|
||||||
|
) {
|
||||||
this.publicUploadProps = publicUploadProps;
|
this.publicUploadProps = publicUploadProps;
|
||||||
|
this.isCFUploadProxyDisabled = isCFUploadProxyDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setFileCount(fileCount: number) {
|
async setFileCount(fileCount: number) {
|
||||||
|
@ -73,6 +78,10 @@ class UploadService {
|
||||||
return this.uploaderName;
|
return this.uploaderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIsCFUploadProxyDisabled() {
|
||||||
|
return this.isCFUploadProxyDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
reducePendingUploadCount() {
|
reducePendingUploadCount() {
|
||||||
this.pendingUploadCount--;
|
this.pendingUploadCount--;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +167,7 @@ class UploadService {
|
||||||
file.localID
|
file.localID
|
||||||
);
|
);
|
||||||
const fileUploadURL = await this.getUploadURL();
|
const fileUploadURL = await this.getUploadURL();
|
||||||
if (USE_CF_PROXY) {
|
if (!this.isCFUploadProxyDisabled) {
|
||||||
fileObjectKey = await UploadHttpClient.putFileV2(
|
fileObjectKey = await UploadHttpClient.putFileV2(
|
||||||
fileUploadURL,
|
fileUploadURL,
|
||||||
file.file.encryptedData as Uint8Array,
|
file.file.encryptedData as Uint8Array,
|
||||||
|
@ -174,7 +183,7 @@ class UploadService {
|
||||||
}
|
}
|
||||||
const thumbnailUploadURL = await this.getUploadURL();
|
const thumbnailUploadURL = await this.getUploadURL();
|
||||||
let thumbnailObjectKey: string = null;
|
let thumbnailObjectKey: string = null;
|
||||||
if (USE_CF_PROXY) {
|
if (!this.isCFUploadProxyDisabled) {
|
||||||
thumbnailObjectKey = await UploadHttpClient.putFileV2(
|
thumbnailObjectKey = await UploadHttpClient.putFileV2(
|
||||||
thumbnailUploadURL,
|
thumbnailUploadURL,
|
||||||
file.thumbnail.encryptedData,
|
file.thumbnail.encryptedData,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
UserDetails,
|
UserDetails,
|
||||||
DeleteChallengeResponse,
|
DeleteChallengeResponse,
|
||||||
GetRemoteStoreValueResponse,
|
GetRemoteStoreValueResponse,
|
||||||
|
GetFeatureFlagResponse,
|
||||||
} from 'types/user';
|
} from 'types/user';
|
||||||
import { ServerErrorCodes } from 'utils/error';
|
import { ServerErrorCodes } from 'utils/error';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
|
@ -28,6 +29,7 @@ import { B64EncryptionResult } from 'types/crypto';
|
||||||
import { getLocalFamilyData, isPartOfFamily } from 'utils/user/family';
|
import { getLocalFamilyData, isPartOfFamily } from 'utils/user/family';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { APPS, getAppName } from 'constants/apps';
|
import { APPS, getAppName } from 'constants/apps';
|
||||||
|
import { setLocalMapEnabled } from 'utils/storage';
|
||||||
|
|
||||||
const ENDPOINT = getEndpoint();
|
const ENDPOINT = getEndpoint();
|
||||||
|
|
||||||
|
@ -452,3 +454,66 @@ export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const syncMapEnabled = async () => {
|
||||||
|
try {
|
||||||
|
const status = await getMapEnabledStatus();
|
||||||
|
setLocalMapEnabled(status);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'failed to sync map enabled status');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMapEnabledStatus = async () => {
|
||||||
|
try {
|
||||||
|
const token = getToken();
|
||||||
|
const resp: AxiosResponse<GetRemoteStoreValueResponse> =
|
||||||
|
await HTTPService.get(
|
||||||
|
`${ENDPOINT}/remote-store`,
|
||||||
|
{
|
||||||
|
key: 'mapEnabled',
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return resp.data.value === 'true';
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'failed to get map enabled status');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateMapEnabledStatus = async (newStatus: boolean) => {
|
||||||
|
try {
|
||||||
|
const token = getToken();
|
||||||
|
await HTTPService.post(
|
||||||
|
`${ENDPOINT}/remote-store/update`,
|
||||||
|
{
|
||||||
|
key: 'mapEnabled',
|
||||||
|
value: newStatus.toString(),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'failed to update map enabled status');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getDisableCFUploadProxyFlag(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const featureFlags = (
|
||||||
|
await fetch('https://static.ente.io/feature_flags.json')
|
||||||
|
).json() as GetFeatureFlagResponse;
|
||||||
|
return featureFlags.disableCFUploadProxy;
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'failed to get feature flags');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -63,6 +63,31 @@ const darkThemeColors: Omit<ThemeColorsOptions, keyof FixedColors> = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
avatarColors: [
|
||||||
|
'#76549A',
|
||||||
|
'#DF7861',
|
||||||
|
'#94B49F',
|
||||||
|
'#87A2FB',
|
||||||
|
'#C689C6',
|
||||||
|
'#937DC2',
|
||||||
|
'#325288',
|
||||||
|
'#85B4E0',
|
||||||
|
'#C1A3A3',
|
||||||
|
'#E1A059',
|
||||||
|
'#426165',
|
||||||
|
'#6B77B2',
|
||||||
|
'#957FEF',
|
||||||
|
'#DD9DE2',
|
||||||
|
'#82AB8B',
|
||||||
|
'#9BBBE8',
|
||||||
|
'#8FBEBE',
|
||||||
|
'#8AC3A1',
|
||||||
|
'#A8B0F2',
|
||||||
|
'#B0C695',
|
||||||
|
'#E99AAD',
|
||||||
|
'#D18484',
|
||||||
|
'#78B5A7',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default darkThemeColors;
|
export default darkThemeColors;
|
||||||
|
|
|
@ -56,6 +56,31 @@ const lightThemeColors: Omit<ThemeColorsOptions, keyof FixedColors> = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
avatarColors: [
|
||||||
|
'#76549A',
|
||||||
|
'#DF7861',
|
||||||
|
'#94B49F',
|
||||||
|
'#87A2FB',
|
||||||
|
'#C689C6',
|
||||||
|
'#937DC2',
|
||||||
|
'#325288',
|
||||||
|
'#85B4E0',
|
||||||
|
'#C1A3A3',
|
||||||
|
'#E1A059',
|
||||||
|
'#426165',
|
||||||
|
'#6B77B2',
|
||||||
|
'#957FEF',
|
||||||
|
'#DD9DE2',
|
||||||
|
'#82AB8B',
|
||||||
|
'#9BBBE8',
|
||||||
|
'#8FBEBE',
|
||||||
|
'#8AC3A1',
|
||||||
|
'#A8B0F2',
|
||||||
|
'#B0C695',
|
||||||
|
'#E99AAD',
|
||||||
|
'#D18484',
|
||||||
|
'#78B5A7',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default lightThemeColors;
|
export default lightThemeColors;
|
||||||
|
|
|
@ -111,6 +111,7 @@ declare module '@mui/material/styles' {
|
||||||
blur: BlurStrength;
|
blur: BlurStrength;
|
||||||
white: Omit<Strength, 'faint'>;
|
white: Omit<Strength, 'faint'>;
|
||||||
black: Omit<Strength, 'faint'>;
|
black: Omit<Strength, 'faint'>;
|
||||||
|
avatarColors: AvatarColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThemeColorsOptions {
|
interface ThemeColorsOptions {
|
||||||
|
@ -126,6 +127,7 @@ declare module '@mui/material/styles' {
|
||||||
blur?: Partial<BlurStrength>;
|
blur?: Partial<BlurStrength>;
|
||||||
white?: Partial<Omit<Strength, 'faint'>>;
|
white?: Partial<Omit<Strength, 'faint'>>;
|
||||||
black?: Partial<Omit<Strength, 'faint'>>;
|
black?: Partial<Omit<Strength, 'faint'>>;
|
||||||
|
avatarColors?: Partial<AvatarColors>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColorStrength {
|
interface ColorStrength {
|
||||||
|
@ -192,5 +194,7 @@ declare module '@mui/material/styles' {
|
||||||
muted: number;
|
muted: number;
|
||||||
faint: number;
|
faint: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AvatarColors = Array<string>;
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -33,10 +33,12 @@ export type GalleryContextType = {
|
||||||
setActiveCollection: (collection: number) => void;
|
setActiveCollection: (collection: number) => void;
|
||||||
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
|
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
|
||||||
setBlockingLoad: (value: boolean) => void;
|
setBlockingLoad: (value: boolean) => void;
|
||||||
|
setIsInSearchMode: (value: boolean) => void;
|
||||||
photoListHeader: TimeStampListItem;
|
photoListHeader: TimeStampListItem;
|
||||||
openExportModal: () => void;
|
openExportModal: () => void;
|
||||||
authenticateUser: (callback: () => void) => void;
|
authenticateUser: (callback: () => void) => void;
|
||||||
user: User;
|
user: User;
|
||||||
|
userIDToEmailMap: Map<number, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum CollectionSelectorIntent {
|
export enum CollectionSelectorIntent {
|
||||||
|
|
|
@ -102,3 +102,7 @@ export interface UpdateRemoteStoreValueRequest {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetFeatureFlagResponse {
|
||||||
|
disableCFUploadProxy?: boolean;
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const getPublicCollectionThumbnailURL = (id: number) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUploadEndpoint = () => {
|
export const getUploadEndpoint = () => {
|
||||||
const endpoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT;
|
const endpoint = process.env.NEXT_PUBLIC_ENTE_UPLOAD_ENDPOINT;
|
||||||
if (isDevDeployment() && endpoint) {
|
if (isDevDeployment() && endpoint) {
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,11 @@ export function setLivePhotoInfoShownCount(count) {
|
||||||
export function getUserLocale(): Language {
|
export function getUserLocale(): Language {
|
||||||
return getData(LS_KEYS.LOCALE)?.value;
|
return getData(LS_KEYS.LOCALE)?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLocalMapEnabled(): boolean {
|
||||||
|
return getData(LS_KEYS.MAP_ENABLED)?.value ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocalMapEnabled(value: boolean) {
|
||||||
|
setData(LS_KEYS.MAP_ENABLED, { value });
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ export enum LS_KEYS {
|
||||||
WAIT_TIME = 'waitTime',
|
WAIT_TIME = 'waitTime',
|
||||||
API_ENDPOINT = 'apiEndpoint',
|
API_ENDPOINT = 'apiEndpoint',
|
||||||
LOCALE = 'locale',
|
LOCALE = 'locale',
|
||||||
|
MAP_ENABLED = 'mapEnabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setData = (key: LS_KEYS, value: object) => {
|
export const setData = (key: LS_KEYS, value: object) => {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import InfoOutlined from '@mui/icons-material/InfoRounded';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import { Subscription } from 'types/billing';
|
import { Subscription } from 'types/billing';
|
||||||
import { logoutUser } from 'services/userService';
|
import { logoutUser } from 'services/userService';
|
||||||
|
import { Link } from '@mui/material';
|
||||||
|
import { OPEN_STREET_MAP_LINK } from 'components/Sidebar/EnableMap';
|
||||||
export const getDownloadAppMessage = (): DialogBoxAttributes => {
|
export const getDownloadAppMessage = (): DialogBoxAttributes => {
|
||||||
return {
|
return {
|
||||||
title: t('DOWNLOAD_APP'),
|
title: t('DOWNLOAD_APP'),
|
||||||
|
@ -129,3 +131,36 @@ export const getSessionExpiredMessage = (): DialogBoxAttributes => ({
|
||||||
variant: 'accent',
|
variant: 'accent',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getMapEnableConfirmationDialog = (
|
||||||
|
enableMapHelper
|
||||||
|
): DialogBoxAttributes => ({
|
||||||
|
title: t('ENABLE_MAPS'),
|
||||||
|
content: (
|
||||||
|
<Trans
|
||||||
|
i18nKey={'ENABLE_MAP_DESCRIPTION'}
|
||||||
|
components={{
|
||||||
|
a: <Link target="_blank" href={OPEN_STREET_MAP_LINK} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
proceed: {
|
||||||
|
action: enableMapHelper,
|
||||||
|
text: t('ENABLE'),
|
||||||
|
variant: 'accent',
|
||||||
|
},
|
||||||
|
close: { text: t('CANCEL') },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getMapDisableConfirmationDialog = (
|
||||||
|
disableMapHelper
|
||||||
|
): DialogBoxAttributes => ({
|
||||||
|
title: t('DISABLE_MAPS'),
|
||||||
|
content: <Trans i18nKey={'DISABLE_MAP_DESCRIPTION'} />,
|
||||||
|
proceed: {
|
||||||
|
action: disableMapHelper,
|
||||||
|
text: t('DISABLE'),
|
||||||
|
variant: 'accent',
|
||||||
|
},
|
||||||
|
close: { text: t('CANCEL') },
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue