mirror of
https://github.com/mue/mue.git
synced 2026-07-07 14:17:09 +02:00
@@ -21,6 +21,7 @@
|
||||
"@sentry/react": "^7.17.2",
|
||||
"embla-carousel-autoplay": "^7.0.3",
|
||||
"embla-carousel-react": "^7.0.3",
|
||||
"fast-blurhash": "^1.1.1",
|
||||
"image-conversion": "^2.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-clock": "3.1.0",
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -17,6 +17,7 @@ specifiers:
|
||||
embla-carousel-react: ^7.0.3
|
||||
eslint: ^8.26.0
|
||||
eslint-config-react-app: ^7.0.1
|
||||
fast-blurhash: ^1.1.1
|
||||
husky: ^8.0.1
|
||||
image-conversion: ^2.1.1
|
||||
prettier: ^2.7.1
|
||||
@@ -45,6 +46,7 @@ dependencies:
|
||||
'@sentry/react': 7.17.2_react@18.2.0
|
||||
embla-carousel-autoplay: 7.0.3
|
||||
embla-carousel-react: 7.0.3_react@18.2.0
|
||||
fast-blurhash: 1.1.1
|
||||
image-conversion: 2.1.1
|
||||
react: 18.2.0
|
||||
react-clock: 3.1.0_biqbaboplfbrettd7655fr4n2y
|
||||
@@ -3605,6 +3607,10 @@ packages:
|
||||
resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==}
|
||||
dev: false
|
||||
|
||||
/fast-blurhash/1.1.1:
|
||||
resolution: {integrity: sha512-U3mTWNRE92S4gKARJBbUmEpuYtnet+1BEeUf1qWrUHs54Db4+0aDqI9LBu7doCueFf2SRlfbR89bj+Cm89uw8A==}
|
||||
dev: false
|
||||
|
||||
/fast-deep-equal/3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
56
src/components/modals/main/settings/ChipSelect.jsx
Normal file
56
src/components/modals/main/settings/ChipSelect.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useState, memo, useEffect } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Box from '@mui/material/Box';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Select from '@mui/material/Select';
|
||||
import Chip from '@mui/material/Chip';
|
||||
|
||||
function ChipSelect({ label, options, name }) {
|
||||
const [optionsSelected, setoptionsSelected] = useState([]);
|
||||
|
||||
const handleChange = (event) => {
|
||||
const {
|
||||
target: { value },
|
||||
} = event;
|
||||
setoptionsSelected(
|
||||
typeof value === 'string' ? value.split(',') : value,
|
||||
);
|
||||
localStorage.setItem('apiCategories', optionsSelected)
|
||||
};
|
||||
console.log(localStorage.getItem('apiCategories'))
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormControl>
|
||||
<InputLabel id="chipSelect-label">{label}</InputLabel>
|
||||
<Select
|
||||
labelId="chipSelect-label"
|
||||
id="chipSelect"
|
||||
multiple
|
||||
value={optionsSelected}
|
||||
onChange={handleChange}
|
||||
input={<OutlinedInput id="select-multiple-chip" label={label} />}
|
||||
renderValue={(optionsSelected) => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{optionsSelected.map((value) => (
|
||||
<Chip key={value} label={value} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem key={option.name} value={option.name}>
|
||||
{option.name.charAt(0).toUpperCase() + option.name.slice(1)} ({option.count})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ChipSelect);
|
||||
@@ -5,6 +5,7 @@ import { MdSource, MdOutlineKeyboardArrowRight, MdOutlineAutoAwesome } from 'rea
|
||||
|
||||
import Header from '../../Header';
|
||||
import Checkbox from '../../Checkbox';
|
||||
import ChipSelect from '../../ChipSelect';
|
||||
import Dropdown from '../../Dropdown';
|
||||
import Slider from '../../Slider';
|
||||
import Radio from '../../Radio';
|
||||
@@ -125,16 +126,25 @@ export default class BackgroundSettings extends PureComponent {
|
||||
</Dropdown>
|
||||
</>
|
||||
) : (
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.settings.sections.background.category')}
|
||||
name="apiCategories"
|
||||
>
|
||||
{this.state.backgroundCategories.map((category) => (
|
||||
<MenuItem value={category.name} key={category.name}>
|
||||
{category.name.charAt(0).toUpperCase() + category.name.slice(1)} ({category.count})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
<>
|
||||
{/*<Dropdown
|
||||
label={variables.getMessage('modals.main.settings.sections.background.category')}
|
||||
name="apiCategories"
|
||||
>
|
||||
{this.state.backgroundCategories.map((category) => (
|
||||
<MenuItem value={category.name} key={category.name}>
|
||||
{category.name.charAt(0).toUpperCase() + category.name.slice(1)} (
|
||||
{category.count})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
<ChipSelect
|
||||
label={variables.getMessage('modals.main.settings.sections.background.category')}
|
||||
options={this.state.backgroundCategories}
|
||||
name="apiCategories"
|
||||
></ChipSelect>
|
||||
*/}
|
||||
</>
|
||||
)}
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'modules/helpers/background/widget';
|
||||
|
||||
import './scss/index.scss';
|
||||
import { decodeBlurHash } from 'fast-blurhash';
|
||||
|
||||
export default class Background extends PureComponent {
|
||||
constructor() {
|
||||
@@ -32,7 +33,7 @@ export default class Background extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
setBackground() {
|
||||
async setBackground() {
|
||||
const backgroundImage = document.getElementById('backgroundImage');
|
||||
|
||||
if (this.state.url !== '') {
|
||||
@@ -53,31 +54,60 @@ export default class Background extends PureComponent {
|
||||
return (backgroundImage.style.background = `url(${url})`);
|
||||
}
|
||||
|
||||
// firstly we set the background as hidden and make sure there is no background set currently
|
||||
backgroundImage.classList.add('backgroundPreload');
|
||||
// // firstly we set the background as hidden and make sure there is no background set currently
|
||||
// backgroundImage.classList.add('backgroundPreload');
|
||||
backgroundImage.style.background = null;
|
||||
|
||||
// same with photo information if not using custom background
|
||||
photoInformation?.classList.add('backgroundPreload');
|
||||
// // same with photo information if not using custom background
|
||||
// photoInformation?.classList.add('backgroundPreload');
|
||||
|
||||
// preloader for background transition, required, so it loads in nice
|
||||
const preloader = document.createElement('img');
|
||||
preloader.src = url;
|
||||
// // preloader for background transition, required, so it loads in nice
|
||||
// const preloader = document.createElement('img');
|
||||
// preloader.src = url;
|
||||
|
||||
// once image has loaded, add the fade-in transition
|
||||
preloader.addEventListener('load', () => {
|
||||
backgroundImage.classList.remove('backgroundPreload');
|
||||
// // once image has loaded, add the fade-in transition
|
||||
// preloader.addEventListener('load', () => {
|
||||
// backgroundImage.classList.remove('backgroundPreload');
|
||||
// backgroundImage.classList.add('fade-in');
|
||||
|
||||
// backgroundImage.style.background = `url(${url})`;
|
||||
// // remove the preloader element we created earlier
|
||||
// preloader.remove();
|
||||
|
||||
// if (photoInformation) {
|
||||
// photoInformation.classList.remove('backgroundPreload');
|
||||
// photoInformation.classList.add('fade-in');
|
||||
// }
|
||||
// });
|
||||
|
||||
if (this.state.photoInfo.blur_hash) {
|
||||
backgroundImage.style.backgroundColor = this.state.photoInfo.colour;
|
||||
backgroundImage.classList.add('fade-in');
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imageData = ctx.createImageData(32, 32);
|
||||
imageData.data.set(decodeBlurHash(this.state.photoInfo.blur_hash, 32, 32));
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
// let blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
|
||||
// const blobUrl = URL.createObjectURL(blob);
|
||||
backgroundImage.style.backgroundImage = `url(${canvas.toDataURL()})`;
|
||||
// backgroundImage.style.backgroundImage = `url(${blobUrl})`;
|
||||
}
|
||||
|
||||
backgroundImage.style.background = `url(${url})`;
|
||||
// remove the preloader element we created earlier
|
||||
preloader.remove();
|
||||
|
||||
if (photoInformation) {
|
||||
photoInformation.classList.remove('backgroundPreload');
|
||||
photoInformation.classList.add('fade-in');
|
||||
}
|
||||
});
|
||||
// const img = new Image();
|
||||
// img.fetchPriority = 'high';
|
||||
// img.decoding = 'async';
|
||||
// img.src = url;
|
||||
// await img.decode();
|
||||
// this.state.photoInfo.width = img.width;
|
||||
// this.state.photoInfo.height = img.height;
|
||||
const blobUrl = URL.createObjectURL(await (await fetch(url)).blob()); // TODO: revoke this later
|
||||
backgroundImage.classList.add('backgroundTransform');
|
||||
backgroundImage.style.backgroundImage = `url(${blobUrl})`;
|
||||
// img.remove();
|
||||
// URL.revokeObjectURL(blobUrl);
|
||||
} else {
|
||||
// custom colour
|
||||
backgroundImage.setAttribute('style', this.state.style);
|
||||
@@ -113,6 +143,14 @@ export default class Background extends PureComponent {
|
||||
return setFavourited(favourited);
|
||||
}
|
||||
|
||||
let apiCategories;
|
||||
|
||||
try {
|
||||
let apiCategories = JSON.parse(localStorage.getItem('apiCategories'))
|
||||
} catch (error) {
|
||||
let apiCategories = localStorage.getItem('apiCategories')
|
||||
}
|
||||
|
||||
const type = localStorage.getItem('backgroundType');
|
||||
switch (type) {
|
||||
case 'api':
|
||||
@@ -122,20 +160,19 @@ export default class Background extends PureComponent {
|
||||
|
||||
// API background
|
||||
const backgroundAPI = localStorage.getItem('backgroundAPI');
|
||||
const apiCategories = JSON.parse(localStorage.getItem('apiCategories'));
|
||||
const apiQuality = localStorage.getItem('apiQuality');
|
||||
|
||||
let requestURL, data;
|
||||
switch (backgroundAPI) {
|
||||
case 'unsplash':
|
||||
requestURL = `${variables.constants.API_URL}/images/unsplash?category=${apiCategories}&quality=${apiQuality}`;
|
||||
requestURL = `${variables.constants.API_URL}/images/unsplash?categories=${apiCategories}&quality=${apiQuality}`;
|
||||
break;
|
||||
case 'pexels':
|
||||
requestURL = `${variables.constants.API_URL}/images/pexels?quality=${apiQuality}`;
|
||||
break;
|
||||
// Defaults to Mue
|
||||
default:
|
||||
requestURL = `${variables.constants.API_URL}/images/random?category=${apiCategories}&quality=${apiQuality}`;
|
||||
requestURL = `${variables.constants.API_URL}/images/random?categories=${apiCategories}&quality=${apiQuality}`;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -172,6 +209,9 @@ export default class Background extends PureComponent {
|
||||
downloads: data.downloads || null,
|
||||
likes: data.likes || null,
|
||||
description: data.description || null,
|
||||
colour: data.colour,
|
||||
blur_hash: data.blur_hash,
|
||||
pun: data.pun || null,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -97,15 +97,15 @@ function PhotoInformation({ info, url, api }) {
|
||||
const ddgProxy = localStorage.getItem('ddgProxy') === 'true';
|
||||
|
||||
// get resolution
|
||||
const img = new Image();
|
||||
img.onload = (event) => {
|
||||
setWidth(event.target.width);
|
||||
setHeight(event.target.height);
|
||||
};
|
||||
img.src =
|
||||
ddgProxy && !info.offline && !url.startsWith('data:')
|
||||
? variables.constants.DDG_IMAGE_PROXY + url
|
||||
: url;
|
||||
// const img = new Image();
|
||||
// img.onload = (event) => {
|
||||
// setWidth(event.target.width);
|
||||
// setHeight(event.target.height);
|
||||
// };
|
||||
// img.src =
|
||||
// ddgProxy && !info.offline && !url.startsWith('data:')
|
||||
// ? variables.constants.DDG_IMAGE_PROXY + url
|
||||
// : url;
|
||||
|
||||
// info is still there because we want the favourite button to work
|
||||
if (localStorage.getItem('photoInformation') === 'false') {
|
||||
@@ -132,7 +132,6 @@ function PhotoInformation({ info, url, api }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const zoom = 12;
|
||||
const tile = variables.constants.API_URL + `/map?latitude=${info.latitude}&longitude=${info.longitude}`;
|
||||
showingPhotoMap = true;
|
||||
|
||||
@@ -318,4 +317,4 @@ function PhotoInformation({ info, url, api }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(PhotoInformation);
|
||||
export default memo(PhotoInformation);
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
.concept-stats {
|
||||
display: flex;
|
||||
padding-top: 5px;
|
||||
padding-top: 5px;
|
||||
gap: 20px;
|
||||
@include themed() {
|
||||
color: t($subColor);
|
||||
@@ -61,6 +61,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mapOn {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
flex-shrink: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.photoInformation-legacy {
|
||||
font-size: 1.36em;
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
@@ -293,7 +310,7 @@
|
||||
}
|
||||
|
||||
.photoInformation-content {
|
||||
line-height: 2em;
|
||||
line-height: 2em;
|
||||
margin-right: 3px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
zoom: 100% !important;
|
||||
}
|
||||
|
||||
.backgroundTransform {
|
||||
@include animation('fadein2 2s');
|
||||
transition: background 2s;
|
||||
}
|
||||
|
||||
@include keyframes(fadein2) {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.backgroundPreload {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user