diff --git a/src/features/background/api/utils.js b/src/features/background/api/utils.js
new file mode 100644
index 00000000..768bd940
--- /dev/null
+++ b/src/features/background/api/utils.js
@@ -0,0 +1,34 @@
+import variables from 'config/variables';
+
+/**
+ * It takes a URL, fetches the resource, and returns a URL to the resource.
+ * @param {string} url The URL to fetch.
+ * @returns A promise that resolves to a blob URL.
+ */
+export const toDataURL = async (url) => {
+ const res = await fetch(url);
+ return URL.createObjectURL(await res.blob());
+};
+
+/**
+ * It takes a string, makes it lowercase, removes commas, and replaces spaces with dashes.
+ * @param {string} text The string to format.
+ * @returns A function that takes a string and returns a string.
+ */
+export const formatText = (text) => {
+ return text.toLowerCase().replaceAll(',', '').replaceAll(' ', '-');
+};
+
+/**
+ * It downloads an image from a URL and saves it to the user's computer.
+ * @param {object} info The photo information.
+ */
+export const downloadImage = async (info) => {
+ const link = document.createElement('a');
+ link.href = await toDataURL(info.url);
+ link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`; // image is more likely to be webp or avif btw
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ variables.stats.postEvent('feature', 'background', 'download');
+};
diff --git a/src/features/background/components/ActionButtons.jsx b/src/features/background/components/ActionButtons.jsx
new file mode 100644
index 00000000..8f0a428a
--- /dev/null
+++ b/src/features/background/components/ActionButtons.jsx
@@ -0,0 +1,56 @@
+import { Tooltip } from 'components/Elements';
+import {
+ MdIosShare as Share,
+ MdGetApp as Download,
+ MdVisibilityOff as VisibilityOff,
+} from 'react-icons/md';
+import Favourite from './Favourite';
+import { downloadImage } from '../api/utils';
+import variables from 'config/variables';
+
+const ActionButtons = ({
+ info,
+ favouriteTooltipText,
+ setFavouriteTooltipText,
+ openShareModal,
+ openExcludeModal,
+}) => {
+ return (
+
+ {!info.offline && (
+
+ openShareModal(true)} />
+
+ )}
+
+ setFavouriteTooltipText(text)}
+ />
+
+ {!info.offline && (
+
+ downloadImage(info)} />
+
+ )}
+ {info.pun && info.category && (
+
+ openExcludeModal(true)} />
+
+ )}
+
+ );
+};
+
+export default ActionButtons;
diff --git a/src/features/background/components/InformationItems.jsx b/src/features/background/components/InformationItems.jsx
new file mode 100644
index 00000000..5b3d86cd
--- /dev/null
+++ b/src/features/background/components/InformationItems.jsx
@@ -0,0 +1,57 @@
+import {
+ MdLocationOn,
+ MdPhotoCamera,
+ MdCrop as Resolution,
+ MdCategory as Category,
+ MdSource as Source,
+} from 'react-icons/md';
+import variables from 'config/variables';
+
+const InformationItems = ({ info, width, height, api }) => {
+ return (
+
+ {info.location && info.location !== 'N/A' && (
+
+
+ {info.location}
+
+ )}
+ {info.camera && info.camera !== 'N/A' && (
+
+
+ {info.camera}
+
+ )}
+
+
+
+ {width}x{height}
+
+
+ {info.category && (
+
+
+ {info.category[0].toUpperCase() + info.category.slice(1)}
+
+ )}
+ {api && (
+
+ )}
+
+ );
+};
+
+export default InformationItems;
diff --git a/src/features/background/components/PhotoInformation.jsx b/src/features/background/components/PhotoInformation.jsx
index 0f5ff667..7e8b1951 100644
--- a/src/features/background/components/PhotoInformation.jsx
+++ b/src/features/background/components/PhotoInformation.jsx
@@ -1,65 +1,19 @@
import variables from 'config/variables';
-import { useState, memo } from 'react';
-import Favourite from './Favourite';
-import {
- MdInfo,
- MdLocationOn,
- MdPhotoCamera,
- MdCrop as Resolution,
- MdGetApp as Download,
- MdVisibility as Views,
- MdIosShare as Share,
- MdSource as Source,
- MdFavorite as MdFavourite,
- MdCategory as Category,
- MdVisibilityOff as VisibilityOff,
-} from 'react-icons/md';
-import { Tooltip } from 'components/Elements';
-
+import { useState, memo, useEffect, useCallback } from 'react';
+import { MdInfo, MdLocationOn } from 'react-icons/md';
import Modal from 'react-modal';
import { ShareModal } from 'components/Elements';
import ExcludeModal from './ExcludeModal';
-
-/**
- * It takes a URL, fetches the resource, and returns a URL to the resource.
- * @param {string} url The URL to fetch.
- * @returns A promise that resolves to a blob URL.
- */
-const toDataURL = async (url) => {
- const res = await fetch(url);
- return URL.createObjectURL(await res.blob());
-};
-
-/**
- * It takes a string, makes it lowercase, removes commas, and replaces spaces with dashes.
- * @param {string} text The string to format.
- * @returns A function that takes a string and returns a string.
- */
-const formatText = (text) => {
- return text.toLowerCase().replaceAll(',', '').replaceAll(' ', '-');
-};
-
-/**
- * It downloads an image from a URL and saves it to the user's computer.
- * @param {object} info The photo information.
- */
-const downloadImage = async (info) => {
- const link = document.createElement('a');
- link.href = await toDataURL(info.url);
- link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`; // image is more likely to be webp or avif btw
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- variables.stats.postEvent('feature', 'background', 'download');
-};
+import InformationItems from './InformationItems';
+import ActionButtons from './ActionButtons';
+import UnsplashStats from './UnsplashStats';
function PhotoInformation({ info, url, api }) {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [usePhotoMap, setPhotoMap] = useState(false);
const [useMapIcon, setMapIcon] = useState(true);
- const [showExtraInfo, setshowExtraInfo] = useState(false);
- //const [showOld, setShowOld] = useState(true);
+ const [showExtraInfo, setShowExtraInfo] = useState(false);
const [other, setOther] = useState(false);
const [shareModal, openShareModal] = useState(false);
const [excludeModal, openExcludeModal] = useState(false);
@@ -67,6 +21,47 @@ function PhotoInformation({ info, url, api }) {
variables.getMessage('widgets.quote.favourite'),
);
+ useEffect(() => {
+ const img = new Image();
+ img.onload = (event) => {
+ setWidth(event.target.width);
+ setHeight(event.target.height);
+ };
+ img.src = url;
+ }, [url]);
+
+ const photoMap = useCallback(() => {
+ if (
+ localStorage.getItem('photoMap') !== 'true' ||
+ !info.latitude ||
+ !info.longitude ||
+ usePhotoMap === false
+ ) {
+ return null;
+ }
+
+ const tile = `${variables.constants.API_URL}/map?latitude=${info.latitude}&longitude=${info.longitude}`;
+ return (
+
+
+
+ );
+ }, [info.latitude, info.longitude, usePhotoMap]);
+
+ useEffect(() => {
+ const photoInformationElement = document.getElementsByClassName('photoInformation')[0];
+ if (photoInformationElement) {
+ photoInformationElement.onmouseover = () => {
+ setPhotoMap(true);
+ setMapIcon(false);
+ };
+ }
+ }, []);
+
if (info.hidden === true || !info.credit) {
return null;
}
@@ -74,7 +69,6 @@ function PhotoInformation({ info, url, api }) {
let credit = info.credit;
let photo = variables.getMessage('widgets.background.credit');
- // unsplash credit
if (info.photographerURL && info.photographerURL !== '' && !info.offline && api) {
photo = (
@@ -82,23 +76,12 @@ function PhotoInformation({ info, url, api }) {
);
credit = (
- <>
-
- {info.credit}
-
- >
+
+ {info.credit}
+
);
}
- // get resolution
- const img = new Image();
- img.onload = (event) => {
- setWidth(event.target.width);
- setHeight(event.target.height);
- };
- img.src = url;
-
- // info is still there because we want the favourite button to work
if (localStorage.getItem('photoInformation') === 'false') {
return (
@@ -112,160 +95,11 @@ function PhotoInformation({ info, url, api }) {
);
}
- let showingPhotoMap = false;
- const photoMap = () => {
- if (
- localStorage.getItem('photoMap') !== 'true' ||
- !info.latitude ||
- !info.longitude ||
- usePhotoMap === false
- ) {
- return null;
- }
-
- const tile =
- variables.constants.API_URL + `/map?latitude=${info.latitude}&longitude=${info.longitude}`;
- showingPhotoMap = true;
-
- return (
-
-
-
- );
- };
-
- const InformationItems = () => {
- return (
-
- {info.location && info.location !== 'N/A' ? (
-
-
- {info.location}
-
- ) : null}
- {info.camera && info.camera !== 'N/A' ? (
-
-
- {info.camera}
-
- ) : null}
-
-
-
- {width}x{height}
-
-
- {info.category && (
-
-
- {info.category[0].toUpperCase() + info.category.slice(1)}
-
- )}
- {api && (
-
- )}
-
- );
- };
-
- const ActionButtons = () => {
- return (
-
- {!info.offline && (
-
- openShareModal(true)} />
-
- )}
-
- setFavouriteTooltipText(text)}
- />
-
- {!info.offline && (
-
- downloadImage(info)} />
-
- )}
- {info.pun && info.category && (
-
- openExcludeModal(true)} />
-
- )}
-
- );
- };
-
- const UnsplashStats = () => {
- return (
-
-
-
- {info.views.toLocaleString()}
-
-
-
- {info.downloads.toLocaleString()}
-
- {!!info.likes ? (
-
-
- {info.likes.toLocaleString()}
-
- ) : null}
-
- );
- };
-
let photoMapClassList = 'map-concept';
if (photoMap() !== null) {
photoMapClassList += ' photoMap';
}
- // only request map image if the user looks at the photo information
- // this is to reduce requests to the api
- try {
- document.getElementsByClassName('photoInformation')[0].onmouseover = () => {
- try {
- setPhotoMap(true);
- setMapIcon(false);
- } catch (e) {}
- };
- } catch (e) {}
-
const widgetStyle = localStorage.getItem('widgetStyle');
return (
@@ -302,18 +136,18 @@ function PhotoInformation({ info, url, api }) {
)}
- {widgetStyle !== 'legacy' || other ? (
+ {(widgetStyle !== 'legacy' || other) && (
setshowExtraInfo(true)}
- onMouseLeave={() => setshowExtraInfo(false)}
+ onMouseEnter={() => setShowExtraInfo(true)}
+ onMouseLeave={() => setShowExtraInfo(false)}
>
{useMapIcon || photoMap() === null ? : ''}
{photoMap()}
- {showingPhotoMap && (
+ {photoMap() && (
- {info.views && info.downloads !== null ?
: null}
+ {info.views && info.downloads !== null &&
}
-
- {(showExtraInfo || other) && excludeModal === false ? (
+ {(showExtraInfo || other) && !excludeModal && (
<>
{variables.getMessage('widgets.background.information')}
-
-
+
+
>
- ) : null}
+ )}
- ) : null}
+ )}
);
}
diff --git a/src/features/background/components/UnsplashStats.jsx b/src/features/background/components/UnsplashStats.jsx
new file mode 100644
index 00000000..e38eff9d
--- /dev/null
+++ b/src/features/background/components/UnsplashStats.jsx
@@ -0,0 +1,29 @@
+import {
+ MdFavorite as MdFavourite,
+ MdGetApp as Download,
+ MdVisibility as Views,
+} from 'react-icons/md';
+import variables from 'config/variables';
+
+const UnsplashStats = ({ info }) => {
+ return (
+
+
+
+ {info.views.toLocaleString()}
+
+
+
+ {info.downloads.toLocaleString()}
+
+ {!!info.likes && (
+
+
+ {info.likes.toLocaleString()}
+
+ )}
+
+ );
+};
+
+export default UnsplashStats;