Pre release (#1213)
This commit is contained in:
commit
c3724b8bbe
|
@ -2,11 +2,6 @@ const cp = require('child_process');
|
|||
const { getIsSentryEnabled } = require('./sentryConfigUtil');
|
||||
|
||||
module.exports = {
|
||||
COOP_COEP_HEADERS: {
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
},
|
||||
|
||||
WEB_SECURITY_HEADERS: {
|
||||
'Strict-Transport-Security': ' max-age=63072000',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
|
@ -20,7 +15,7 @@ module.exports = {
|
|||
// self is safe enough
|
||||
'default-src': "'self'",
|
||||
// data to allow two factor qr code
|
||||
'img-src': "'self' blob: data:",
|
||||
'img-src': "'self' blob: data: https://*.openstreetmap.org",
|
||||
'media-src': "'self' blob:",
|
||||
'manifest-src': "'self'",
|
||||
'style-src': "'self' 'unsafe-inline'",
|
||||
|
|
|
@ -53,6 +53,8 @@
|
|||
"idb": "^7.0.0",
|
||||
"is-electron": "^2.2.0",
|
||||
"jszip": "3.8.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||
"libsodium-wrappers": "^0.7.8",
|
||||
"localforage": "^1.9.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
|
@ -93,6 +95,7 @@
|
|||
"@next/bundle-analyzer": "^13.1.6",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/debounce-promise": "^3.1.3",
|
||||
"@types/leaflet": "^1.9.3",
|
||||
"@types/libsodium-wrappers": "^0.7.8",
|
||||
"@types/node": "^14.6.4",
|
||||
"@types/photoswipe": "^4.1.1",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,14 @@
|
|||
"CAPTION_PLACEHOLDER": "Add a description",
|
||||
"LOCATION": "Location",
|
||||
"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",
|
||||
"VIEW_EXIF": "View all EXIF data",
|
||||
"NO_EXIF": "No EXIF data",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "Añadir una descripción",
|
||||
"LOCATION": "Localización",
|
||||
"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",
|
||||
"VIEW_EXIF": "Ver todos los datos de EXIF",
|
||||
"NO_EXIF": "No hay datos EXIF",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "Ajouter une description",
|
||||
"LOCATION": "Emplacement",
|
||||
"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",
|
||||
"VIEW_EXIF": "Visualiser toutes les données EXIF",
|
||||
"NO_EXIF": "Aucune donnée EXIF",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "Voeg een beschrijving toe",
|
||||
"LOCATION": "Locatie",
|
||||
"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",
|
||||
"VIEW_EXIF": "Bekijk alle EXIF gegevens",
|
||||
"NO_EXIF": "Geen EXIF gegevens",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"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": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
|
|
|
@ -232,6 +232,15 @@
|
|||
"CAPTION_PLACEHOLDER": "添加说明",
|
||||
"LOCATION": "地理位置",
|
||||
"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": "详情",
|
||||
"VIEW_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 { formatDate, formatTime } from 'utils/time/format';
|
||||
import Titlebar from 'components/Titlebar';
|
||||
import MapBox from './MapBox';
|
||||
import InfoItem from './InfoItem';
|
||||
import { FlexWrapper } from 'components/Container';
|
||||
import EnteSpinner from 'components/EnteSpinner';
|
||||
|
@ -32,6 +33,10 @@ import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
|
|||
import { AppContext } from 'pages/_app';
|
||||
import { t } from 'i18next';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import {
|
||||
getMapDisableConfirmationDialog,
|
||||
getMapEnableConfirmationDialog,
|
||||
} from 'utils/ui';
|
||||
|
||||
export const FileInfoSidebar = styled((props: DialogProps) => (
|
||||
<EnteDrawer {...props} anchor="right" />
|
||||
|
@ -170,9 +175,24 @@ export function FileInfo({
|
|||
}
|
||||
const onCollectionChipClick = (collectionID) => {
|
||||
galleryContext.setActiveCollection(collectionID);
|
||||
galleryContext.setIsInSearchMode(false);
|
||||
closePhotoViewer();
|
||||
};
|
||||
|
||||
const openEnableMapConfirmationDialog = () =>
|
||||
appContext.setDialogBoxAttributesV2(
|
||||
getMapEnableConfirmationDialog(() =>
|
||||
appContext.updateMapEnabled(true)
|
||||
)
|
||||
);
|
||||
|
||||
const openDisableMapConfirmationDialog = () =>
|
||||
appContext.setDialogBoxAttributesV2(
|
||||
getMapDisableConfirmationDialog(() =>
|
||||
appContext.updateMapEnabled(false)
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
|
||||
<Titlebar onClose={handleCloseInfo} title={t('INFO')} backIsClose />
|
||||
|
@ -210,25 +230,48 @@ export function FileInfo({
|
|||
)}
|
||||
|
||||
{location && (
|
||||
<InfoItem
|
||||
icon={<LocationOnOutlined />}
|
||||
title={t('LOCATION')}
|
||||
caption={
|
||||
<Link
|
||||
href={getOpenStreetMapLink(location)}
|
||||
target="_blank"
|
||||
sx={{ fontWeight: 'bold' }}>
|
||||
{t('SHOW_ON_MAP')}
|
||||
</Link>
|
||||
}
|
||||
customEndButton={
|
||||
<CopyButton
|
||||
code={getOpenStreetMapLink(location)}
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<InfoItem
|
||||
icon={<LocationOnOutlined />}
|
||||
title={t('LOCATION')}
|
||||
caption={
|
||||
!appContext.mapEnabled ? (
|
||||
<Link
|
||||
href={getOpenStreetMapLink(location)}
|
||||
target="_blank"
|
||||
sx={{ fontWeight: 'bold' }}>
|
||||
{t('SHOW_ON_MAP')}
|
||||
</Link>
|
||||
) : (
|
||||
<LinkButton
|
||||
onClick={
|
||||
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
|
||||
icon={<TextSnippetOutlined />}
|
||||
|
|
|
@ -7,6 +7,7 @@ import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
|
|||
import Titlebar from 'components/Titlebar';
|
||||
import { useState } from 'react';
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
||||
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 AdvancedSettings from '../AdvancedSettings';
|
||||
import MapSettings from '../MapSetting';
|
||||
import { LanguageSelector } from './LanguageSelector';
|
||||
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
|
||||
|
||||
export default function Preferences({ open, onClose, onRootClose }) {
|
||||
const [advancedSettingsView, setAdvancedSettingsView] = useState(false);
|
||||
const [mapSettingsView, setMapSettingsView] = useState(false);
|
||||
|
||||
const openAdvancedSettings = () => setAdvancedSettingsView(true);
|
||||
const closeAdvancedSettings = () => setAdvancedSettingsView(false);
|
||||
|
||||
const openMapSettings = () => setMapSettingsView(true);
|
||||
const closeMapSettings = () => setMapSettingsView(false);
|
||||
|
||||
const handleRootClose = () => {
|
||||
onClose();
|
||||
onRootClose();
|
||||
|
@ -53,6 +58,11 @@ export default function Preferences({ open, onClose, onRootClose }) {
|
|||
label={t('ADVANCED')}
|
||||
/>
|
||||
)}
|
||||
<EnteMenuItem
|
||||
onClick={openMapSettings}
|
||||
endIcon={<ChevronRight />}
|
||||
label={t('MAP')}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
@ -61,6 +71,11 @@ export default function Preferences({ open, onClose, onRootClose }) {
|
|||
onClose={closeAdvancedSettings}
|
||||
onRootClose={onRootClose}
|
||||
/>
|
||||
<MapSettings
|
||||
open={mapSettingsView}
|
||||
onClose={closeMapSettings}
|
||||
onRootClose={onRootClose}
|
||||
/>
|
||||
</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';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
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 {
|
||||
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))'};
|
||||
`;
|
||||
|
||||
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 }>`
|
||||
opacity: ${(props) => (!props.$active ? 0 : 1)};
|
||||
left: 0;
|
||||
|
@ -323,6 +334,8 @@ export default function PreviewCard(props: IProps) {
|
|||
}
|
||||
};
|
||||
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
|
||||
return (
|
||||
<Cont
|
||||
key={`thumb-${file.id}-${props.showPlaceholder}`}
|
||||
|
@ -358,6 +371,14 @@ export default function PreviewCard(props: IProps) {
|
|||
)
|
||||
)}
|
||||
<SelectedOverlay selected={selected} />
|
||||
{(file.ownerID !== user.id ||
|
||||
(file.ownerID === user.id &&
|
||||
file.pubMagicMetadata?.data?.uploaderName)) && (
|
||||
<AvatarOverlay>
|
||||
<Avatar file={file} />
|
||||
</AvatarOverlay>
|
||||
)}
|
||||
|
||||
<HoverOverlay checked={selected} />
|
||||
<InSelectRangeOverLay
|
||||
$active={isRangeSelectActive && isInsSelectRange}
|
||||
|
|
|
@ -91,8 +91,6 @@ export const NULL_EXTRACTED_METADATA: ParsedExtractedMetadata = {
|
|||
|
||||
export const A_SEC_IN_MICROSECONDS = 1e6;
|
||||
|
||||
export const USE_CF_PROXY = false;
|
||||
|
||||
export const DEFAULT_IMPORT_SUGGESTION: ImportSuggestion = {
|
||||
rootFolderName: '',
|
||||
hasNestedFolders: false,
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
import {
|
||||
getFamilyPortalRedirectURL,
|
||||
getRoadmapRedirectURL,
|
||||
updateMapEnabledStatus,
|
||||
} from 'services/userService';
|
||||
import { CustomError } from 'utils/error';
|
||||
import {
|
||||
|
@ -76,6 +77,7 @@ import {
|
|||
import exportService from 'services/export';
|
||||
import { ExportStage } from 'constants/export';
|
||||
import { REDIRECTS } from 'constants/redirects';
|
||||
import { getLocalMapEnabled, setLocalMapEnabled } from 'utils/storage';
|
||||
|
||||
const redirectMap = new Map([
|
||||
[REDIRECTS.ROADMAP, getRoadmapRedirectURL],
|
||||
|
@ -102,7 +104,9 @@ type AppContextType = {
|
|||
redirectURL: string;
|
||||
setRedirectURL: (url: string) => void;
|
||||
mlSearchEnabled: boolean;
|
||||
mapEnabled: boolean;
|
||||
updateMlSearchEnabled: (enabled: boolean) => Promise<void>;
|
||||
updateMapEnabled: (enabled: boolean) => Promise<void>;
|
||||
startLoading: () => void;
|
||||
finishLoading: () => void;
|
||||
closeMessageDialog: () => void;
|
||||
|
@ -147,6 +151,7 @@ export default function App(props) {
|
|||
const [redirectName, setRedirectName] = useState<string>(null);
|
||||
const [redirectURL, setRedirectURL] = useState(null);
|
||||
const [mlSearchEnabled, setMlSearchEnabled] = useState(false);
|
||||
const [mapEnabled, setMapEnabled] = useState(false);
|
||||
const isLoadingBarRunning = useRef(false);
|
||||
const loadingBar = useRef(null);
|
||||
const [dialogMessage, setDialogMessage] = useState<DialogBoxAttributes>();
|
||||
|
@ -249,6 +254,10 @@ export default function App(props) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setMapEnabled(getLocalMapEnabled());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectron()) {
|
||||
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 = () => {
|
||||
!isLoadingBarRunning.current && loadingBar.current?.continuousStart();
|
||||
isLoadingBarRunning.current = true;
|
||||
|
@ -493,6 +512,8 @@ export default function App(props) {
|
|||
setThemeColor,
|
||||
somethingWentWrong,
|
||||
setDialogBoxAttributesV2,
|
||||
mapEnabled,
|
||||
updateMapEnabled,
|
||||
}}>
|
||||
{(loading || !isI18nReady) && (
|
||||
<Overlay
|
||||
|
|
|
@ -38,7 +38,11 @@ import {
|
|||
setIsFirstLogin,
|
||||
setJustSignedUp,
|
||||
} from 'utils/storage';
|
||||
import { isTokenValid, validateKey } from 'services/userService';
|
||||
import {
|
||||
isTokenValid,
|
||||
syncMapEnabled,
|
||||
validateKey,
|
||||
} from 'services/userService';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import { LoadingOverlay } from 'components/LoadingOverlay';
|
||||
|
@ -120,6 +124,7 @@ import { IsArchived } from 'utils/magicMetadata';
|
|||
import { isSameDayAnyYear, isInsideLocationTag } from 'utils/search';
|
||||
import { getSessionExpiredMessage } from 'utils/ui';
|
||||
import { syncEntities } from 'services/entityService';
|
||||
import { constructUserIDToEmailMap } from 'services/collectionService';
|
||||
|
||||
export const DeadCenter = styled('div')`
|
||||
flex: 1;
|
||||
|
@ -137,10 +142,12 @@ const defaultGalleryContext: GalleryContextType = {
|
|||
setActiveCollection: () => null,
|
||||
syncWithRemote: () => null,
|
||||
setBlockingLoad: () => null,
|
||||
setIsInSearchMode: () => null,
|
||||
photoListHeader: null,
|
||||
openExportModal: () => null,
|
||||
authenticateUser: () => null,
|
||||
user: null,
|
||||
userIDToEmailMap: null,
|
||||
};
|
||||
|
||||
export const GalleryContext = createContext<GalleryContextType>(
|
||||
|
@ -216,6 +223,8 @@ export default function Gallery() {
|
|||
useContext(AppContext);
|
||||
const [collectionSummaries, setCollectionSummaries] =
|
||||
useState<CollectionSummaries>();
|
||||
const [userIDToEmailMap, setUserIDToEmailMap] =
|
||||
useState<Map<number, string>>(null);
|
||||
const [activeCollection, setActiveCollection] = useState<number>(undefined);
|
||||
const [fixCreationTimeView, setFixCreationTimeView] = useState(false);
|
||||
const [fixCreationTimeAttributes, setFixCreationTimeAttributes] =
|
||||
|
@ -312,6 +321,17 @@ export default function Gallery() {
|
|||
setDerivativeState(user, collections, files, trashedFiles, hiddenFiles);
|
||||
}, [collections, files, hiddenFiles, trashedFiles, user]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (!collections) {
|
||||
return;
|
||||
}
|
||||
const userIdToEmailMap = await constructUserIDToEmailMap();
|
||||
setUserIDToEmailMap(userIdToEmailMap);
|
||||
};
|
||||
fetchData();
|
||||
}, [collections]);
|
||||
|
||||
useEffect(() => {
|
||||
collectionSelectorAttributes && setCollectionSelectorView(true);
|
||||
}, [collectionSelectorAttributes]);
|
||||
|
@ -564,6 +584,7 @@ export default function Gallery() {
|
|||
await syncHiddenFiles(hiddenCollections, setHiddenFiles);
|
||||
await syncTrash(collections, setTrashedFiles);
|
||||
await syncEntities();
|
||||
await syncMapEnabled();
|
||||
} catch (e) {
|
||||
switch (e.message) {
|
||||
case ServerErrorCodes.SESSION_EXPIRED:
|
||||
|
@ -602,7 +623,6 @@ export default function Gallery() {
|
|||
setFavItemIds(favItemIds);
|
||||
const archivedCollections = getArchivedCollections(collections);
|
||||
setArchivedCollections(archivedCollections);
|
||||
|
||||
const collectionSummaries = await getCollectionSummaries(
|
||||
user,
|
||||
collections,
|
||||
|
@ -849,9 +869,11 @@ export default function Gallery() {
|
|||
setActiveCollection,
|
||||
syncWithRemote,
|
||||
setBlockingLoad,
|
||||
setIsInSearchMode,
|
||||
photoListHeader,
|
||||
openExportModal,
|
||||
authenticateUser,
|
||||
userIDToEmailMap,
|
||||
user,
|
||||
}}>
|
||||
<FullScreenDropZone
|
||||
|
|
|
@ -1260,3 +1260,30 @@ export async function unhideToCollection(
|
|||
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 { UploadURL } from 'types/upload';
|
||||
import { S3FileAttributes } from 'types/file';
|
||||
import { USE_CF_PROXY } from 'constants/upload';
|
||||
import { Remote } from 'comlink';
|
||||
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
|
||||
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
|
||||
|
@ -112,20 +111,12 @@ export async function uploadThumbnail(
|
|||
updatedThumbnail,
|
||||
fileKey
|
||||
);
|
||||
let thumbnailObjectKey: string = null;
|
||||
if (USE_CF_PROXY) {
|
||||
thumbnailObjectKey = await uploadHttpClient.putFileV2(
|
||||
uploadURL,
|
||||
encryptedThumbnail.encryptedData,
|
||||
() => {}
|
||||
);
|
||||
} else {
|
||||
thumbnailObjectKey = await uploadHttpClient.putFile(
|
||||
uploadURL,
|
||||
encryptedThumbnail.encryptedData,
|
||||
() => {}
|
||||
);
|
||||
}
|
||||
const thumbnailObjectKey = await uploadHttpClient.putFile(
|
||||
uploadURL,
|
||||
encryptedThumbnail.encryptedData,
|
||||
() => {}
|
||||
);
|
||||
|
||||
return {
|
||||
objectKey: thumbnailObjectKey,
|
||||
decryptionHeader: encryptedThumbnail.decryptionHeader,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART,
|
||||
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
|
||||
USE_CF_PROXY,
|
||||
} from 'constants/upload';
|
||||
import UIService from './uiService';
|
||||
import UploadHttpClient from './uploadHttpClient';
|
||||
|
@ -63,7 +62,7 @@ export async function uploadStreamInParts(
|
|||
index
|
||||
);
|
||||
let eTag = null;
|
||||
if (USE_CF_PROXY) {
|
||||
if (!uploadService.getIsCFUploadProxyDisabled()) {
|
||||
eTag = await UploadHttpClient.putFilePartV2(
|
||||
fileUploadURL,
|
||||
uploadChunk,
|
||||
|
@ -117,7 +116,7 @@ async function completeMultipartUpload(
|
|||
{ CompleteMultipartUpload: { Part: partEtags } },
|
||||
options
|
||||
);
|
||||
if (USE_CF_PROXY) {
|
||||
if (!uploadService.getIsCFUploadProxyDisabled()) {
|
||||
await UploadHttpClient.completeMultipartUploadV2(completeURL, body);
|
||||
} else {
|
||||
await UploadHttpClient.completeMultipartUpload(completeURL, body);
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
getPublicCollectionUID,
|
||||
} from 'services/publicCollectionService';
|
||||
import { getDedicatedCryptoWorker } from 'utils/comlink/ComlinkCryptoWorker';
|
||||
import { getDisableCFUploadProxyFlag } from 'services/userService';
|
||||
|
||||
const MAX_CONCURRENT_UPLOADS = 4;
|
||||
|
||||
|
@ -61,7 +62,8 @@ class UploadManager {
|
|||
publicCollectProps: PublicUploadProps
|
||||
) {
|
||||
UIService.init(progressUpdater);
|
||||
UploadService.init(publicCollectProps);
|
||||
const isCFUploadProxyDisabled = await getDisableCFUploadProxyFlag();
|
||||
UploadService.init(publicCollectProps, isCFUploadProxyDisabled);
|
||||
this.setFiles = setFiles;
|
||||
this.publicUploadProps = publicCollectProps;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import {
|
|||
import { encryptFile, getFileSize, readFile } from './fileService';
|
||||
import { uploadStreamUsingMultipart } from './multiPartUploadService';
|
||||
import UIService from './uiService';
|
||||
import { USE_CF_PROXY } from 'constants/upload';
|
||||
import { Remote } from 'comlink';
|
||||
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
|
||||
import publicUploadHttpClient from './publicUploadHttpClient';
|
||||
|
@ -52,8 +51,14 @@ class UploadService {
|
|||
|
||||
private publicUploadProps: PublicUploadProps = undefined;
|
||||
|
||||
init(publicUploadProps: PublicUploadProps) {
|
||||
private isCFUploadProxyDisabled: boolean = false;
|
||||
|
||||
init(
|
||||
publicUploadProps: PublicUploadProps,
|
||||
isCFUploadProxyDisabled: boolean
|
||||
) {
|
||||
this.publicUploadProps = publicUploadProps;
|
||||
this.isCFUploadProxyDisabled = isCFUploadProxyDisabled;
|
||||
}
|
||||
|
||||
async setFileCount(fileCount: number) {
|
||||
|
@ -73,6 +78,10 @@ class UploadService {
|
|||
return this.uploaderName;
|
||||
}
|
||||
|
||||
getIsCFUploadProxyDisabled() {
|
||||
return this.isCFUploadProxyDisabled;
|
||||
}
|
||||
|
||||
reducePendingUploadCount() {
|
||||
this.pendingUploadCount--;
|
||||
}
|
||||
|
@ -158,7 +167,7 @@ class UploadService {
|
|||
file.localID
|
||||
);
|
||||
const fileUploadURL = await this.getUploadURL();
|
||||
if (USE_CF_PROXY) {
|
||||
if (!this.isCFUploadProxyDisabled) {
|
||||
fileObjectKey = await UploadHttpClient.putFileV2(
|
||||
fileUploadURL,
|
||||
file.file.encryptedData as Uint8Array,
|
||||
|
@ -174,7 +183,7 @@ class UploadService {
|
|||
}
|
||||
const thumbnailUploadURL = await this.getUploadURL();
|
||||
let thumbnailObjectKey: string = null;
|
||||
if (USE_CF_PROXY) {
|
||||
if (!this.isCFUploadProxyDisabled) {
|
||||
thumbnailObjectKey = await UploadHttpClient.putFileV2(
|
||||
thumbnailUploadURL,
|
||||
file.thumbnail.encryptedData,
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
UserDetails,
|
||||
DeleteChallengeResponse,
|
||||
GetRemoteStoreValueResponse,
|
||||
GetFeatureFlagResponse,
|
||||
} from 'types/user';
|
||||
import { ServerErrorCodes } from 'utils/error';
|
||||
import isElectron from 'is-electron';
|
||||
|
@ -28,6 +29,7 @@ import { B64EncryptionResult } from 'types/crypto';
|
|||
import { getLocalFamilyData, isPartOfFamily } from 'utils/user/family';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { APPS, getAppName } from 'constants/apps';
|
||||
import { setLocalMapEnabled } from 'utils/storage';
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
|
||||
|
@ -452,3 +454,66 @@ export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => {
|
|||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -111,6 +111,7 @@ declare module '@mui/material/styles' {
|
|||
blur: BlurStrength;
|
||||
white: Omit<Strength, 'faint'>;
|
||||
black: Omit<Strength, 'faint'>;
|
||||
avatarColors: AvatarColors;
|
||||
}
|
||||
|
||||
interface ThemeColorsOptions {
|
||||
|
@ -126,6 +127,7 @@ declare module '@mui/material/styles' {
|
|||
blur?: Partial<BlurStrength>;
|
||||
white?: Partial<Omit<Strength, 'faint'>>;
|
||||
black?: Partial<Omit<Strength, 'faint'>>;
|
||||
avatarColors?: Partial<AvatarColors>;
|
||||
}
|
||||
|
||||
interface ColorStrength {
|
||||
|
@ -192,5 +194,7 @@ declare module '@mui/material/styles' {
|
|||
muted: number;
|
||||
faint: number;
|
||||
}
|
||||
|
||||
type AvatarColors = Array<string>;
|
||||
}
|
||||
export {};
|
||||
|
|
|
@ -33,10 +33,12 @@ export type GalleryContextType = {
|
|||
setActiveCollection: (collection: number) => void;
|
||||
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
|
||||
setBlockingLoad: (value: boolean) => void;
|
||||
setIsInSearchMode: (value: boolean) => void;
|
||||
photoListHeader: TimeStampListItem;
|
||||
openExportModal: () => void;
|
||||
authenticateUser: (callback: () => void) => void;
|
||||
user: User;
|
||||
userIDToEmailMap: Map<number, string>;
|
||||
};
|
||||
|
||||
export enum CollectionSelectorIntent {
|
||||
|
|
|
@ -102,3 +102,7 @@ export interface UpdateRemoteStoreValueRequest {
|
|||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface GetFeatureFlagResponse {
|
||||
disableCFUploadProxy?: boolean;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ export const getPublicCollectionThumbnailURL = (id: number) => {
|
|||
};
|
||||
|
||||
export const getUploadEndpoint = () => {
|
||||
const endpoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT;
|
||||
const endpoint = process.env.NEXT_PUBLIC_ENTE_UPLOAD_ENDPOINT;
|
||||
if (isDevDeployment() && endpoint) {
|
||||
return endpoint;
|
||||
}
|
||||
|
|
|
@ -26,3 +26,11 @@ export function setLivePhotoInfoShownCount(count) {
|
|||
export function getUserLocale(): Language {
|
||||
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',
|
||||
API_ENDPOINT = 'apiEndpoint',
|
||||
LOCALE = 'locale',
|
||||
MAP_ENABLED = 'mapEnabled',
|
||||
}
|
||||
|
||||
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 { Subscription } from 'types/billing';
|
||||
import { logoutUser } from 'services/userService';
|
||||
import { Link } from '@mui/material';
|
||||
import { OPEN_STREET_MAP_LINK } from 'components/Sidebar/EnableMap';
|
||||
export const getDownloadAppMessage = (): DialogBoxAttributes => {
|
||||
return {
|
||||
title: t('DOWNLOAD_APP'),
|
||||
|
@ -129,3 +131,36 @@ export const getSessionExpiredMessage = (): DialogBoxAttributes => ({
|
|||
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