refactor: Rewrite marketplace quote pack logic entirely

This commit is contained in:
alexsparkes
2024-06-20 14:21:00 +01:00
parent dfcd509b96
commit cb54e705ee
6 changed files with 454 additions and 320 deletions

View File

@@ -12,6 +12,9 @@ export const MarketplaceDataProvider = ({ children }) => {
const [collections, setCollections] = useState([]);
const [selectedItem, setSelectedItem] = useState(null);
const [selectedCollection, setSelectedCollection] = useState(null);
const [installedItems, setInstalledItems] = useState(
JSON.parse(localStorage.getItem('installed')) || [],
);
let numOfRequests = 0;
const controller = new AbortController();
@@ -88,7 +91,30 @@ export const MarketplaceDataProvider = ({ children }) => {
return new Promise((resolve) => {
resolve(item.data);
});
}
};
const installItem = () => {
if (!installedItems.some((item) => item.name === selectedItem.name)) {
setInstalledItems([...installedItems, selectedItem]);
localStorage.setItem('installed', JSON.stringify(installedItems));
}
console.log('type', selectedItem.type, 'name', selectedItem.name, 'installed', installedItems);
if (selectedItem.type === 'quotes') {
let quotePacks = JSON.parse(localStorage.getItem('quote_packs')) || {};
quotePacks[selectedItem.name] = selectedItem.quotes;
localStorage.setItem('quote_packs', JSON.stringify(quotePacks));
}
};
const uninstallItem = () => {
setInstalledItems(installedItems.filter((item) => item.name !== selectedItem.name));
localStorage.setItem('installed', JSON.stringify(installedItems));
if (selectedItem.type === 'quotes') {
let quotePacks = JSON.parse(localStorage.getItem('quote_packs')) || {};
delete quotePacks[selectedItem.name];
localStorage.setItem('quote_packs', JSON.stringify(quotePacks));
}
};
return (
<MarketDataContext.Provider
@@ -104,6 +130,10 @@ export const MarketplaceDataProvider = ({ children }) => {
setSelectedItem,
setSelectedCollection,
collections,
installedItems,
setInstalledItems,
installItem,
uninstallItem,
}}
>
{children}

View File

@@ -10,12 +10,6 @@ function ItemCard({ item, type, onCollection, isCurator, cardStyle }) {
item._onCollection = onCollection;
const SelectItem = () => {
getItemData(item.type, item.name).then((data) => {
setSubTab(data.display_name);
});
};
const getName = (name) => {
const nameMappings = {
photos: 'photo_packs',
@@ -25,6 +19,12 @@ function ItemCard({ item, type, onCollection, isCurator, cardStyle }) {
return nameMappings[name] || name;
};
const SelectItem = () => {
getItemData(getName(item.type), item.name).then((data) => {
setSubTab(data.display_name);
});
};
switch (cardStyle) {
case 'list':
return (

View File

@@ -22,6 +22,7 @@ import Markdown from 'markdown-to-jsx';
import { Button } from 'components/Elements';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
import { Carousel } from '../components/Elements/Carousel';
import { AnimatePresence, motion } from 'framer-motion';
const ItemPage = () => {
const { subTab } = useTab();
@@ -29,17 +30,28 @@ const ItemPage = () => {
const [count, setCount] = useState(5);
const [item, setItemData] = useState(null);
const [shareModal, setShareModal] = useState(false);
const { selectedItem } = useMarketData();
const { installedItems, setInstalledItems, selectedItem, installItem, uninstallItem } =
useMarketData();
let themeColour = selectedItem?.colour || '#000000';
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
const installedItems = JSON.parse(localStorage.getItem('installed')) || [];
localStorage.setItem('installed', JSON.stringify(installedItems));
const installed = installedItems.some((item) => item.name === selectedItem.name);
setIsInstalled(installed);
}, [selectedItem]);
}, [installedItems, selectedItem.name]);
const installItem = () => {
{
/*const uninstallItem = () => {
setInstalledItems(installedItems.filter((item) => item.name !== selectedItem.name));
localStorage.setItem('installed', JSON.stringify(installedItems));
setIsInstalled(false);
};*/
}
{
/*const installItem = () => {
const installedItems = JSON.parse(localStorage.getItem('installed')) || [];
if (!installedItems.some((item) => item.name === selectedItem.name)) {
installedItems.push(selectedItem);
@@ -158,7 +170,8 @@ const ItemPage = () => {
}
setIsInstalled(false);
};
};*/
}
const locale = localStorage.getItem('language');
const shortLocale = locale.includes('-') ? locale.split('-')[0] : locale;
@@ -339,7 +352,7 @@ const ItemPage = () => {
}}
>
<img
className="icon"
className="icon mb-5"
alt="icon"
draggable={false}
src={selectedItem?.icon_url}
@@ -348,21 +361,56 @@ const ItemPage = () => {
e.target.src = placeholderIcon;
}}
/>
{!isInstalled ? (
<button
onClick={installItem}
className="bg-white text-black cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base hover:bg-[#ccc]"
>
Add To Mue
</button>
{localStorage.getItem('welcomePreview') !== 'true' ? (
<>
{!isInstalled ? (
<button
onClick={installItem}
style={{ backgroundColor: selectedItem?.colour }}
className={` text-white cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base hover:opacity-50`}
>
{variables.getMessage('marketplace:product.buttons.addtomue')}
</button>
) : (
<button
onClick={uninstallItem}
style={{ backgroundColor: selectedItem?.colour }}
className="cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base hover:bg-[#ccc]"
>
Remove
</button>
)}
</>
) : (
<button
onClick={uninstallItem}
className="bg-white text-black cursor-pointer transition-all duration-200 rounded-full px-6 py-2 text-base hover:bg-[#ccc]"
>
Remove
</button>
<p style={{ textAlign: 'center' }}>
{variables.getMessage('marketplace:product.buttons.not_available_preview')}
</p>
)}
{selectedItem?.sideload !== true && (
<div className="grid grid-cols-2 gap-5">
<button
onClick={() => setShareModal(true)}
style={{ backgroundColor: selectedItem?.colour }}
className={` text-white cursor-pointer transition-all duration-200 rounded-full px-6 py-3 text-base hover:opacity-50 grid place-items-center`}
>
<MdIosShare />
</button>
<button
onClick={() =>
window.open(
variables.constants.REPORT_ITEM + selectedItem?.display_name.split(' ').join('+'),
'_blank',
)
}
style={{ backgroundColor: selectedItem?.colour }}
className={` text-white cursor-pointer transition-all duration-200 rounded-full px-6 py-3 text-base hover:opacity-50 grid place-items-center`}
>
<MdFlag />
</button>
</div>
)}
{/*}
{localStorage.getItem('welcomePreview') !== 'true' ? (
<Button
type="settings"
@@ -397,7 +445,7 @@ const ItemPage = () => {
tooltipKey="report"
/>
</div>
)}
)}*/}
{selectedItem?.in_collections?.length > 0 && (
<div>
<div className="inCollection">

View File

@@ -1,299 +1,45 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { MdUpdate, MdOutlineExtensionOff, MdSendTimeExtension } from 'react-icons/md';
import { toast } from 'react-toastify';
import Modal from 'react-modal';
import { NewItems as Items } from '../components/Items/Items';
import { useMarketData } from 'features/marketplace/api/MarketplaceDataContext';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import { ItemPage } from '../views/ItemPage';
import { MdOutlineExtensionOff } from 'react-icons/md';
import { SideloadFailedModal } from '../components/Elements/SideloadFailedModal/SideloadFailedModal';
import ItemPage from './oldItemPage';
import Items from '../components/Items/OldItems';
import { NewItems } from '../components/Items/Items';
import { Dropdown, FileUpload } from 'components/Form/Settings';
import { Header, CustomActions } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
const Library = () => {
let installed = JSON.parse(localStorage.getItem('installed'));
const { selectedItem } = useMarketData();
const { subTab, changeTab } = useTab();
import { install, uninstall } from 'utils/marketplace';
import { sortItems } from '../api';
export default class Added extends PureComponent {
constructor() {
super();
this.state = {
installed: JSON.parse(localStorage.getItem('installed')),
item: {},
button: '',
showFailed: false,
failedReason: '',
};
this.buttons = {
uninstall: (
<Button
type="settings"
onClick={() => this.uninstall()}
label={variables.getMessage('marketplace:product.buttons.remove')}
/>
),
};
// Ensure installed is an array
if (!Array.isArray(installed)) {
installed = [];
}
installAddon(input) {
let failedReason = '';
if (!input.name) {
failedReason = variables.getMessage('addons:sideload.errors.no_name');
} else if (!input.author) {
failedReason = variables.getMessage('addons:sideload.errors.no_author');
} else if (!input.type) {
failedReason = variables.getMessage('addons:sideload.errors.no_type');
} else if (!input.version) {
failedReason = variables.getMessage('addons:sideload.errors.no_version');
} else if (
input.type === 'photos' &&
(!input.photos ||
!input.photos.length ||
!input.photos[0].url ||
!input.photos[0].url.default ||
!input.photos[0].photographer ||
!input.photos[0].location)
) {
failedReason = variables.getMessage('addons:sideload.errors.invalid_photos');
} else if (
input.type === 'quotes' &&
(!input.quotes || !input.quotes.length || !input.quotes[0].quote || !input.quotes[0].author)
) {
failedReason = variables.getMessage('addons:sideload.errors.invalid_quotes');
}
if (failedReason !== '' && this.state.showFailed === false) {
return this.setState({
failedReason,
showFailed: true,
});
}
install(input.type, input, true, false);
toast(variables.getMessage('toasts.installed'));
variables.stats.postEvent('marketplace', 'Sideload');
this.setState({
installed: JSON.parse(localStorage.getItem('installed')),
});
}
getSideloadButton() {
if (installed.length === 0) {
return (
<Button
type="settings"
onClick={() => document.getElementById('file-input').click()}
ref={this.customDnd}
icon={<MdSendTimeExtension />}
label={variables.getMessage('addons:sideload.title')}
/>
);
}
toggle(type, data) {
if (type === 'item') {
const installed = JSON.parse(localStorage.getItem('installed'));
const info = {
data: installed.find((i) => i.name === data.name),
};
this.setState({
item: {
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: info.data.description,
//updated: info.updated,
version: info.data.version,
icon: info.data.screenshot_url,
data: info.data,
},
button: this.buttons.uninstall,
});
variables.stats.postEvent('marketplace', 'ItemPage viewed');
} else {
this.setState({
item: {},
});
}
}
uninstall() {
uninstall(this.state.item.type, this.state.item.display_name);
toast(variables.getMessage('toasts.uninstalled'));
this.setState({
button: '',
installed: JSON.parse(localStorage.getItem('installed')),
item: {},
});
variables.stats.postEvent('marketplace', 'Uninstall');
}
updateCheck() {
let updates = 0;
this.state.installed.forEach(async (item) => {
const data = await (
await fetch(variables.constants.API_URL + 'marketplace/item/' + item.name)
).json();
if (data.version !== item.version) {
updates++;
}
});
if (updates > 0) {
toast(
variables.getMessage('addons:updates_available', {
amount: updates,
}),
);
} else {
toast(variables.getMessage('addons:no_updates'));
}
}
removeAll() {
try {
this.state.installed.forEach((item) => {
uninstall(item.type, item.name);
});
} catch (e) {}
localStorage.setItem('installed', JSON.stringify([]));
toast(variables.getMessage('toasts.uninstalled_all'));
this.setState({
installed: [],
});
this.forceUpdate();
}
componentDidMount() {
this.setState({
installed: sortItems(JSON.parse(localStorage.getItem('installed')), 'newest'),
});
}
render() {
console.log(this.state.installed);
const sideLoadBackendElements = () => (
<>
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ showFailed: false })}
isOpen={this.state.showFailed}
className="Modal resetmodal mainModal resetmodal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<SideloadFailedModal
modalClose={() => this.setState({ showFailed: false })}
reason={this.state.failedReason}
/>
</Modal>
<FileUpload
id="file-input"
type="settings"
accept="application/json"
loadFunction={(e) => this.installAddon(JSON.parse(e))}
/>
</>
);
if (this.state.installed.length === 0) {
return (
<>
<Header title={variables.getMessage('modals.main.navbar.addons')} report={false}>
<CustomActions>{this.getSideloadButton()}</CustomActions>
</Header>
{sideLoadBackendElements()}
<div className="emptyItems">
<div className="emptyNewMessage">
<MdOutlineExtensionOff />
<span className="title">{variables.getMessage('addons:empty.title')}</span>
<span className="subtitle">{variables.getMessage('addons:empty.description')}</span>
</div>
{/*<Header title={variables.getMessage('modals.main.navbar.addons')} report={false}>
<CustomActions>{this.getSideloadButton()}</CustomActions>
</Header>
{sideLoadBackendElements()}*/}
<div className="emptyItems">
<div className="emptyNewMessage">
<MdOutlineExtensionOff />
<span className="title">{variables.getMessage('addons:empty.title')}</span>
<span className="subtitle">{variables.getMessage('addons:empty.description')}</span>
<button onClick={() => changeTab('marketplace')}>Go to marketplace</button>
</div>
</>
);
}
if (this.state.item.display_name) {
return (
<ItemPage
data={this.state.item}
button={this.state.button}
addons={true}
toggleFunction={() => this.toggle()}
/>
);
}
return (
<>
<Header title={variables.getMessage('addons:added')} report={false}>
<CustomActions>
{this.getSideloadButton()}
{sideLoadBackendElements()}
<Button
type="settings"
onClick={() => this.updateCheck()}
icon={<MdUpdate />}
label={variables.getMessage('addons:check_updates')}
/>
<Button
type="settings"
onClick={() => this.removeAll()}
icon={<MdOutlineExtensionOff />}
label="Remove all addons"
/>
{/*<Button
type="settings"
onClick={() => document.getElementById('file-input').click()}
icon={<MdSendTimeExtension />}
label={variables.getMessage('addons:sideload.title')}
` />*/}
</CustomActions>
</Header>
{/*<Dropdown
label={variables.getMessage('addons:sort.title')}
name="sortAddons"
onChange={(value) => this.setState({ installed: sortItems(this.state.installed, value) })}
items={[
{
value: 'newest',
text: variables.getMessage('addons:sort.newest'),
},
{
value: 'oldest',
text: variables.getMessage('addons:sort.oldest'),
},
{
value: 'a-z',
text: variables.getMessage('addons:sort.a_z'),
},
{
value: 'z-a',
text: variables.getMessage('addons:sort.z_a'),
},
]}
/>*/}
{/*<Items
items={this.state.installed}
isAdded={true}
filter=""
toggleFunction={(input) => this.toggle('item', input)}
showCreateYourOwn={false}
/>*/}
<div className="w-full ">
<NewItems items={this.state.installed} view="list" />
</div>
</>
);
}
}
return (
<>
{subTab === '' && selectedItem === null && <Items items={installed} view="list" />}
{subTab !== '' && selectedItem !== null && <ItemPage />}
</>
);
};
export default Library;

View File

@@ -0,0 +1,301 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { MdUpdate, MdOutlineExtensionOff, MdSendTimeExtension } from 'react-icons/md';
import { toast } from 'react-toastify';
import Modal from 'react-modal';
import { SideloadFailedModal } from '../components/Elements/SideloadFailedModal/SideloadFailedModal';
import ItemPage from './oldItemPage';
import Items from '../components/Items/OldItems';
import { NewItems } from '../components/Items/Items';
import { Dropdown, FileUpload } from 'components/Form/Settings';
import { Header, CustomActions } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
import { install, uninstall } from 'utils/marketplace';
import { sortItems } from '../api';
export default class Added extends PureComponent {
constructor() {
super();
this.state = {
installed: JSON.parse(localStorage.getItem('installed')),
item: {},
button: '',
showFailed: false,
failedReason: '',
};
this.buttons = {
uninstall: (
<Button
type="settings"
onClick={() => this.uninstall()}
label={variables.getMessage('marketplace:product.buttons.remove')}
/>
),
};
}
installAddon(input) {
let failedReason = '';
if (!input.name) {
failedReason = variables.getMessage('addons:sideload.errors.no_name');
} else if (!input.author) {
failedReason = variables.getMessage('addons:sideload.errors.no_author');
} else if (!input.type) {
failedReason = variables.getMessage('addons:sideload.errors.no_type');
} else if (!input.version) {
failedReason = variables.getMessage('addons:sideload.errors.no_version');
} else if (
input.type === 'photos' &&
(!input.photos ||
!input.photos.length ||
!input.photos[0].url ||
!input.photos[0].url.default ||
!input.photos[0].photographer ||
!input.photos[0].location)
) {
failedReason = variables.getMessage('addons:sideload.errors.invalid_photos');
} else if (
input.type === 'quotes' &&
(!input.quotes || !input.quotes.length || !input.quotes[0].quote || !input.quotes[0].author)
) {
failedReason = variables.getMessage('addons:sideload.errors.invalid_quotes');
}
if (failedReason !== '' && this.state.showFailed === false) {
return this.setState({
failedReason,
showFailed: true,
});
}
install(input.type, input, true, false);
toast(variables.getMessage('toasts.installed'));
variables.stats.postEvent('marketplace', 'Sideload');
this.setState({
installed: JSON.parse(localStorage.getItem('installed')),
});
}
getSideloadButton() {
return (
<Button
type="settings"
onClick={() => document.getElementById('file-input').click()}
ref={this.customDnd}
icon={<MdSendTimeExtension />}
label={variables.getMessage('addons:sideload.title')}
/>
);
}
toggle(type, data) {
if (type === 'item') {
const installed = JSON.parse(localStorage.getItem('installed'));
const info = {
data: installed.find((i) => i.name === data.name),
};
this.setState({
item: {
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: info.data.description,
//updated: info.updated,
version: info.data.version,
icon: info.data.screenshot_url,
data: info.data,
},
button: this.buttons.uninstall,
});
variables.stats.postEvent('marketplace', 'ItemPage viewed');
} else {
this.setState({
item: {},
});
}
}
uninstall() {
uninstall(this.state.item.type, this.state.item.display_name);
toast(variables.getMessage('toasts.uninstalled'));
this.setState({
button: '',
installed: JSON.parse(localStorage.getItem('installed')),
item: {},
});
variables.stats.postEvent('marketplace', 'Uninstall');
}
updateCheck() {
let updates = 0;
this.state.installed.forEach(async (item) => {
const data = await (
await fetch(variables.constants.API_URL + 'marketplace/item/' + item.name)
).json();
if (data.version !== item.version) {
updates++;
}
});
if (updates > 0) {
toast(
variables.getMessage('addons:updates_available', {
amount: updates,
}),
);
} else {
toast(variables.getMessage('addons:no_updates'));
}
}
removeAll() {
try {
this.state.installed.forEach((item) => {
uninstall(item.type, item.name);
});
} catch (e) {}
localStorage.setItem('installed', JSON.stringify([]));
toast(variables.getMessage('toasts.uninstalled_all'));
this.setState({
installed: [],
});
this.forceUpdate();
}
componentDidMount() {
this.setState({
installed: sortItems(JSON.parse(localStorage.getItem('installed')), 'newest'),
});
}
render() {
console.log(this.state.installed);
const sideLoadBackendElements = () => (
<>
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ showFailed: false })}
isOpen={this.state.showFailed}
className="Modal resetmodal mainModal resetmodal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<SideloadFailedModal
modalClose={() => this.setState({ showFailed: false })}
reason={this.state.failedReason}
/>
</Modal>
<FileUpload
id="file-input"
type="settings"
accept="application/json"
loadFunction={(e) => this.installAddon(JSON.parse(e))}
/>
</>
);
if (this.state.installed.length === 0) {
return (
<>
<Header title={variables.getMessage('modals.main.navbar.addons')} report={false}>
<CustomActions>{this.getSideloadButton()}</CustomActions>
</Header>
{sideLoadBackendElements()}
<div className="emptyItems">
<div className="emptyNewMessage">
<MdOutlineExtensionOff />
<span className="title">{variables.getMessage('addons:empty.title')}</span>
<span className="subtitle">{variables.getMessage('addons:empty.description')}</span>
</div>
</div>
</>
);
}
if (this.state.item.display_name) {
return (
<ItemPage
data={this.state.item}
button={this.state.button}
addons={true}
toggleFunction={() => this.toggle()}
/>
);
}
return (
<>
<Header title={variables.getMessage('addons:added')} report={false}>
<CustomActions>
{this.getSideloadButton()}
{sideLoadBackendElements()}
<Button
type="settings"
onClick={() => this.updateCheck()}
icon={<MdUpdate />}
label={variables.getMessage('addons:check_updates')}
/>
<Button
type="settings"
onClick={() => this.removeAll()}
icon={<MdOutlineExtensionOff />}
label="Remove all addons"
/>
{/*<Button
type="settings"
onClick={() => document.getElementById('file-input').click()}
icon={<MdSendTimeExtension />}
label={variables.getMessage('addons:sideload.title')}
` />*/}
</CustomActions>
</Header>
{/*<Dropdown
label={variables.getMessage('addons:sort.title')}
name="sortAddons"
onChange={(value) => this.setState({ installed: sortItems(this.state.installed, value) })}
items={[
{
value: 'newest',
text: variables.getMessage('addons:sort.newest'),
},
{
value: 'oldest',
text: variables.getMessage('addons:sort.oldest'),
},
{
value: 'a-z',
text: variables.getMessage('addons:sort.a_z'),
},
{
value: 'z-a',
text: variables.getMessage('addons:sort.z_a'),
},
]}
/>*/}
{/*<Items
items={this.state.installed}
isAdded={true}
filter=""
toggleFunction={(input) => this.toggle('item', input)}
showCreateYourOwn={false}
/>*/}
<div className="w-full ">
{subTab === '' && selectedItem === null && (
<NewItems items={this.state.installed} view="list" />
)}
</div>
</>
);
}
}