refactor(marketplace): WIP implementation of collection

This commit is contained in:
alexsparkes
2024-06-18 15:12:09 +01:00
parent 3f2f3fdfa3
commit 9c900c6484
13 changed files with 1344 additions and 1146 deletions

View File

@@ -208,8 +208,8 @@ const TabNavbar = ({ modalClose }) => {
key={tab.id}
onClick={() => changeTab(tab.id)}
className={`${
activeTab === tab.id ? '' : 'hover:text-white/60'
} flex flex-row gap-2 items-center relative rounded-sm px-3 py-1.5 text-sm text-white outline-sky-400 transition focus-visible:outline-2`}
activeTab === tab.id ? '' : 'hover:text-white/70'
} transition-all duration-800 ease-in-out flex flex-row gap-2 items-center relative rounded-sm px-3 py-1.5 text-sm text-white outline-sky-400 transition focus-visible:outline-2`}
style={{
WebkitTapHighlightColor: 'transparent',
}}

View File

@@ -81,60 +81,61 @@
padding: 5px 10px;
}
}
.itemPage {
table {
border-collapse: separate;
border-radius: 10px;
margin-top: 20px;
table {
border-collapse: separate;
border-radius: 10px;
margin-top: 20px;
@include themed {
box-shadow: 0 0 0 1px t($modal-sidebarActive);
padding: 0;
border: 0;
outline: none;
}
tr:first-child {
@include themed {
border-radius: t($borderRadius);
color: t($subColor);
box-shadow: 0 0 0 1px t($modal-sidebarActive);
padding: 0;
border: 0;
outline: none;
}
letter-spacing: 2px;
th {
padding: 20px;
}
}
td {
padding: 15px;
}
tr {
th:last-child {
display: grid;
place-items: center;
}
}
::placeholder {
@include themed {
color: t($subColor);
}
}
tr:not(:first-child) {
@include themed {
background: t($modal-secondaryColour);
}
textarea {
width: 90%;
margin: 10px;
tr:first-child {
@include themed {
color: t($color);
border-radius: t($borderRadius);
color: t($subColor);
}
letter-spacing: 2px;
th {
padding: 20px;
}
}
td {
padding: 15px;
}
tr {
th:last-child {
display: grid;
place-items: center;
}
}
::placeholder {
@include themed {
color: t($subColor);
}
}
tr:not(:first-child) {
@include themed {
background: t($modal-secondaryColour);
}
textarea {
width: 90%;
margin: 10px;
@include themed {
color: t($color);
}
}
}
}

View File

@@ -9,7 +9,9 @@ export const useMarketData = () => useContext(MarketDataContext);
export const MarketplaceDataProvider = ({ children }) => {
const [done, setDone] = useState(false);
const [items, setItems] = useState([]);
const [collections, setCollections] = useState([]);
const [selectedItem, setSelectedItem] = useState(null);
let numOfRequests = 0;
const controller = new AbortController();
const getItems = async (type = 'all') => {
@@ -28,10 +30,32 @@ export const MarketplaceDataProvider = ({ children }) => {
return;
}
//console.log(data);
numOfRequests++;
console.log("Request number: ", numOfRequests);
setItems(sortItems(data, 'z-a'));
setDone(true);
};
const getCollections = async () => {
setDone(false);
const { data } = await (
await fetch(`${variables.constants.API_URL}/marketplace/collections`, {
signal: controller.signal,
})
).json();
if (controller.signal.aborted === true) {
return;
}
console.log(data);
setCollections(data);
setDone(true);
}
const getItemData = async (itemType, itemName) => {
const response = await fetch(
`${variables.constants.API_URL}/marketplace/item/${itemType}/${itemName}`,
@@ -46,7 +70,7 @@ export const MarketplaceDataProvider = ({ children }) => {
});
};
return (
<MarketDataContext.Provider value={{ done, items, selectedItem, getItems, getItemData, setSelectedItem }}>
<MarketDataContext.Provider value={{ done, items, selectedItem, getItems, getCollections, getItemData, setSelectedItem, collections }}>
{children}
</MarketDataContext.Provider>
);

View File

@@ -2,29 +2,30 @@ import { MdOutlineArrowForward, MdOutlineOpenInNew } from 'react-icons/md';
import { Button } from 'components/Elements';
import variables from 'config/variables';
const Collection = ({ collection, collectionFunction }) => {
const { news, background_colour, img, display_name, description } = collection;
const Collection = ({ collections, collectionFunction }) => {
const randomIndex = Math.floor(Math.random() * collections.length);
const collection = collections[randomIndex];
const getStyle = () => {
if (news) {
return { backgroundColor: background_colour };
if (collection?.news) {
return { backgroundColor: collection?.background_colour };
}
return {
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${img}')`,
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${collection?.img}')`,
};
};
return (
<div className="collection" style={getStyle()}>
<div className="content">
<span className="title">{display_name} using component</span>
<span className="subtitle">{description ? description.substr(0, 75) : ''}</span>
<span className="title">{collection?.display_name}</span>
<span className="subtitle">{collection?.description ? collection?.description.substr(0, 75) : ''}</span>
</div>
{collection.news === true ? (
{collection?.news === true ? (
<a
className="btn-collection"
href={collection.news_link}
href={collection?.news_link}
target="_blank"
rel="noopener noreferrer"
>
@@ -33,7 +34,7 @@ const Collection = ({ collection, collectionFunction }) => {
) : (
<Button
type="collection"
onClick={() => collectionFunction(collection.name)}
onClick={() => collectionFunction(collection?.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('marketplace:explore_collection')}
iconPlacement={'right'}

View File

@@ -1,191 +1,148 @@
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 { motion } from 'framer-motion';
import { useState } from 'react';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
import { MdOpenInNew } from "react-icons/md";
import { Button } from 'components/Elements';
function ItemCard({ item, type, onCollection, isCurator, cardStyle }) {
const { getItemData, selectedItem, setSelectedItem } = useMarketData();
const { setSubTab } = useTab();
function filterItems(item, filter) {
const lowerCaseFilter = filter.toLowerCase();
return (
item.name?.toLowerCase().includes(lowerCaseFilter) ||
filter === '' ||
item.author?.toLowerCase().includes(lowerCaseFilter) ||
item.type?.toLowerCase().includes(lowerCaseFilter)
);
}
function ItemCard({ item, toggleFunction, type, onCollection, isCurator }) {
item._onCollection = onCollection;
return (
<motion.div
whileHover={{ y: -10 }}
transition={{ type: 'spring', stiffness: 150, damping: 15, mass: 0.5 }}
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;
}}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
{!isCurator ? (
<span className="card-subtitle">
{variables.getMessage('marketplace:by', { author: item.author })}
</span>
) : (
''
)}
{type === 'all' && !onCollection ? (
<span className="card-type">
{variables.getMessage('marketplace:' + item.type)}
</span>
) : null}
</div>
</motion.div>
);
const SelectItem = () => {
console.log('Item selected: ', item.display_name);
console.log('Item type: ', item.type);
getItemData(item.type, item.name).then((data) => {
console.log('Selected item: ', data);
//setSelectedItem(data);
setSubTab(data.display_name);
});
};
const getName = (name) => {
const nameMappings = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return nameMappings[name] || name;
};
switch (cardStyle) {
case 'list':
return (
<tr key={item.name} className="py-5 hover:bg-white/5 rounded-lg cursor-pointer" onClick={SelectItem}>
<td className="flex flex-row items-center gap-10 py-3">
<img
className="w-10 h-10 rounded-lg"
alt="icon"
draggable={false}
src={item.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
{item.display_name}
</td>
<td>{variables.getMessage('marketplace:' + getName(item.type)) || 'marketplace'}</td>
<td>{item.author}</td>
<td><MdOpenInNew /></td>
</tr>
);
default:
return (
<motion.div
whileHover={{ y: -10 }}
transition={{ type: 'spring', stiffness: 150, damping: 15, mass: 0.5 }}
className="item"
onClick={SelectItem}
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;
}}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
{!isCurator ? (
<span className="card-subtitle">
{variables.getMessage('marketplace:by', { author: item.author })}
</span>
) : (
''
)}
{type === true && !onCollection ? (
<span className="card-type">{variables.getMessage('marketplace:' + item.type)}</span>
) : null}
</div>
</motion.div>
);
}
}
function Items({
isCurator,
type,
items,
collection,
toggleFunction,
collectionFunction,
onCollection,
filter,
moreByCreator,
showCreateYourOwn,
}) {
const shouldShowCollection =
((collection && !onCollection && (filter === null || filter === '')) ||
(type === 'collections' && !onCollection && (filter === null || filter === ''))) &&
type !== 'preset_settings';
const NewItems = ({ items, view }) => {
const { setSubTab } = useTab();
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('marketplace:learn_more')} <MdOutlineOpenInNew />
</a>
) : (
<Button
type="collection"
onClick={() => collectionFunction(collection?.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('marketplace:explore_collection')}
iconPlacement={'right'}
/>
)}
</div>
)}
<div className="flex flex-row gap-2 my-3">
<div
onClick={() => {
this.setState({ type: 'all' });
this.getItems();
}}
className="transition-all duration-200 bg-white rounded-full px-6 py-2 text-base text-black"
>
All
</div>
<div
onClick={() => {
this.setState({ type: 'photo_packs' });
this.getItems();
}}
className="transition-all duration-200 bg-[#333] hover:bg-[#222222] cursor-pointer rounded-full px-6 py-2 text-base text-white"
>
Photo Packs
</div>
<div
onClick={() => {
this.setState({ type: 'quote_packs' });
this.getItems();
}}
className="transition-all duration-200 bg-[#333] hover:bg-[#222222] cursor-pointer rounded-full px-6 py-2 text-base text-white"
>
Quote Packs
</div>
</div>
<div className={`items ${moreByCreator ? 'creatorItems' : ''}`}>
{items
?.filter((item) => filterItems(item, filter))
.map((item, index) => (
switch (view) {
case 'list':
return (
<table className="w-full">
<tbody>
<tr>
<th>Name</th>
<th>Type</th>
<th>{variables.getMessage('settings:sections.quote.author')}</th>
<th></th>
</tr>
{items.map((item, index) => (
<ItemCard
onClick={() => setSubTab(item.name)}
item={item}
type={true}
key={index}
cardStyle="list"
/>
))}
</tbody>
</table>
);
default:
return (
<div className="items">
{items.map((item, index) => (
<ItemCard
isCurator={isCurator}
onClick={() => setSubTab(item.name)}
item={item}
toggleFunction={toggleFunction}
type={type}
onCollection={onCollection}
type={true}
key={index}
cardStyle="card"
/>
))}
</div>
<div className="loader"></div>
{!onCollection && showCreateYourOwn ? (
<div className="createYourOwn">
<MdAutoFixHigh />
<span className="title">{variables.getMessage('marketplace:cant_find')}</span>
<span className="subtitle">
{variables.getMessage('marketplace:knowledgebase_one') + ' '}
<a
className="link"
target="_blank"
href={variables.constants.KNOWLEDGEBASE}
rel="noreferrer"
>
{variables.getMessage('marketplace:knowledgebase_two')}
</a>
{' ' + variables.getMessage('marketplace:knowledgebase_three')}
</span>
</div>
) : null}
</>
);
}
);
}
};
const MemoizedItems = memo(Items);
export { MemoizedItems as default, MemoizedItems as Items };
export { NewItems };

View File

@@ -1,92 +0,0 @@
import variables from 'config/variables';
import { motion } from 'framer-motion';
import { useState } from 'react';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
function ItemCard({ item, onClick, type, onCollection, isCurator }) {
const { getItemData, selectedItem, setSelectedItem } = useMarketData();
const { setSubTab } = useTab();
item._onCollection = onCollection;
const SelectItem = () => {
console.log("Item selected: ", item.display_name);
console.log("Item type: ", item.type);
getItemData(item.type, item.name).then((data) => {
console.log("Selected item: ", data);
//setSelectedItem(data);
setSubTab(data.display_name);
});
}
return (
<motion.div
whileHover={{ y: -10 }}
transition={{ type: 'spring', stiffness: 150, damping: 15, mass: 0.5 }}
className="item"
onClick={SelectItem}
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;
}}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
{!isCurator ? (
<span className="card-subtitle">
{variables.getMessage('marketplace:by', { author: item.author })}
</span>
) : (
''
)}
{type === 'all' && !onCollection ? (
<span className="card-type">
{variables.getMessage('marketplace:' + item.type)}
</span>
) : null}
</div>
</motion.div>
);
}
const NewItems = ({ items }) => {
const { setSubTab } = useTab();
return (
<div className="items">
{items.map((item, index) => (
<ItemCard
onClick={() => setSubTab(item.name)}
//isCurator={isCurator}
item={item}
//toggleFunction={toggleFunction}
//type={type}
//onCollection={onCollection}
key={index}
/>
))}
</div>
);
};
export { NewItems };

View File

@@ -0,0 +1,191 @@
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 { motion } from 'framer-motion';
import { Button } from 'components/Elements';
function filterItems(item, filter) {
const lowerCaseFilter = filter.toLowerCase();
return (
item.name?.toLowerCase().includes(lowerCaseFilter) ||
filter === '' ||
item.author?.toLowerCase().includes(lowerCaseFilter) ||
item.type?.toLowerCase().includes(lowerCaseFilter)
);
}
function ItemCard({ item, toggleFunction, type, onCollection, isCurator }) {
item._onCollection = onCollection;
return (
<motion.div
whileHover={{ y: -10 }}
transition={{ type: 'spring', stiffness: 150, damping: 15, mass: 0.5 }}
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;
}}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
{!isCurator ? (
<span className="card-subtitle">
{variables.getMessage('marketplace:by', { author: item.author })}
</span>
) : (
''
)}
{type === 'all' && !onCollection ? (
<span className="card-type">
{variables.getMessage('marketplace:' + item.type)}
</span>
) : null}
</div>
</motion.div>
);
}
function Items({
isCurator,
type,
items,
collection,
toggleFunction,
collectionFunction,
onCollection,
filter,
moreByCreator,
showCreateYourOwn,
}) {
const shouldShowCollection =
((collection && !onCollection && (filter === null || filter === '')) ||
(type === 'collections' && !onCollection && (filter === null || filter === ''))) &&
type !== 'preset_settings';
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('marketplace:learn_more')} <MdOutlineOpenInNew />
</a>
) : (
<Button
type="collection"
onClick={() => collectionFunction(collection?.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('marketplace:explore_collection')}
iconPlacement={'right'}
/>
)}
</div>
)}
<div className="flex flex-row gap-2 my-3">
<div
onClick={() => {
this.setState({ type: 'all' });
this.getItems();
}}
className="transition-all duration-200 bg-white rounded-full px-6 py-2 text-base text-black"
>
All
</div>
<div
onClick={() => {
this.setState({ type: 'photo_packs' });
this.getItems();
}}
className="transition-all duration-200 bg-[#333] hover:bg-[#222222] cursor-pointer rounded-full px-6 py-2 text-base text-white"
>
Photo Packs
</div>
<div
onClick={() => {
this.setState({ type: 'quote_packs' });
this.getItems();
}}
className="transition-all duration-200 bg-[#333] hover:bg-[#222222] cursor-pointer rounded-full px-6 py-2 text-base text-white"
>
Quote Packs
</div>
</div>
<div className={`items ${moreByCreator ? 'creatorItems' : ''}`}>
{items
?.filter((item) => filterItems(item, filter))
.map((item, index) => (
<ItemCard
isCurator={isCurator}
item={item}
toggleFunction={toggleFunction}
type={type}
onCollection={onCollection}
key={index}
/>
))}
</div>
<div className="loader"></div>
{!onCollection && showCreateYourOwn ? (
<div className="createYourOwn">
<MdAutoFixHigh />
<span className="title">{variables.getMessage('marketplace:cant_find')}</span>
<span className="subtitle">
{variables.getMessage('marketplace:knowledgebase_one') + ' '}
<a
className="link"
target="_blank"
href={variables.constants.KNOWLEDGEBASE}
rel="noreferrer"
>
{variables.getMessage('marketplace:knowledgebase_two')}
</a>
{' ' + variables.getMessage('marketplace:knowledgebase_three')}
</span>
</div>
) : null}
</>
);
}
const MemoizedItems = memo(Items);
export { MemoizedItems as default, MemoizedItems as Items };

View File

@@ -5,8 +5,8 @@ import { toast } from 'react-toastify';
import Modal from 'react-modal';
import { SideloadFailedModal } from '../components/Elements/SideloadFailedModal/SideloadFailedModal';
import ItemPage from './ItemPage';
import Items from '../components/Items/Items';
import ItemPage from './oldItemPage';
import Items from '../components/Items/OldItems';
import { Dropdown, FileUpload } from 'components/Form/Settings';
import { Header, CustomActions } from 'components/Layout/Settings';
import { Button } from 'components/Elements';

View File

@@ -3,8 +3,8 @@ import { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import { MdWifiOff, MdLocalMall, MdOutlineArrowForward, MdLibraryAdd } from 'react-icons/md';
import ItemPage from './ItemPage';
import Items from '../components/Items/Items';
import ItemPage from './oldItemPage';
import Items from '../components/Items/OldItems';
import { Header } from 'components/Layout/Settings';
import { Button } from 'components/Elements';

View File

@@ -1,6 +1,8 @@
import variables from 'config/variables';
import { PureComponent, Fragment } from 'react';
import { toast } from 'react-toastify';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { useEffect, useState } from 'react';
import { ShareModal } from 'components/Elements';
import {
MdIosShare,
MdFlag,
@@ -14,465 +16,343 @@ import {
MdClose,
MdLibraryAdd,
} from 'react-icons/md';
import Modal from 'react-modal';
import { Header } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
import { install, uninstall } from 'utils/marketplace';
import { Carousel } from '../components/Elements/Carousel';
import { ShareModal } from 'components/Elements';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { Items } from '../components/Items/Items';
import Modal from 'react-modal';
import Markdown from 'markdown-to-jsx';
import { Button } from 'components/Elements';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
import { Carousel } from '../components/Elements/Carousel';
class ItemPage extends PureComponent {
constructor(props) {
super(props);
console.log(this.props)
this.state = {
showUpdateButton:
this.props.data.local.installed === true && this.props.data.local.version !== this.props.data.version,
shareModal: false,
count: 5,
moreByCurator: [],
};
this.buttons = {
uninstall: (
<Button
type="settings"
onClick={() => this.manage('uninstall')}
icon={<MdClose />}
label={variables.getMessage('marketplace:product.buttons.remove')}
/>
),
install: (
<Button
type="settings"
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
label={variables.getMessage('marketplace:product.buttons.addtomue')}
/>
),
};
}
const ItemPage = () => {
const { subTab } = useTab();
const controller = new AbortController();
const [count, setCount] = useState(5);
const [item, setItemData] = useState(null);
const [shareModal, setShareModal] = useState(false);
const { selectedItem } = useMarketData();
async getCurator(name) {
try {
const { data } = await (
await fetch(`${variables.constants.API_URL}/marketplace/curator/${name}`)
).json();
this.setState({
moreByCurator: data.items,
});
} catch (e) {
console.error(e);
}
}
componentDidMount() {
this.getCurator(this.props.data.author);
document.querySelector('#modal').scrollTop = 0;
this.setState({
button: this.props.data.local.installed ? this.buttons.uninstall : this.buttons.install,
})
}
updateAddon() {
uninstall(this.props.data.type, this.props.data.display_name);
install(this.props.data.type, this.props.data);
toast(variables.getMessage('toasts.updated'));
this.setState({
showUpdateButton: false,
});
}
incrementCount(type) {
const newCount =
this.state.count !== this.props.data.data[type].length
? this.props.data.data[type].length
: 5;
this.setState({ count: newCount });
}
getName(name) {
const getName = (name) => {
const nameMappings = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return nameMappings[name] || name;
}
};
manage(type) {
if (type === 'install') {
install(this.props.data.type, this.props.data.data);
} else {
uninstall(this.props.data.type,this.props.data.display_name);
}
const moreInfoItem = (icon, header, text) => (
<div className="infoItem">
{icon}
<div className="text">
<span className="header">{header}</span>
<span>{text}</span>
</div>
</div>
);
toast(variables.getMessage('toasts.' + type + 'ed'));
this.setState({
button: type === 'install' ? this.buttons.uninstall : this.buttons.install,
});
variables.stats.postEvent(
'marketplace-item',
`${this.state.item.display_name} ${type === 'install' ? 'installed' : 'uninstalled'}`,
);
variables.stats.postEvent('marketplace', type === 'install' ? 'Install' : 'Uninstall');
}
render() {
const locale = localStorage.getItem('language');
const shortLocale = locale.includes('_') ? locale.split('_')[0] : locale;
let languageNames = new Intl.DisplayNames([shortLocale], { type: 'language' });
const convertedType = (() => {
const map = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return map[this.props.data.data.type];
})();
const moreByCurator = this.state.moreByCurator
.filter((item) => item.type === convertedType && item.name !== this.props.data.data.name)
.sort(() => 0.5 - Math.random())
.slice(0, 3);
if (!this.props.data.display_name) {
return null;
}
// prevent console error
let iconsrc = this.props.data.icon;
if (!this.props.data.icon) {
iconsrc = null;
}
let updateButton;
if (this.state.showUpdateButton) {
updateButton = (
<Fragment key="update">
<Button
type="settings"
onClick={() => this.updateAddon()}
label={variables.getMessage('addons:product.buttons.update_addon')}
/>
</Fragment>
);
}
const itemWarning = () => {
const template = (message) => (
<div className="itemWarning">
<MdOutlineWarning />
<div className="text">
<span className="header">Warning</span>
<span>{message}</span>
</div>
</div>
);
if (this.props.data.data.sideload === true) {
return template(variables.getMessage('marketplace:product.sideload_warning'));
}
if (this.props.data.data.image_api === true) {
return template(variables.getMessage('marketplace:product.third_party_api'));
}
if (this.props.data.data.language !== undefined && this.props.data.data.language !== null) {
if (shortLocale !== this.props.data.data.language) {
return template(variables.getMessage('marketplace:product.not_in_language'));
}
}
return null;
};
const moreInfoItem = (icon, header, text) => (
<div className="infoItem">
{icon}
const itemWarning = () => {
const template = (message) => (
<div className="itemWarning">
<MdOutlineWarning />
<div className="text">
<span className="header">{header}</span>
<span>{text}</span>
<span className="header">Warning</span>
<span>{message}</span>
</div>
</div>
);
let dateObj, formattedDate;
if (this.props.data.data.updated_at) {
dateObj = new Date(this.props.data.data.updated_at);
formattedDate = new Intl.DateTimeFormat(shortLocale, {
year: 'numeric',
month: 'long',
day: '2-digit',
}).format(dateObj);
/*if (this.props.data.data.sideload === true) {
return template(variables.getMessage('marketplace:product.sideload_warning'));
}
if (this.props.data.data.image_api === true) {
return template(variables.getMessage('marketplace:product.third_party_api'));
}
if (this.props.data.data.language !== undefined && this.props.data.data.language !== null) {
if (shortLocale !== this.props.data.data.language) {
return template(variables.getMessage('marketplace:product.not_in_language'));
}
}*/
return null;
};
const ItemDetails = () => {
return (
<>
<Modal
closeTimeoutMS={300}
isOpen={this.state.shareModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
onRequestClose={() => this.setState({ shareModal: false })}
>
<ShareModal
data={variables.constants.API_URL + '/marketplace/share/' + btoa(this.props.data.slug)}
modalClose={() => this.setState({ shareModal: false })}
/>
</Modal>
<Header
title={
this.props.addons
? variables.getMessage('addons:added')
: this.props.data.onCollection && this.props.data.data.in_collections?.length > 0
? this.props.data.data.in_collections[0].display_name
: variables.getMessage('modals.main.navbar.marketplace')
}
secondaryTitle={
this.props.data.data.sideload
? this.props.data.data.name
: this.props.data.data.display_name
}
report={false}
goBack={this.props.toggleFunction}
/>
<div className="itemPage">
<div className="itemShowcase">
<div className="subHeader">
{moreInfoItem(
<MdAccountCircle />,
variables.getMessage('marketplace:product.created_by'),
this.props.data.author,
)}
{itemWarning()}
</div>
{this.props.data.data.photos && (
<div className="carousel">
<div className="carousel_container">
<Carousel data={this.props.data.data.photos} />
</div>
</div>
<div className="marketplaceDescription">
<span className="title">{variables.getMessage('marketplace:product.details')}</span>
<div className="moreInfo">
{selectedItem?.updated_at &&
moreInfoItem(
<MdCalendarMonth />,
variables.getMessage('marketplace:product.updated_at'),
formattedDate,
)}
{this.props.data.data.settings && (
<img
alt="product"
draggable={false}
src={iconsrc}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
{selectedItem?.quotes &&
moreInfoItem(
<MdFormatQuote />,
variables.getMessage('marketplace:product.no_quotes'),
selectedItem?.quotes.length,
)}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('marketplace:product.description')}
{selectedItem?.photos &&
moreInfoItem(
<MdImage />,
variables.getMessage('marketplace:product.no_images'),
selectedItem?.photos.length,
)}
{selectedItem?.quotes && selectedItem?.language
? moreInfoItem(
<MdTranslate />,
variables.getMessage('settings:sections.language.title'),
languageNames.of(selectedItem?.language),
)
: null}
{moreInfoItem(
<MdStyle />,
variables.getMessage('settings:sections.background.type.title'),
variables.getMessage('marketplace:' + getName(selectedItem?.type)) || 'marketplace',
)}
</div>
</div>
);
};
const ItemShowcase = () => {
switch (selectedItem?.type) {
case 'quotes':
return (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('settings:sections.quote.title')}</th>
<th>{variables.getMessage('settings:sections.quote.author')}</th>
</tr>
{selectedItem?.quotes.slice(0, count).map((quote, index) => (
<tr key={index}>
<td>{quote.quote}</td>
<td>{quote.author}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => count === selectedItem?.quotes.length}>
{count !== selectedItem?.quotes.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
<Markdown>{this.props.data.data.description}</Markdown>
</div>
{this.props.data.data.quotes && (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('settings:sections.quote.title')}</th>
<th>{variables.getMessage('settings:sections.quote.author')}</th>
</>
);
case 'settings':
return (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('marketplace:product.setting')}</th>
<th>{variables.getMessage('marketplace:product.value')}</th>
</tr>
{Object.entries(selectedItem?.settings)
.slice(0, count)
.map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
{this.props.data.data.quotes.slice(0, this.state.count).map((quote, index) => (
<tr key={index}>
<td>{quote.quote}</td>
<td>{quote.author}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => this.incrementCount('quotes')}>
{this.state.count !== this.props.data.data.quotes.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
</div>
</>
)}
{this.props.data.data.settings && (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('marketplace:product.setting')}</th>
<th>{variables.getMessage('marketplace:product.value')}</th>
</tr>
{Object.entries(this.props.data.data.settings)
.slice(0, this.state.count)
.map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => this.incrementCount('settings')}>
{this.state.count !== this.props.data.data.settings.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
</div>
</>
)}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('marketplace:product.details')}
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => this.incrementCount('settings')}>
{count !== selectedItem?.settings.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
<div className="moreInfo">
{this.props.data.data.updated_at &&
moreInfoItem(
<MdCalendarMonth />,
variables.getMessage('marketplace:product.updated_at'),
formattedDate,
)}
{this.props.data.data.quotes &&
moreInfoItem(
<MdFormatQuote />,
variables.getMessage('marketplace:product.no_quotes'),
this.props.data.data.quotes.length,
)}
{this.props.data.data.photos &&
moreInfoItem(
<MdImage />,
variables.getMessage('marketplace:product.no_images'),
this.props.data.data.photos.length,
)}
{this.props.data.data.quotes && this.props.data.data.language
? moreInfoItem(
<MdTranslate />,
variables.getMessage('settings:sections.language.title'),
languageNames.of(this.props.data.data.language),
)
: null}
{moreInfoItem(
<MdStyle />,
variables.getMessage('settings:sections.background.type.title'),
variables.getMessage(
'marketplace:' + this.getName(this.props.data.data.type),
) || 'marketplace',
)}
</div>
</div>
</div>
<div
className="itemInfo"
style={{
backgroundImage: `url("${this.props.data.data.icon_url}")`,
</>
);
default:
return null;
}
};
const sidePanel = () => {
return (
<div
className="itemInfo"
style={{
backgroundImage: `url("${selectedItem?.icon_url || placeholderIcon}")`,
}}
>
<div className="front">
<img
className="icon"
alt="icon"
draggable={false}
src={selectedItem?.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
>
<div className="front">
<img
className="icon"
alt="icon"
draggable={false}
src={this.props.data.data.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
{localStorage.getItem('welcomePreview') !== 'true' ? (
<Button
type="settings"
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
label={variables.getMessage('marketplace:product.buttons.addtomue')}
/>
) : (
<p style={{ textAlign: 'center' }}>
{variables.getMessage('marketplace:product.buttons.not_available_preview')}
</p>
)}
{selectedItem?.sideload !== true && (
<div className="iconButtons">
<Button
type="icon"
onClick={() => setShareModal(true)}
icon={<MdIosShare />}
tooltipTitle={variables.getMessage('widgets.quote.share')}
tooltipKey="share"
/>
<Button
type="icon"
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
selectedItem?.display_name.split(' ').join('+'),
'_blank',
)
}
icon={<MdFlag />}
tooltipTitle={variables.getMessage('marketplace:product.buttons.report')}
tooltipKey="report"
/>
{localStorage.getItem('welcomePreview') !== 'true' ? (
this.state.button
) : (
<p style={{ textAlign: 'center' }}>
{variables.getMessage(
'marketplace:product.buttons.not_available_preview',
)}
</p>
)}
{this.props.data.data.sideload !== true && (
<div className="iconButtons">
<Button
type="icon"
onClick={() => this.setState({ shareModal: true })}
icon={<MdIosShare />}
tooltipTitle={variables.getMessage('widgets.quote.share')}
tooltipKey="share"
/>
<Button
type="icon"
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
this.props.data.data.display_name.split(' ').join('+'),
'_blank',
)
}
icon={<MdFlag />}
tooltipTitle={variables.getMessage(
'marketplace:product.buttons.report',
)}
tooltipKey="report"
/>
</div>
)}
{this.props.data.data.in_collections?.length > 0 && (
<div>
<div className="inCollection">
<span className="subtitle">
{variables.getMessage('marketplace:product.part_of')}
</span>
<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>
</div>
)}
</div>
)}
{selectedItem?.in_collections?.length > 0 && (
<div>
<div className="inCollection">
<span className="subtitle">
{variables.getMessage('marketplace:product.part_of')}
</span>
<span
className="title"
/*onClick={() =>
this.props.toggleFunction(
'collection',
selectedItem?.in_collections[0].name,
)
}*/
>
{selectedItem?.in_collections[0].display_name}
</span>
</div>
</div>
)}
</div>
</div>
);
};
const locale = localStorage.getItem('language');
const shortLocale = locale.includes('_') ? locale.split('_')[0] : locale;
let languageNames = new Intl.DisplayNames([shortLocale], { type: 'language' });
let dateObj, formattedDate;
if (selectedItem?.updated_at) {
dateObj = new Date(selectedItem?.updated_at);
formattedDate = new Intl.DateTimeFormat(shortLocale, {
year: 'numeric',
month: 'long',
day: '2-digit',
}).format(dateObj);
}
useEffect(() => {
document.querySelector('#modal').scrollTop = 0;
});
return (
<>
<Modal
closeTimeoutMS={300}
isOpen={shareModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
onRequestClose={() => setShareModal(false)}
>
<ShareModal
data={variables.constants.API_URL + '/marketplace/share/' + btoa(selectedItem?.name)}
modalClose={() => setShareModal(false)}
/>
</Modal>
<div className="itemPage">
<div className="itemShowcase">
<div className="subHeader">
{moreInfoItem(
<MdAccountCircle />,
variables.getMessage('marketplace:product.created_by'),
selectedItem?.author,
)}
{itemWarning()}
</div>
{selectedItem?.photos && (
<div className="carousel">
<div className="carousel_container">
<Carousel data={selectedItem?.photos} />
</div>
</div>
)}
{selectedItem?.settings && selectedItem?.screenshot_url !== null && (
<img
alt="product"
draggable={false}
src={selectedItem?.screenshot_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
)}
<div className="marketplaceDescription">
<span className="title">{variables.getMessage('marketplace:product.description')}</span>
<Markdown>{selectedItem?.description}</Markdown>
</div>
<ItemShowcase />
<ItemDetails />
</div>
{sidePanel()}
</div>
{/*{moreByCurator.length > 1 && (
<div className="moreFromCurator">
<span className="title">
{variables.getMessage('marketplace:product.more_from_curator', {
name: this.props.data.author,
})}
</span>
<div>
<Items
isCurator={true}
type={this.props.data.data.type}
items={moreByCurator}
onCollection={this.state.collection}
toggleFunction={(input) => this.props.toggleFunction('item', input)}
collectionFunction={(input) => this.props.toggleFunction('collection', input)}
filter={''}
moreByCreator={true}
showCreateYourOwn={false}
/>
</div>
</div>
{moreByCurator.length > 1 && (
<div className="moreFromCurator">
<span className="title">
{variables.getMessage('marketplace:product.more_from_curator', {
name: this.props.data.author,
})}
</span>
<div>
<Items
isCurator={true}
type={this.props.data.data.type}
items={moreByCurator}
onCollection={this.state.collection}
toggleFunction={(input) => this.props.toggleFunction('item', input)}
collectionFunction={(input) => this.props.toggleFunction('collection', input)}
filter={''}
moreByCreator={true}
showCreateYourOwn={false}
/>
</div>
</div>
)}
</>
);
}
}
)}*/}
</>
);
};
export { ItemPage as default, ItemPage };

View File

@@ -1,374 +0,0 @@
import variables from 'config/variables';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { useEffect, useState } from 'react';
import { ShareModal } from 'components/Elements';
import {
MdIosShare,
MdFlag,
MdAccountCircle,
MdCalendarMonth,
MdFormatQuote,
MdImage,
MdTranslate,
MdOutlineWarning,
MdStyle,
MdClose,
MdLibraryAdd,
} from 'react-icons/md';
import { Header } from 'components/Layout/Settings';
import Modal from 'react-modal';
import Markdown from 'markdown-to-jsx';
import { Button } from 'components/Elements';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
import { Carousel } from '../components/Elements/Carousel';
const NewItemPage = () => {
const { subTab } = useTab();
const controller = new AbortController();
const [count, setCount] = useState(5);
const [item, setItemData] = useState(null);
const [shareModal, setShareModal] = useState(false);
const { selectedItem } = useMarketData();
const getName = (name) => {
const nameMappings = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return nameMappings[name] || name;
};
const moreInfoItem = (icon, header, text) => (
<div className="infoItem">
{icon}
<div className="text">
<span className="header">{header}</span>
<span>{text}</span>
</div>
</div>
);
const itemWarning = () => {
const template = (message) => (
<div className="itemWarning">
<MdOutlineWarning />
<div className="text">
<span className="header">Warning</span>
<span>{message}</span>
</div>
</div>
);
/*if (this.props.data.data.sideload === true) {
return template(variables.getMessage('marketplace:product.sideload_warning'));
}
if (this.props.data.data.image_api === true) {
return template(variables.getMessage('marketplace:product.third_party_api'));
}
if (this.props.data.data.language !== undefined && this.props.data.data.language !== null) {
if (shortLocale !== this.props.data.data.language) {
return template(variables.getMessage('marketplace:product.not_in_language'));
}
}*/
return null;
};
const ItemDetails = () => {
return (
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('marketplace:product.details')}
</span>
<div className="moreInfo">
{selectedItem?.updated_at &&
moreInfoItem(
<MdCalendarMonth />,
variables.getMessage('marketplace:product.updated_at'),
formattedDate,
)}
{selectedItem?.quotes &&
moreInfoItem(
<MdFormatQuote />,
variables.getMessage('marketplace:product.no_quotes'),
selectedItem.quotes.length,
)}
{selectedItem?.photos &&
moreInfoItem(
<MdImage />,
variables.getMessage('marketplace:product.no_images'),
selectedItem.photos.length,
)}
{selectedItem?.quotes && selectedItem?.language
? moreInfoItem(
<MdTranslate />,
variables.getMessage('settings:sections.language.title'),
languageNames.of(selectedItem.language),
)
: null}
{moreInfoItem(
<MdStyle />,
variables.getMessage('settings:sections.background.type.title'),
variables.getMessage(
'marketplace:' + getName(selectedItem.type),
) || 'marketplace',
)}
</div>
</div>
);
};
const ItemShowcase = () => {
switch (selectedItem.type) {
case 'quotes':
return (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('settings:sections.quote.title')}</th>
<th>{variables.getMessage('settings:sections.quote.author')}</th>
</tr>
{selectedItem.quotes.slice(0, count).map((quote, index) => (
<tr key={index}>
<td>{quote.quote}</td>
<td>{quote.author}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => count === selectedItem.quotes.length}>
{count !== selectedItem.quotes.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
</div>
</>
);
case 'settings':
return (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('marketplace:product.setting')}</th>
<th>{variables.getMessage('marketplace:product.value')}</th>
</tr>
{Object.entries(selectedItem.settings)
.slice(0, count)
.map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => this.incrementCount('settings')}>
{count !== selectedItem.settings.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
</div>
</>
);
default:
return null;
}
};
const locale = localStorage.getItem('language');
const shortLocale = locale.includes('_') ? locale.split('_')[0] : locale;
let languageNames = new Intl.DisplayNames([shortLocale], { type: 'language' });
let dateObj, formattedDate;
if (selectedItem.updated_at) {
dateObj = new Date(selectedItem.updated_at);
formattedDate = new Intl.DateTimeFormat(shortLocale, {
year: 'numeric',
month: 'long',
day: '2-digit',
}).format(dateObj);
}
return (
<>
<Modal
closeTimeoutMS={300}
isOpen={shareModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
onRequestClose={() => setShareModal(false)}
>
<ShareModal
data={variables.constants.API_URL + '/marketplace/share/' + btoa(selectedItem.name)}
modalClose={() => setShareModal(false)}
/>
</Modal>
{/*<Header
title={
this.props.addons
? variables.getMessage('addons:added')
: this.props.data.onCollection && this.props.data.data.in_collections?.length > 0
? this.props.data.data.in_collections[0].display_name
: variables.getMessage('modals.main.navbar.marketplace')
}
secondaryTitle={
this.props.data.data.sideload
? this.props.data.data.name
: this.props.data.data.display_name
}
report={false}
goBack={this.props.toggleFunction}
/>*/}
<div className="itemPage">
<div className="itemShowcase">
<div className="subHeader">
{moreInfoItem(
<MdAccountCircle />,
variables.getMessage('marketplace:product.created_by'),
selectedItem.author,
)}
{itemWarning()}
</div>
{selectedItem.photos && (
<div className="carousel">
<div className="carousel_container">
<Carousel data={selectedItem.photos} />
</div>
</div>
)}
{/*{selectedItem.settings && (
<img
alt="product"
draggable={false}
src={iconsrc}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
)}*/}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('marketplace:product.description')}
</span>
<Markdown>{selectedItem.description}</Markdown>
</div>
<ItemShowcase />
<ItemDetails />
</div>
<div
className="itemInfo"
style={{
backgroundImage: `url("${selectedItem.icon_url || placeholderIcon}")`,
}}
>
<div className="front">
<img
className="icon"
alt="icon"
draggable={false}
src={selectedItem.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
{localStorage.getItem('welcomePreview') !== 'true' ? (
<Button
type="settings"
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
label={variables.getMessage('marketplace:product.buttons.addtomue')}
/>
) : (
<p style={{ textAlign: 'center' }}>
{variables.getMessage(
'marketplace:product.buttons.not_available_preview',
)}
</p>
)}
{selectedItem.sideload !== true && (
<div className="iconButtons">
<Button
type="icon"
onClick={() => setShareModal(true)}
icon={<MdIosShare />}
tooltipTitle={variables.getMessage('widgets.quote.share')}
tooltipKey="share"
/>
<Button
type="icon"
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
this.props.data.data.display_name.split(' ').join('+'),
'_blank',
)
}
icon={<MdFlag />}
tooltipTitle={variables.getMessage(
'marketplace:product.buttons.report',
)}
tooltipKey="report"
/>
</div>
)}
{selectedItem.in_collections?.length > 0 && (
<div>
<div className="inCollection">
<span className="subtitle">
{variables.getMessage('marketplace:product.part_of')}
</span>
<span
className="title"
/*onClick={() =>
this.props.toggleFunction(
'collection',
selectedItem.in_collections[0].name,
)
}*/
>
{selectedItem.in_collections[0].display_name}
</span>
</div>
</div>
)}
</div>
</div>
</div>
{/*{moreByCurator.length > 1 && (
<div className="moreFromCurator">
<span className="title">
{variables.getMessage('marketplace:product.more_from_curator', {
name: this.props.data.author,
})}
</span>
<div>
<Items
isCurator={true}
type={this.props.data.data.type}
items={moreByCurator}
onCollection={this.state.collection}
toggleFunction={(input) => this.props.toggleFunction('item', input)}
collectionFunction={(input) => this.props.toggleFunction('collection', input)}
filter={''}
moreByCreator={true}
showCreateYourOwn={false}
/>
</div>
</div>
)}*/}
</>
);
};
export { NewItemPage as default, NewItemPage };

View File

@@ -0,0 +1,478 @@
import variables from 'config/variables';
import { PureComponent, Fragment } from 'react';
import { toast } from 'react-toastify';
import {
MdIosShare,
MdFlag,
MdAccountCircle,
MdCalendarMonth,
MdFormatQuote,
MdImage,
MdTranslate,
MdOutlineWarning,
MdStyle,
MdClose,
MdLibraryAdd,
} from 'react-icons/md';
import Modal from 'react-modal';
import { Header } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
import { install, uninstall } from 'utils/marketplace';
import { Carousel } from '../components/Elements/Carousel';
import { ShareModal } from 'components/Elements';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { Items } from '../components/Items/OldItems';
import Markdown from 'markdown-to-jsx';
class ItemPage extends PureComponent {
constructor(props) {
super(props);
console.log(this.props)
this.state = {
showUpdateButton:
this.props.data.local.installed === true && this.props.data.local.version !== this.props.data.version,
shareModal: false,
count: 5,
moreByCurator: [],
};
this.buttons = {
uninstall: (
<Button
type="settings"
onClick={() => this.manage('uninstall')}
icon={<MdClose />}
label={variables.getMessage('marketplace:product.buttons.remove')}
/>
),
install: (
<Button
type="settings"
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
label={variables.getMessage('marketplace:product.buttons.addtomue')}
/>
),
};
}
async getCurator(name) {
try {
const { data } = await (
await fetch(`${variables.constants.API_URL}/marketplace/curator/${name}`)
).json();
this.setState({
moreByCurator: data.items,
});
} catch (e) {
console.error(e);
}
}
componentDidMount() {
this.getCurator(this.props.data.author);
document.querySelector('#modal').scrollTop = 0;
this.setState({
button: this.props.data.local.installed ? this.buttons.uninstall : this.buttons.install,
})
}
updateAddon() {
uninstall(this.props.data.type, this.props.data.display_name);
install(this.props.data.type, this.props.data);
toast(variables.getMessage('toasts.updated'));
this.setState({
showUpdateButton: false,
});
}
incrementCount(type) {
const newCount =
this.state.count !== this.props.data.data[type].length
? this.props.data.data[type].length
: 5;
this.setState({ count: newCount });
}
getName(name) {
const nameMappings = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return nameMappings[name] || name;
}
manage(type) {
if (type === 'install') {
install(this.props.data.type, this.props.data.data);
} else {
uninstall(this.props.data.type,this.props.data.display_name);
}
toast(variables.getMessage('toasts.' + type + 'ed'));
this.setState({
button: type === 'install' ? this.buttons.uninstall : this.buttons.install,
});
variables.stats.postEvent(
'marketplace-item',
`${this.state.item.display_name} ${type === 'install' ? 'installed' : 'uninstalled'}`,
);
variables.stats.postEvent('marketplace', type === 'install' ? 'Install' : 'Uninstall');
}
render() {
const locale = localStorage.getItem('language');
const shortLocale = locale.includes('_') ? locale.split('_')[0] : locale;
let languageNames = new Intl.DisplayNames([shortLocale], { type: 'language' });
const convertedType = (() => {
const map = {
photos: 'photo_packs',
quotes: 'quote_packs',
settings: 'preset_settings',
};
return map[this.props.data.data.type];
})();
const moreByCurator = this.state.moreByCurator
.filter((item) => item.type === convertedType && item.name !== this.props.data.data.name)
.sort(() => 0.5 - Math.random())
.slice(0, 3);
if (!this.props.data.display_name) {
return null;
}
// prevent console error
let iconsrc = this.props.data.icon;
if (!this.props.data.icon) {
iconsrc = null;
}
let updateButton;
if (this.state.showUpdateButton) {
updateButton = (
<Fragment key="update">
<Button
type="settings"
onClick={() => this.updateAddon()}
label={variables.getMessage('addons:product.buttons.update_addon')}
/>
</Fragment>
);
}
const itemWarning = () => {
const template = (message) => (
<div className="itemWarning">
<MdOutlineWarning />
<div className="text">
<span className="header">Warning</span>
<span>{message}</span>
</div>
</div>
);
if (this.props.data.data.sideload === true) {
return template(variables.getMessage('marketplace:product.sideload_warning'));
}
if (this.props.data.data.image_api === true) {
return template(variables.getMessage('marketplace:product.third_party_api'));
}
if (this.props.data.data.language !== undefined && this.props.data.data.language !== null) {
if (shortLocale !== this.props.data.data.language) {
return template(variables.getMessage('marketplace:product.not_in_language'));
}
}
return null;
};
const moreInfoItem = (icon, header, text) => (
<div className="infoItem">
{icon}
<div className="text">
<span className="header">{header}</span>
<span>{text}</span>
</div>
</div>
);
let dateObj, formattedDate;
if (this.props.data.data.updated_at) {
dateObj = new Date(this.props.data.data.updated_at);
formattedDate = new Intl.DateTimeFormat(shortLocale, {
year: 'numeric',
month: 'long',
day: '2-digit',
}).format(dateObj);
}
return (
<>
<Modal
closeTimeoutMS={300}
isOpen={this.state.shareModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
onRequestClose={() => this.setState({ shareModal: false })}
>
<ShareModal
data={variables.constants.API_URL + '/marketplace/share/' + btoa(this.props.data.slug)}
modalClose={() => this.setState({ shareModal: false })}
/>
</Modal>
<Header
title={
this.props.addons
? variables.getMessage('addons:added')
: this.props.data.onCollection && this.props.data.data.in_collections?.length > 0
? this.props.data.data.in_collections[0].display_name
: variables.getMessage('modals.main.navbar.marketplace')
}
secondaryTitle={
this.props.data.data.sideload
? this.props.data.data.name
: this.props.data.data.display_name
}
report={false}
goBack={this.props.toggleFunction}
/>
<div className="itemPage">
<div className="itemShowcase">
<div className="subHeader">
{moreInfoItem(
<MdAccountCircle />,
variables.getMessage('marketplace:product.created_by'),
this.props.data.author,
)}
{itemWarning()}
</div>
{this.props.data.data.photos && (
<div className="carousel">
<div className="carousel_container">
<Carousel data={this.props.data.data.photos} />
</div>
</div>
)}
{this.props.data.data.settings && (
<img
alt="product"
draggable={false}
src={iconsrc}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
)}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('marketplace:product.description')}
</span>
<Markdown>{this.props.data.data.description}</Markdown>
</div>
{this.props.data.data.quotes && (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('settings:sections.quote.title')}</th>
<th>{variables.getMessage('settings:sections.quote.author')}</th>
</tr>
{this.props.data.data.quotes.slice(0, this.state.count).map((quote, index) => (
<tr key={index}>
<td>{quote.quote}</td>
<td>{quote.author}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => this.incrementCount('quotes')}>
{this.state.count !== this.props.data.data.quotes.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
</div>
</>
)}
{this.props.data.data.settings && (
<>
<table>
<tbody>
<tr>
<th>{variables.getMessage('marketplace:product.setting')}</th>
<th>{variables.getMessage('marketplace:product.value')}</th>
</tr>
{Object.entries(this.props.data.data.settings)
.slice(0, this.state.count)
.map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
<div className="showMoreItems">
<span className="link" onClick={() => this.incrementCount('settings')}>
{this.state.count !== this.props.data.data.settings.length
? variables.getMessage('marketplace:product.show_all')
: variables.getMessage('marketplace:product.show_less')}
</span>
</div>
</>
)}
<div className="marketplaceDescription">
<span className="title">
{variables.getMessage('marketplace:product.details')}
</span>
<div className="moreInfo">
{this.props.data.data.updated_at &&
moreInfoItem(
<MdCalendarMonth />,
variables.getMessage('marketplace:product.updated_at'),
formattedDate,
)}
{this.props.data.data.quotes &&
moreInfoItem(
<MdFormatQuote />,
variables.getMessage('marketplace:product.no_quotes'),
this.props.data.data.quotes.length,
)}
{this.props.data.data.photos &&
moreInfoItem(
<MdImage />,
variables.getMessage('marketplace:product.no_images'),
this.props.data.data.photos.length,
)}
{this.props.data.data.quotes && this.props.data.data.language
? moreInfoItem(
<MdTranslate />,
variables.getMessage('settings:sections.language.title'),
languageNames.of(this.props.data.data.language),
)
: null}
{moreInfoItem(
<MdStyle />,
variables.getMessage('settings:sections.background.type.title'),
variables.getMessage(
'marketplace:' + this.getName(this.props.data.data.type),
) || 'marketplace',
)}
</div>
</div>
</div>
<div
className="itemInfo"
style={{
backgroundImage: `url("${this.props.data.data.icon_url}")`,
}}
>
<div className="front">
<img
className="icon"
alt="icon"
draggable={false}
src={this.props.data.data.icon_url}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;
}}
/>
{localStorage.getItem('welcomePreview') !== 'true' ? (
this.state.button
) : (
<p style={{ textAlign: 'center' }}>
{variables.getMessage(
'marketplace:product.buttons.not_available_preview',
)}
</p>
)}
{this.props.data.data.sideload !== true && (
<div className="iconButtons">
<Button
type="icon"
onClick={() => this.setState({ shareModal: true })}
icon={<MdIosShare />}
tooltipTitle={variables.getMessage('widgets.quote.share')}
tooltipKey="share"
/>
<Button
type="icon"
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
this.props.data.data.display_name.split(' ').join('+'),
'_blank',
)
}
icon={<MdFlag />}
tooltipTitle={variables.getMessage(
'marketplace:product.buttons.report',
)}
tooltipKey="report"
/>
</div>
)}
{this.props.data.data.in_collections?.length > 0 && (
<div>
<div className="inCollection">
<span className="subtitle">
{variables.getMessage('marketplace:product.part_of')}
</span>
<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>
</div>
)}
</div>
</div>
</div>
{moreByCurator.length > 1 && (
<div className="moreFromCurator">
<span className="title">
{variables.getMessage('marketplace:product.more_from_curator', {
name: this.props.data.author,
})}
</span>
<div>
<Items
isCurator={true}
type={this.props.data.data.type}
items={moreByCurator}
onCollection={this.state.collection}
toggleFunction={(input) => this.props.toggleFunction('item', input)}
collectionFunction={(input) => this.props.toggleFunction('collection', input)}
filter={''}
moreByCreator={true}
showCreateYourOwn={false}
/>
</div>
</div>
)}
</>
);
}
}
export { ItemPage as default, ItemPage };

View File

@@ -1,21 +1,146 @@
import { memo, useEffect } from 'react';
import { memo, useEffect, useState } from 'react';
import variables from 'config/variables';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
import MarketplaceTab from '../../marketplace/views/Browse';
import { NewItems as Items } from '../../marketplace/components/Items/NewItems';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import { NewItemPage } from '../../marketplace/views/newItemPage';
import { ItemPage } from '../../marketplace/views/ItemPage';
import { NewItems as Items } from '../../marketplace/components/Items/Items';
import { AnimatePresence, motion } from 'framer-motion';
import Collection from '../../marketplace/components/Collection/Collection';
import clsx from 'clsx';
import { PiGridFourFill } from 'react-icons/pi';
import {
MdFormatListBulleted,
MdFilterListAlt,
MdOutlineArrowForward,
MdOutlineOpenInNew,
} from 'react-icons/md';
function Marketplace(props) {
const { done, items, getItems } = useMarketData();
const { done, items, collections, getItems, getCollections, selectedItem } = useMarketData();
const { subTab } = useTab();
const [itemsView, setItemsView] = useState('grid');
const [itemsFilter, setItemsFilter] = useState('all');
const [filteredItems, setFilteredItems] = useState([]);
const [sortItems, setSortItems] = useState('a-z');
const FilterOptions = () => {
return (
<div className="flex flex-row gap-2 my-3">
<div
onClick={() => {
setItemsFilter('all');
}}
className={clsx(
'cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base ',
{
'bg-white text-black': itemsFilter === 'all',
'bg-[#333] hover:bg-[#222222] text-white cursor:pointer': itemsFilter !== 'all',
},
)}
>
All
</div>
<div
onClick={() => {
setItemsFilter('photo_packs');
}}
className={clsx(
'cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base ',
{
'bg-white text-black': itemsFilter === 'photo_packs',
'bg-[#333] hover:bg-[#222222] text-white cursor:pointer':
itemsFilter !== 'photo_packs',
},
)}
>
{variables.getMessage('marketplace:photo_packs') || 'marketplace'}
</div>
<div
onClick={() => {
setItemsFilter('quote_packs');
}}
className={clsx(
'cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base ',
{
'bg-white text-black': itemsFilter === 'quote_packs',
'bg-[#333] hover:bg-[#222222] text-white': itemsFilter !== 'quote_packs',
},
)}
>
{variables.getMessage('marketplace:quote_packs') || 'marketplace'}
</div>
<div
onClick={() => {
setItemsFilter('preset_settings');
}}
className={clsx(
'cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base ',
{
'bg-white text-black': itemsFilter === 'preset_settings',
'bg-[#333] hover:bg-[#222222] text-white': itemsFilter !== 'preset_settings',
},
)}
>
{variables.getMessage('marketplace:preset_settings') || 'marketplace'}
</div>
</div>
);
};
const ItemView = () => {
return (
<div className="flex flex-row gap-2">
<button
onClick={() => setItemsView('grid')}
className={clsx('cursor:pointer h-[40px] w-[40px] grid place-items-center rounded-lg', {
'bg-[#333]': itemsView === 'grid',
})}
>
<PiGridFourFill />
</button>
<button
onClick={() => setItemsView('list')}
className={clsx('cursor:pointer h-[40px] w-[40px] grid place-items-center rounded-lg', {
'bg-[#333]': itemsView === 'list',
})}
>
<MdFormatListBulleted />
</button>
</div>
);
};
useEffect(() => {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return;
}
getItems();
}, []);
getCollections();
}, []); // Only runs once when the component mounts
useEffect(() => {
switch (itemsFilter) {
case 'all':
setFilteredItems(items);
break;
case 'photo_packs':
setFilteredItems(items.filter((item) => item.type === 'photo_packs'));
break;
case 'quote_packs':
setFilteredItems(items.filter((item) => item.type === 'quote_packs'));
break;
case 'preset_settings':
setFilteredItems(items.filter((item) => item.type === 'preset_settings'));
break;
default:
setFilteredItems(items);
}
}, [itemsFilter, items]); // Only depends on itemsFilter and items
return (
<AnimatePresence mode="wait">
@@ -27,12 +152,19 @@ function Marketplace(props) {
transition={{ duration: 0.8 }}
>
{subTab === '' ? (
<motion.div key="items">
<Items items={items} />
</motion.div>
<>
<Collection collections={collections} />
<motion.div key="items">
<div className="w-full flex flex-row justify-between items-center">
{FilterOptions()}
<div className="flex flex-row gap-2">{ItemView()}</div>
</div>
<Items items={filteredItems} view={itemsView} />
</motion.div>
</>
) : (
<motion.div key="itempage">
<NewItemPage />
<ItemPage />
</motion.div>
)}
</motion.div>