mirror of
https://github.com/mue/mue.git
synced 2026-06-05 23:45:53 +02:00
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:
@@ -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'),
|
||||
|
||||
@@ -395,6 +395,10 @@ p.author {
|
||||
|
||||
.subtitle {
|
||||
color: #ccc !important;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -53,6 +53,7 @@ class BackgroundOptions extends PureComponent {
|
||||
}
|
||||
|
||||
updateAPI(e) {
|
||||
localStorage.setItem('nextImage', null);
|
||||
if (e === 'mue') {
|
||||
this.setState({
|
||||
backgroundCategories: this.state.backgroundCategoriesOG,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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) ||
|
||||
|
||||
@@ -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">
|
||||
|
||||
49
src/features/weather/components/WeatherSkeleton.jsx
Normal file
49
src/features/weather/components/WeatherSkeleton.jsx
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user