feat: marketplace improvements, remove Unsplash text, add Weather skeleton, replace Twitter with X, minor fixes etc

Co-authored-by: Alex Sparkes <alexsparkes@gmail.com>
Co-authored-by: Isaac <contact@eartharoid.me>
This commit is contained in:
David Ralph
2024-05-20 18:58:39 +01:00
parent 4bf61f4b22
commit 1c40816dcb
21 changed files with 533 additions and 200 deletions

View File

@@ -77,6 +77,7 @@ function Tab({ label, currentTab, onClick, navbarTab }) {
variables.getMessage('modals.main.marketplace.all'),
variables.getMessage('modals.main.settings.sections.experimental.title'),
].includes(label);
const mue = [
variables.getMessage('modals.main.marketplace.product.overview'),
variables.getMessage('modals.main.addons.added'),

View File

@@ -395,6 +395,10 @@ p.author {
.subtitle {
color: #ccc !important;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
}
}

View File

@@ -47,7 +47,8 @@ p.description {
}
.header {
text-transform: uppercase;
// text-transform: uppercase;
font-size: small;
@include themed {
color: t($subColor);
@@ -68,6 +69,59 @@ p.description {
}
}
.subHeader {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
width: calc(100% - 30px);
gap: 25px;
.items {
margin-top: 0 !important;
}
.item {
flex: 1 0 40% !important;
}
.infoItem {
display: flex;
flex-flow: row;
align-items: center;
gap: 15px;
flex: 1 0 44%;
svg {
@include themed {
background-image: t($slightGradient);
box-shadow: t($boxShadow);
}
padding: 7px;
border-radius: 100%;
}
.text {
display: flex;
flex-flow: column;
}
}
.header {
font-size: small;
@include themed {
color: t($subColor);
}
}
span {
@include themed {
color: t($color);
}
}
}
.showMoreItems {
display: flex;
flex-flow: column;
@@ -81,3 +135,10 @@ p.description {
flex-flow: column;
gap: 15px;
}
.moreFromCurator {
margin-top: 50px;
display: flex;
flex-flow: column;
gap: 15px;
}

View File

@@ -1,9 +1,9 @@
import { memo } from 'react';
import variables from 'config/variables';
import { MdClose, MdEmail, MdContentCopy } from 'react-icons/md';
import { FaTwitter, FaFacebookF } from 'react-icons/fa';
import { FaFacebookF } from 'react-icons/fa';
import { AiFillWechat } from 'react-icons/ai';
import { SiTencentqq } from 'react-icons/si';
import { SiTencentqq, SiX } from 'react-icons/si';
import Tooltip from '../Tooltip/Tooltip';
import { toast } from 'react-toastify';
@@ -49,13 +49,13 @@ function ShareModal({ modalClose, data }) {
onClick={() =>
window
.open(
`https://twitter.com/intent/tweet?text=Check out ${data.name} on @getmue: ${data.url}`,
`https://x.com/intent/tweet?text=Check out ${data.name} on @getmue: ${data.url}`,
'_blank',
)
.focus()
}
icon={<FaTwitter />}
tooltipTitle="Twitter"
icon={<SiX />}
tooltipTitle="X (Twitter)"
type="icon"
/>
<Button
@@ -65,7 +65,7 @@ function ShareModal({ modalClose, data }) {
.focus()
}
icon={<FaFacebookF />}
tooltipTitle="Twitter"
tooltipTitle="Facebook"
type="icon"
/>
<Button

View File

@@ -25,35 +25,33 @@ function ChipSelect({ label, options }) {
};
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 && `(${option.count})`}
</MenuItem>
))}
</Select>
</FormControl>
</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 && `(${option.count})`}
</MenuItem>
))}
</Select>
</FormControl>
);
}
const MemoizedChipSelect = memo(ChipSelect);
export { ChipSelect as default, MemoizedChipSelect as ChipSelect };
export { ChipSelect as default, MemoizedChipSelect as ChipSelect };

View File

@@ -71,9 +71,6 @@ function PhotoInformation({ info, url, api }) {
return null;
}
// remove unsplash text
const unsplash = variables.getMessage('widgets.background.unsplash');
let credit = info.credit;
let photo = variables.getMessage('widgets.background.credit');
@@ -88,9 +85,6 @@ function PhotoInformation({ info, url, api }) {
<>
<a href={info.photographerURL} target="_blank" rel="noopener noreferrer">
{info.credit}
</a>{' '}
<a href="https://unsplash.com?utm_source=mue" target="_blank" rel="noopener noreferrer">
{unsplash}
</a>
</>
);

View File

@@ -53,6 +53,7 @@ class BackgroundOptions extends PureComponent {
}
updateAPI(e) {
localStorage.setItem('nextImage', null);
if (e === 'mue') {
this.setState({
backgroundCategories: this.state.backgroundCategoriesOG,

View File

@@ -15,5 +15,4 @@ function Lightbox({ modalClose, img }) {
}
const MemoizedLightbox = memo(Lightbox);
export default MemoizedLightbox;
export { MemoizedLightbox as Lightbox };
export { MemoizedLightbox as default, MemoizedLightbox as Lightbox };

View File

@@ -23,6 +23,4 @@ function SideloadFailedModal({ modalClose, reason }) {
}
const MemoizedSideloadFailedModal = memo(SideloadFailedModal);
export default MemoizedSideloadFailedModal;
export { MemoizedSideloadFailedModal as SideloadFailedModal };
export { MemoizedSideloadFailedModal as default, MemoizedSideloadFailedModal as SideloadFailedModal };

View File

@@ -21,6 +21,8 @@ import { Button } from 'components/Elements';
import { install, uninstall } from 'utils/marketplace';
import { Carousel } from '../Elements/Carousel';
import { ShareModal } from 'components/Elements';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { Items } from './Items';
class Item extends PureComponent {
constructor(props) {
@@ -32,9 +34,38 @@ class Item extends PureComponent {
this.props.addonInstalledVersion !== this.props.data.version,
shareModal: false,
count: 5,
moreByCurator: [],
};
}
async getCurator(name) {
try {
const { data } = await (
await fetch(`${variables.constants.API_URL}/marketplace/curator/${name}`)
).json();
const convertedType = (() => {
const map = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return map[this.props.data.data.type];
})();
this.setState({
moreByCurator: data.items.filter(
(item) => item.type !== convertedType && item.name !== this.props.data.data.name,
),
});
console.log(this.state.curator);
} catch (e) {
console.error(e);
}
}
componentDidMount() {
this.getCurator(this.props.data.author);
}
updateAddon() {
uninstall(this.props.data.type, this.props.data.display_name);
install(this.props.data.type, this.props.data);
@@ -125,6 +156,13 @@ class Item extends PureComponent {
/>
<div className="itemPage">
<div className="itemShowcase">
<div className="subHeader">
{moreInfoItem(
<MdAccountCircle />,
variables.getMessage('modals.main.marketplace.product.created_by'),
this.props.data.author,
)}
</div>
{this.props.data.data.photos && (
<div className="carousel">
<div className="carousel_container">
@@ -138,8 +176,21 @@ class Item extends PureComponent {
draggable={false}
src={iconsrc}
onClick={() => this.setState({ showLightbox: true })}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
)}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('modals.main.marketplace.product.description')}
</span>
<span
className="subtitle"
dangerouslySetInnerHTML={{ __html: this.props.data.description }}
/>
</div>
{this.props.data.data.quotes && (
<>
<table>
@@ -194,56 +245,47 @@ class Item extends PureComponent {
)}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('modals.main.marketplace.product.description')}
{variables.getMessage('modals.main.marketplace.product.details')}
</span>
<span
className="subtitle"
dangerouslySetInnerHTML={{ __html: this.props.data.description }}
/>
</div>
<div className="moreInfo">
{moreInfoItem(
<MdBugReport />,
variables.getMessage('modals.main.marketplace.product.version'),
updateButton ? (
<span>
{this.props.data.version} (Installed: {this.props.data.addonInstalledVersion})
</span>
) : (
<span>{this.props.data.version}</span>
),
)}
{moreInfoItem(
<MdAccountCircle />,
variables.getMessage('modals.main.marketplace.product.author'),
this.props.data.author,
)}
{this.props.data.data.quotes &&
moreInfoItem(
<MdFormatQuote />,
variables.getMessage('modals.main.marketplace.product.no_quotes'),
this.props.data.data.quotes.length,
<div className="moreInfo">
{moreInfoItem(
<MdBugReport />,
variables.getMessage('modals.main.marketplace.product.version'),
updateButton ? (
<span>
{this.props.data.version} (Installed: {this.props.data.addonInstalledVersion})
</span>
) : (
<span>{this.props.data.version}</span>
),
)}
{this.props.data.data.photos &&
moreInfoItem(
<MdImage />,
variables.getMessage('modals.main.marketplace.product.no_images'),
this.props.data.data.photos.length,
{this.props.data.data.quotes &&
moreInfoItem(
<MdFormatQuote />,
variables.getMessage('modals.main.marketplace.product.no_quotes'),
this.props.data.data.quotes.length,
)}
{this.props.data.data.photos &&
moreInfoItem(
<MdImage />,
variables.getMessage('modals.main.marketplace.product.no_images'),
this.props.data.data.photos.length,
)}
{this.props.data.data.quotes && this.props.data.data.language
? moreInfoItem(
<MdTranslate />,
variables.getMessage('modals.main.settings.sections.language.title'),
this.props.data.data.language,
)
: null}
{moreInfoItem(
<MdStyle />,
variables.getMessage('modals.main.settings.sections.background.type.title'),
variables.getMessage(
'modals.main.marketplace.' + this.getName(this.props.data.data.type),
) || 'marketplace',
)}
{this.props.data.data.quotes && this.props.data.data.language !== ''
? moreInfoItem(
<MdTranslate />,
variables.getMessage('modals.main.settings.sections.language.title'),
this.props.data.data.language,
)
: null}
{moreInfoItem(
<MdStyle />,
variables.getMessage('modals.main.settings.sections.background.type.title'),
variables.getMessage(
'modals.main.marketplace.' + this.getName(this.props.data.data.type),
) || 'marketplace',
)}
</div>
</div>
</div>
<div
@@ -258,6 +300,10 @@ class Item extends PureComponent {
alt="icon"
draggable={false}
src={this.props.data.data.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
{this.props.button}
<div className="iconButtons">
@@ -290,7 +336,15 @@ class Item extends PureComponent {
<span className="subtitle">
{variables.getMessage('modals.main.marketplace.product.part_of')}
</span>
<span className="title" onClick={this.props.toggleFunction}>
<span
className="title"
onClick={() =>
this.props.toggleFunction(
'collection',
this.props.data.data.in_collections[0].name,
)
}
>
{this.props.data.data.in_collections[0].display_name}
</span>
</div>
@@ -299,6 +353,37 @@ class Item extends PureComponent {
</div>
</div>
</div>
{this.state.moreByCurator.length > 1 ? (
<div className="moreFromCurator">
<span className="title">
{variables.getMessage('modals.main.marketplace.product.more_from_curator', {
name: this.props.data.author,
})}
</span>
<div>
{/* {this.state.curator.items
.filter(
(item) =>
item.name !== this.props.data.data.name &&
item.type !== this.props.data.data.type,
)
.map((item) => (
<div key={`${item.type}/${item.name}`}>{item.display_name}</div>
))} */}
<Items
hideCurator={true}
type={'all'}
items={this.state.moreByCurator}
onCollection={this.state.collection}
toggleFunction={(input) => this.props.toggleFunction('item', input)}
collectionFunction={(input) => this.props.toggleFunction('collection', input)}
filter={''}
/>
</div>
</div>
) : (
''
)}
</div>
);
}

View File

@@ -1,8 +1,7 @@
import variables from 'config/variables';
import React, { memo } from 'react';
import { MdAutoFixHigh, MdOutlineArrowForward, MdOutlineOpenInNew } from 'react-icons/md';
import placeholderIcon from '../../../../assets/icons/marketplace-placeholder.png';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { Button } from 'components/Elements';
@@ -15,16 +14,41 @@ function filterItems(item, filter) {
item.type?.toLowerCase().includes(lowerCaseFilter)
);
}
function Item({ item, toggleFunction, type, onCollection }) {
function Item({ item, toggleFunction, type, onCollection, hideCurator }) {
return (
<div className="item" onClick={() => toggleFunction(item)} key={item.name}>
<img className="item-back" alt="" draggable={false} src={item.icon_url} onError={(e)=>{e.target.onerror = null; e.target.src=placeholderIcon}} aria-hidden="true" />
<img className="item-icon" alt="icon" draggable={false} src={item.icon_url} onError={(e)=>{e.target.onerror = null; e.target.src=placeholderIcon}} />
<img
className="item-back"
alt=""
draggable={false}
src={item.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
aria-hidden="true"
/>
<img
className="item-icon"
alt="icon"
draggable={false}
src={item.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
<span className="card-subtitle">
{variables.getMessage('modals.main.marketplace.by', { author: item.author })}
</span>
{!hideCurator ? (
<span className="card-subtitle">
{variables.getMessage('modals.main.marketplace.by', { author: item.author })}
</span>
) : (
''
)}
{type === 'all' && !onCollection ? (
<span className="card-type">
{variables.getMessage('modals.main.marketplace.' + item.type)}
@@ -36,6 +60,7 @@ function Item({ item, toggleFunction, type, onCollection }) {
}
function Items({
hideCurator,
type,
items,
collection,
@@ -45,53 +70,52 @@ function Items({
filter,
}) {
const shouldShowCollection =
(!onCollection && (filter === null || filter === '')) ||
(collection && !onCollection && (filter === null || filter === '')) ||
(type === 'collections' && !onCollection && (filter === null || filter === ''));
return (
<>
{shouldShowCollection && (
<>
<div
className="collection"
style={
collection?.news
? { backgroundColor: collection?.background_colour }
: {
backgroundImage: `linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.7), transparent, rgba(0, 0, 0, 0.7), rgba(0 ,0, 0, 0.9)), url('${collection?.img}')`,
}
}
>
<div className="content">
<span className="title">{collection?.display_name}</span>
<span className="subtitle">{collection?.description}</span>
</div>
{collection?.news === true ? (
<a
className="btn-collection"
href={collection?.news_link}
target="_blank"
rel="noopener noreferrer"
>
{variables.getMessage('modals.main.marketplace.learn_more')} <MdOutlineOpenInNew />
</a>
) : (
<Button
type="collection"
onClick={() => collectionFunction(collection?.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('modals.main.marketplace.explore_collection')}
iconPlacement={'right'}
/>
)}
<div
className="collection"
style={
collection?.news
? { backgroundColor: collection?.background_colour }
: {
backgroundImage: `linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.7), transparent, rgba(0, 0, 0, 0.7), rgba(0 ,0, 0, 0.9)), url('${collection?.img}')`,
}
}
>
<div className="content">
<span className="title">{collection?.display_name}</span>
<span className="subtitle">{collection?.description}</span>
</div>
</>
{collection?.news === true ? (
<a
className="btn-collection"
href={collection?.news_link}
target="_blank"
rel="noopener noreferrer"
>
{variables.getMessage('modals.main.marketplace.learn_more')} <MdOutlineOpenInNew />
</a>
) : (
<Button
type="collection"
onClick={() => collectionFunction(collection?.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('modals.main.marketplace.explore_collection')}
iconPlacement={'right'}
/>
)}
</div>
)}
<div className="items">
{items
?.filter((item) => filterItems(item, filter))
.map((item) => (
<Item
hideCurator={hideCurator}
item={item}
toggleFunction={toggleFunction}
type={type}

View File

@@ -157,14 +157,17 @@ class Marketplace extends PureComponent {
return;
}
const sorted = this.sortMarketplace(data, false);
this.setState({
items: data,
oldItems: data,
items: sorted.items,
sortType: sorted.sortType,
oldItems: sorted.items,
collections: collections.data,
displayedCollection:
collections.data[Math.floor(Math.random() * collections.data.length)] || [],
done: true,
});
this.sortMarketplace(localStorage.getItem('sortMarketplace'), false);
}
manage(type) {
@@ -210,15 +213,26 @@ class Marketplace extends PureComponent {
}
}
sortMarketplace(value, sendEvent) {
let items = this.state.oldItems;
sortMarketplace(data, sendEvent) {
const value = localStorage.getItem('sortMarketplace');
let items = data || this.state.items;
if (!items) {
return;
}
switch (value) {
case 'a-z':
items.sort();
// fix sort not working sometimes
if (this.state.sortType === 'z-a') {
items.reverse();
}
// sort by name key alphabetically
const sorted = items.sort((a, b) => {
if (a.display_name < b.display_name) {
return -1;
}
if (a.display_name > b.display_name) {
return 1;
}
return 0;
});
items = sorted;
break;
case 'z-a':
items.sort();
@@ -228,14 +242,19 @@ class Marketplace extends PureComponent {
break;
}
this.setState({
items: items,
sortType: value,
});
if (sendEvent) {
variables.stats.postEvent('marketplace', 'Sort');
}
return {
items: items,
sortType: value,
};
}
changeSort(value) {
localStorage.setItem('sortMarketplace', value);
this.setState(this.sortMarketplace(null, true));
}
returnToMain() {
@@ -390,7 +409,7 @@ class Marketplace extends PureComponent {
<Dropdown
label={variables.getMessage('modals.main.addons.sort.title')}
name="sortMarketplace"
onChange={(value) => this.sortMarketplace(value)}
onChange={(value) => this.changeSort(value)}
items={[
{
value: 'a-z',
@@ -406,42 +425,37 @@ class Marketplace extends PureComponent {
</>
)}
{this.props.type === 'collections' && !this.state.collection ? (
this.state.items.map((item) => (
<>
{!item.news ? (
<div
className="collection"
style={
item.news
? { backgroundColor: item.background_colour }
: {
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${item.img}')`,
}
}
>
<div className="content">
<span className="title">{item.display_name}</span>
<span className="subtitle">{item.description}</span>
</div>
<Button
type="collection"
onClick={() => this.toggle('collection', item.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('modals.main.marketplace.explore_collection')}
iconPlacement="right"
/>
this.state.items.map((item) =>
!item.news ? (
<div
className="collection"
style={
item.news
? { backgroundColor: item.background_colour }
: {
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${item.img}')`,
}
}
>
<div className="content">
<span className="title">{item.display_name}</span>
<span className="subtitle">{item.description}</span>
</div>
) : null}
</>
))
<Button
type="collection"
onClick={() => this.toggle('collection', item.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('modals.main.marketplace.explore_collection')}
iconPlacement="right"
/>
</div>
) : null,
)
) : (
<Items
type={this.props.type}
items={this.state.items}
collection={
this.state.collections[Math.floor(Math.random() * this.state.collections.length)] ||
[]
}
collection={this.state.displayedCollection}
onCollection={this.state.collection}
toggleFunction={(input) => this.toggle('item', input)}
collectionFunction={(input) => this.toggle('collection', input)}

View File

@@ -1,8 +1,8 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { MdEmail, MdContactPage } from 'react-icons/md';
import { FaDiscord, FaTwitter } from 'react-icons/fa';
import { SiGithubsponsors, SiOpencollective } from 'react-icons/si';
import { FaDiscord } from 'react-icons/fa';
import { SiGithubsponsors, SiOpencollective, SiX } from 'react-icons/si';
import { BiDonateHeart } from 'react-icons/bi';
import { Tooltip, Button } from 'components/Elements';
@@ -215,9 +215,9 @@ class About extends PureComponent {
/>
<Button
type="linkIconButton"
href={'https://twitter.com/' + variables.constants.TWITTER_HANDLE}
icon={<FaTwitter />}
tooltipTitle="Twitter"
href={'https://x.com/' + variables.constants.TWITTER_HANDLE}
icon={<SiX />}
tooltipTitle="X (Twitter)"
/>
<Button
type="linkIconButton"

View File

@@ -1,6 +1,6 @@
import { memo } from 'react';
import { FaDiscord, FaTwitter } from 'react-icons/fa';
import { SiGithubsponsors, SiOpencollective } from 'react-icons/si';
import { FaDiscord } from 'react-icons/fa';
import { SiGithubsponsors, SiOpencollective, SiX } from 'react-icons/si';
function QuicklinksSkeleton() {
return (
@@ -10,7 +10,7 @@ function QuicklinksSkeleton() {
<FaDiscord />
</div>
<div>
<FaTwitter />
<SiX />
</div>
<div>
<SiGithubsponsors />

View File

@@ -154,7 +154,7 @@ class Quote extends PureComponent {
const metadata = authorImagePage?.imageinfo?.[0]?.extmetadata;
const license = metadata?.LicenseShortName;
const photographer =
this.stripHTML(metadata.Attribution?.value || metadata.Artist?.value || '').replace(/ \(talk\)/, '') || // talk page link (if applicable) is only removed for English
this.stripHTML(metadata?.Attribution?.value || metadata?.Artist?.value || '').replace(/ \(talk\)/, '') || // talk page link (if applicable) is only removed for English
'Unknown';
authorimglicense = `© ${photographer}. ${license.value}`;
authorimglicense = authorimglicense.replace(/copyright\s/i, '').replace(/©\s©\s/, '© ');
@@ -462,8 +462,8 @@ class Quote extends PureComponent {
) : (
<div className="author-content whileLoading" ref={this.quoteauthor}>
{/* these are placeholders for skeleton and as such don't need translating */}
<span className="title">loading</span>
<span className="subtitle">loading</span>
<span className="title pulse">loading</span>
<span className="subtitle pulse">loading</span>
</div>
)}
{(this.state.authorOccupation !== 'Unknown' && this.state.authorlink !== null) ||

View File

@@ -3,6 +3,8 @@ import { PureComponent } from 'react';
import WeatherIcon from './components/WeatherIcon';
import Expanded from './components/Expanded';
import WeatherSkeleton from './components/WeatherSkeleton';
import EventBus from 'utils/eventbus';
@@ -42,12 +44,13 @@ class WeatherWidget extends PureComponent {
}
render() {
if (this.state.done === false) {
return <div className="weather"></div>;
}
const weatherType = localStorage.getItem('weatherType') || 1;
if (this.state.done === false) {
return <WeatherSkeleton weatherType={weatherType} />;
}
if (!this.state.weather) {
return (
<div className="weather">

View File

@@ -0,0 +1,49 @@
import { memo } from 'react';
function WeatherSkeleton({ weatherType }) {
return (
<div className="weather skeleton">
<div className="weatherCore">
<div className="iconAndTemps">
<div className="weathericon">
<div className="mainSkeletonIcon pulse"></div>
<span className="pulse">20C</span>
</div>
{weatherType >= 2 && (
<span className="minmax">
<span className="subtitle pulse">min</span>
<span className="subtitle pulse">max</span>
</span>
)}
</div>
{weatherType >= 2 && (
<div className="extra-info">
<span className="pulse">feels like x</span>
<span className="loc pulse">location</span>
</div>
)}
</div>
{weatherType >= 3 && (
<div className="weatherExpandedInfo">
<span className="subtitle pulse">extra information</span>
<div className="weatherExpandedInfoItems">
<div className="infoItemSkeleton">
<div className="smallSkeletonIcon pulse"></div>
<span className="loc pulse">location</span>
</div>
<div className="infoItemSkeleton">
<div className="smallSkeletonIcon pulse"></div>
<span className="loc pulse">location</span>
</div>
<div className="infoItemSkeleton">
<div className="smallSkeletonIcon pulse"></div>
<span className="loc pulse">location</span>
</div>
</div>
</div>
)}
</div>
);
}
export default memo(WeatherSkeleton);

View File

@@ -135,3 +135,63 @@
}
}
}
.weather.skeleton {
.iconAndTemps {
gap: 10px;
}
.weatherCore {
gap: 10px;
}
.weathericon {
gap: 10px;
}
.minmax {
max-width: fit-content;
background: transparent !important;
.subtitle {
margin-bottom: 5px;
}
}
.weatherExpandedInfoItems {
padding-top: 10px;
}
.mainSkeletonIcon {
width: 70px;
height: 70px;
border-radius: 100%;
}
.infoItemSkeleton {
display: flex;
flex-flow: row;
gap: 5px;
align-items: center;
.subtitle {
font-size: 18px;
}
}
.smallSkeletonIcon {
width: 20px;
height: 20px;
border-radius: 100%;
}
.title,
span {
color: transparent;
width: 100px;
}
.subtitle {
color: transparent;
width: 50px;
}
}

View File

@@ -530,6 +530,8 @@
"information": "Information",
"last_updated": "Last Updated",
"description": "Description",
"details": "Details",
"more_from_curator": "More from {name}",
"show_more": "Show More",
"show_less": "Show Less",
"show_all": "Show All",
@@ -537,7 +539,7 @@
"no_images": "No. Images",
"no_quotes": "No. Quotes",
"version": "Version",
"author": "Author",
"created_by": "Created by",
"part_of": "Part of",
"explore": "Explore",
"buttons": {

View File

@@ -202,3 +202,42 @@ body {
}
}
}
// credit to Kendrick Arnett https://codepen.io/kendrick/pen/WxNwdE
.light {
.pulse {
height: 100%;
width: 100%;
background: linear-gradient(-90deg, #efefef 0%, #cccccc 50%, #efefef 100%);
background-size: 400% 400%;
animation: pulse 1.2s ease-in-out infinite;
@keyframes pulse {
0% {
background-position: 0% 0%;
}
100% {
background-position: -135% 0%;
}
}
}
}
.dark {
.pulse {
height: 100%;
width: 100%;
background: linear-gradient(-90deg, #000000 0%, rgb(83, 83, 83) 50%, #000000 100%);
background-size: 400% 400%;
animation: pulse 1.2s ease-in-out infinite;
@keyframes pulse {
0% {
background-position: 0% 0%;
}
100% {
background-position: -135% 0%;
}
}
}
}

View File

@@ -129,6 +129,7 @@ export default defineConfig(({ command, mode }) => {
'@': path.resolve(__dirname, './src'),
i18n: path.resolve(__dirname, './src/i18n'),
components: path.resolve(__dirname, './src/components'),
assets: path.resolve(__dirname, './src/assets'),
config: path.resolve(__dirname, './src/config'),
features: path.resolve(__dirname, './src/features'),
lib: path.resolve(__dirname, './src/lib'),