update, improve, and fix some bugs with some addon components

pull/67/head
dperolio 3 years ago
parent e21571cdf4
commit c450c8d4ef
No known key found for this signature in database
GPG Key ID: 3E9BBAA710D3DDCE

@ -1,11 +1,12 @@
import React, { memo, useState, useEffect } from 'react';
import React, { memo, useEffect, useState } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
import { toPlural } from '@vizality/util/string';
import { useForceUpdate } from '@vizality/hooks';
import { contextMenu } from '@vizality/webpack';
import { Events } from '@vizality/constants';
import { Messages } from '@vizality/i18n';
import { Divider, FormTitle, Anchor, Icon, LazyImage, Button, Switch } from '..';
import { Divider, FormTitle, Anchor, Icon, LazyImage, Button, Switch, OverflowTooltip } from '..';
import { AddonContextMenu } from '.';
const { openContextMenu } = contextMenu;
@ -78,7 +79,7 @@ const Permissions = memo(({ permissions }) => {
*/
const Author = memo(({ author }) => {
return (
<div className='vz-addon-card-author-wrapper'>
<OverflowTooltip className='vz-addon-card-author-wrapper' text={author.name || author}>
<Anchor
type='user'
userId={author.id}
@ -86,7 +87,7 @@ const Author = memo(({ author }) => {
>
{author.name || author}
</Anchor>
</div>
</OverflowTooltip>
);
});
@ -106,44 +107,35 @@ const Description = memo(({ description }) => (
* Addon details.
* @component
*/
const Details = memo(() => {
const Details = memo(({ stars }) => {
const downloads = Math.floor(Math.random() * (7777777 - 0)).toLocaleString();
return (
<div className='vz-addon-card-details'>
<div className='vz-addon-card-detail-wrapper'>
<div className='vz-addon-card-detail-label'>Rating</div>
<div className='vz-addon-card-detail-value-wrapper'>
<OverflowTooltip text={`${stars} Stars`} className='vz-addon-card-detail-value-wrapper'>
<Icon
className='vz-addon-card-detail-value-icon-wrapper vz-addon-card-rating-icon-wrapper'
iconClassName='vz-addon-card-rating-icon'
name='Star'
size='14'
/>
<div className='vz-addon-card-detail-value vz-addon-card-detail-rating-number'>5</div>
</div>
</div>
<div className='vz-addon-card-detail-wrapper'>
<div className='vz-addon-card-detail-label'>Downloads</div>
<div className='vz-addon-card-detail-value-wrapper'>
<Icon
className='vz-addon-card-detail-value-icon-wrapper vz-addon-card-rating-icon-wrapper'
iconClassName='vz-addon-card-rating-icon'
name='Download'
size='14'
size='18'
/>
<div className='vz-addon-card-detail-value vz-addon-card-detail-downloads-count'>123,663</div>
</div>
<div className='vz-addon-card-detail-value vz-addon-card-detail-rating-number'>
{stars}
</div>
</OverflowTooltip>
</div>
<div className='vz-addon-card-detail-wrapper'>
<div className='vz-addon-card-detail-label'>Last Updated</div>
<div className='vz-addon-card-detail-value-wrapper'>
<OverflowTooltip text={`${downloads} Downloads`} className='vz-addon-card-detail-value-wrapper'>
<Icon
className='vz-addon-card-detail-value-icon-wrapper vz-addon-card-rating-icon-wrapper'
iconClassName='vz-addon-card-rating-icon'
name='ClockReverse'
size='14'
name='CloudDownload'
size='18'
/>
<div className='vz-addon-card-detail-value vz-addon-card-detail-updated-date'>11/26/2020</div>
</div>
<div className='vz-addon-card-detail-value vz-addon-card-detail-downloads-count'>
{downloads}
</div>
</OverflowTooltip>
</div>
</div>
);
@ -155,7 +147,7 @@ const Details = memo(() => {
* @param {object} props
* @param {string} props.icon Addon icon
*/
const AddonIcon = ({ icon }) => {
const AddonIcon = memo(({ icon }) => {
return (
<div className='vz-addon-card-icon'>
<LazyImage
@ -165,7 +157,7 @@ const AddonIcon = ({ icon }) => {
/>
</div>
);
};
});
/**
* Addon footer. Used in addon cards.
@ -178,12 +170,12 @@ const AddonIcon = ({ icon }) => {
* @param {boolean} props.isInstalled Is installed
* @param {function(!props.isEnabled)} props.onToggle Toggle handler
*/
const Footer = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggle }) => {
const Footer = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
return (
<div className='vz-addon-card-footer-wrapper'>
<div className='vz-addon-card-footer'>
<div className='vz-addon-card-footer-section-left'>
{isInstalled &&
{!community && isInstalled &&
<div className='vz-addon-card-uninstall'>
<Button
onClick={evt => {
@ -198,43 +190,90 @@ const Footer = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggl
</Button>
</div>
}
{/* <div className='vz-addon-card-tags'>
<div className='vz-addon-card-tag'>
Pie
</div>
<div className='vz-addon-card-tag'>
Lazer Beams
</div>
<div className='vz-addon-card-tag'>
Stuff
</div>
</div> */}
{community && <Details stars={addon.stars} />}
</div>
<div className='vz-addon-card-footer-section-right'>
{isInstalled && hasSettings &&
{!community && isInstalled && hasSettings &&
<div className='vz-addon-card-settings-button'>
<Icon
className='vz-addon-card-settings-button-icon-wrapper'
iconClassName='vz-addon-card-settings-button-icon'
name='Gear'
tooltip='Settings'
onClick={() => vizality.api.routes.navigateTo(`/vizality/${toPlural(type)}/${addon.addonId}`)}
onClick={evt => {
evt?.persist?.();
evt?.preventDefault?.();
vizality.api.routes.navigateTo(`/vizality/${type}/${addon.addonId}/settings`);
}}
/>
</div>
}
<div className='vz-addon-card-toggle-wrapper'>
<Switch
className='vz-addon-card-toggle'
value={isEnabled}
onChange={() => onToggle(!isEnabled)}
/>
</div>
{!community && (
<div className='vz-addon-card-toggle-wrapper'>
<Switch
className='vz-addon-card-toggle'
checked={isEnabled}
onChange={async v => onToggle(v)}
/>
</div>
)}
{community && (
<div className='vz-addon-card-community-action'>
{isInstalled
? (
<div className='vz-addon-card-uninstall'>
<Button
onClick={evt => {
evt.stopPropagation();
vizality.manager[toPlural(type)].uninstall(addon.addonId);
}}
color={Button.Colors.RED}
look={Button.Looks.FILLED}
size={Button.Sizes.ICON}
>
<Icon name='Trash' tooltip='Uninstall' />
</Button>
</div>
)
: (
<Button
onClick={evt => {
evt.stopPropagation();
vizality.manager[toPlural(type)].install(addon.addonId);
}}
color={Button.Colors.GREEN}
look={Button.Looks.FILLED}
size={Button.Sizes.SMALL}
>
Install
</Button>
)
}
</div>
)}
</div>
</div>
</div>
);
});
const Banner = memo(({ addon }) => {
const hasBanner = Boolean(addon.manifest.banner);
return (
<div className='vz-addon-card-banner-wrapper'>
<div className='vz-addon-card-banner-inner' >
{hasBanner && (
<LazyImage
className='vz-addon-card-banner-image-wrapper'
imageClassName='vz-addon-card-banner-image'
src={addon.manifest.banner}
/>
)}
</div>
</div>
);
});
/**
* Addon card in card layout.
* @component
@ -246,19 +285,18 @@ const Footer = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggl
* @param {boolean} props.isInstalled Is installed
* @param {function()} props.onToggle Toggle handler
*/
const AddonCard = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggle }) => {
const AddonCard = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
return (
<div className='vz-addon-card-header-wrapper'>
{/* {showPreviewImages && <Previews {...props} />} */}
<div className='vz-addon-card-content-wrapper'>
<div className='vz-addon-card-content'>
<div className='vz-addon-card-header'>
<AddonIcon icon={addon.manifest.icon} />
<div className='vz-addon-card-metadata'>
<div className='vz-addon-card-name-version'>
<div className='vz-addon-card-name'>
<OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
{addon.manifest.name}
</div>
</OverflowTooltip>
<span className='vz-addon-card-version'>
{addon.manifest.version}
</span>
@ -269,6 +307,7 @@ const AddonCard = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onTo
<Description description={addon.manifest.description} />
<Permissions permissions={addon.manifest.permissions} />
<Footer
community={community}
addon={addon}
type={type}
hasSettings={hasSettings}
@ -293,7 +332,10 @@ const AddonCard = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onTo
* @param {boolean} props.isInstalled Is installed
* @param {function()} props.onToggle Toggle handler
*/
const AddonCardCompact = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggle }) => {
const AddonCardCompact = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
const [ installing, setInstalling ] = useState(false);
const [ installFailed, setInstallFailed ] = useState(false);
return (
<div className='vz-addon-card-header-wrapper'>
<div className='vz-addon-card-content-wrapper'>
@ -302,49 +344,79 @@ const AddonCardCompact = memo(({ addon, type, hasSettings, isEnabled, isInstalle
<AddonIcon icon={addon.manifest.icon} />
<div className='vz-addon-card-metadata'>
<div className='vz-addon-card-name-version'>
<div className='vz-addon-card-name'>
<OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
{addon.manifest.name}
</div>
<span className='vz-addon-card-version'>
{addon.manifest.version}
</span>
</OverflowTooltip>
</div>
<Author author={addon.manifest.author} />
</div>
{false && <Details />}
<div className='vz-addon-card-actions'>
{isInstalled &&
<div className='vz-addon-card-uninstall'>
<Icon
className='vz-addon-card-uninstall-button-wrapper'
iconClassName='vz-addon-card-uninstall-button'
name='Trash'
tooltip='Uninstall'
onClick={evt => {
evt.stopPropagation();
vizality.manager[toPlural(type)].uninstall(addon.addonId);
}}
/>
</div>
}
{hasSettings && (
{!community && hasSettings && (
<div className='vz-addon-card-settings'>
<Icon
className='vz-addon-card-settings-button-wrapper'
iconClassName='vz-addon-card-settings-button'
name='Gear'
tooltip='Settings'
onClick={() => void 0}
onClick={evt => {
evt?.persist?.();
evt?.preventDefault?.();
vizality.api.routes.navigateTo(`/vizality/${type}/${addon.addonId}/settings`);
}}
/>
</div>
)}
{community && (
isInstalled
? (
<div className='vz-addon-card-uninstall'>
<Button
onClick={evt => {
evt.stopPropagation();
vizality.manager[toPlural(type)].uninstall(addon.addonId);
}}
color={Button.Colors.RED}
look={Button.Looks.FILLED}
size={Button.Sizes.ICON}
>
<Icon name='Trash' tooltip='Uninstall' />
</Button>
</div>
)
: (
<div className='vz-addon-card-install'>
<Button
onClick={evt => unstable_batchedUpdates(async () => {
evt.stopPropagation();
setInstallFailed(false);
setInstalling(true);
await vizality.manager[toPlural(type)].install(addon.addonId)
.then(() => setInstalling(false))
.catch(() => unstable_batchedUpdates(() => {
setInstalling(false);
setInstallFailed(true);
}));
})}
disabled={installing}
submitting={installing}
color={installFailed ? Button.Colors.YELLOW : Button.Colors.GREEN}
look={Button.Looks.FILLED}
size={Button.Sizes.ICON}
>
<Icon name={installFailed ? 'Retry' : 'CloudDownload'} tooltip={installFailed ? 'Retry' : 'Install'} />
</Button>
</div>
)
)}
{!community && (
<div className='vz-addon-card-toggle-wrapper'>
<Switch
className='vz-addon-card-toggle'
checked={isEnabled}
onChange={async v => onToggle(v)}
/>
</div>
)}
<div className='vz-addon-card-toggle-wrapper'>
<Switch
className='vz-addon-card-toggle'
value={isEnabled}
onChange={() => onToggle(!isEnabled)}
/>
</div>
</div>
</div>
</div>
@ -364,7 +436,7 @@ const AddonCardCompact = memo(({ addon, type, hasSettings, isEnabled, isInstalle
* @param {boolean} props.isInstalled Is installed
* @param {function()} props.onToggle Toggle handler
*/
const AddonCardCover = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggle }) => {
const AddonCardCover = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
return (
<div className='vz-addon-card-header-wrapper'>
<div className='vz-addon-card-content-wrapper'>
@ -373,9 +445,9 @@ const AddonCardCover = memo(({ addon, type, hasSettings, isEnabled, isInstalled,
<div className='vz-addon-card-header'>
<div className='vz-addon-card-metadata'>
<div className='vz-addon-card-name-version'>
<div className='vz-addon-card-name'>
<OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
{addon.manifest.name}
</div>
</OverflowTooltip>
<span className='vz-addon-card-version'>
{addon.manifest.version}
</span>
@ -386,6 +458,7 @@ const AddonCardCover = memo(({ addon, type, hasSettings, isEnabled, isInstalled,
<Description description={addon.manifest.description} />
<Permissions permissions={addon.manifest.permissions} />
<Footer
community={community}
addon={addon}
type={type}
hasSettings={hasSettings}
@ -410,20 +483,18 @@ const AddonCardCover = memo(({ addon, type, hasSettings, isEnabled, isInstalled,
* @param {boolean} props.isInstalled Is installed
* @param {function()} props.onToggle Toggle handler
*/
const AddonCardList = memo(({ addon, type, hasSettings, isEnabled, isInstalled, onToggle, showPreviewImages }) => {
const AddonCardList = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
return (
<div className='vz-addon-card-header-wrapper'>
{/* {showPreviewImages && <Previews {...props} />} */}
{!showPreviewImages && <AddonIcon icon={addon.manifest.icon} />}
<AddonIcon icon={addon.manifest.icon} />
<div className='vz-addon-card-content-wrapper'>
<div className='vz-addon-card-content'>
<div className='vz-addon-card-header'>
{/* {showPreviewImages && <AddonIcon icon={manifest.icon} />} */}
<div className='vz-addon-card-metadata'>
<div className='vz-addon-card-name-version'>
<div className='vz-addon-card-name'>
<OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
{addon.manifest.name}
</div>
</OverflowTooltip>
<span className='vz-addon-card-version'>
{addon.manifest.version}
</span>
@ -434,6 +505,7 @@ const AddonCardList = memo(({ addon, type, hasSettings, isEnabled, isInstalled,
<Description description={addon.manifest.description} />
<Permissions permissions={addon.manifest.permissions} />
<Footer
community={community}
addon={addon}
type={type}
hasSettings={hasSettings}
@ -455,8 +527,49 @@ const AddonCardList = memo(({ addon, type, hasSettings, isEnabled, isInstalled,
* @param {string} props.type Addon type
* @param {string} props.display Addon card display type
*/
export default memo(({ addonId, type, display, enabled, installed, settings, updates }) => {
const addon = vizality.manager[toPlural(type)].get(addonId);
export default memo(({ addonId, type, display, community, isEnabled, isInstalled, hasSettings, hasUpdates }) => {
isEnabled = typeof isEnabled === 'boolean' ? isEnabled : vizality.manager[toPlural(type)].isEnabled(addonId);
isInstalled = typeof isInstalled === 'boolean' ? isInstalled : vizality.manager[toPlural(type)].isInstalled(addonId);
hasSettings = typeof hasSettings === 'boolean' ? hasSettings : vizality.manager[toPlural(type)].hasSettings(addonId);
const forceUpdate = useForceUpdate();
/**
* Handles addon enables and disables.
* @param {object} payload Event payload
* @param {string} payload.addonId Addon ID
* @param {string} payload.type Addon type
*/
const handleEnableDisable = payload => {
/**
* If this is the addon being enabled/disabled, force rerender the card.
*/
if (payload.addonId === addonId && payload.type === type) {
forceUpdate();
}
};
/**
* Add event listeners for addon enable and addon disable.
*/
useEffect(() => {
vizality.manager[toPlural(type)].on(Events.VIZALITY_ADDON_ENABLE, handleEnableDisable);
vizality.manager[toPlural(type)].on(Events.VIZALITY_ADDON_DISABLE, handleEnableDisable);
return () => {
/**
* Remove the event listeners.
*/
vizality.manager[toPlural(type)].removeListener(Events.VIZALITY_ADDON_ENABLE, handleEnableDisable);
vizality.manager[toPlural(type)].removeListener(Events.VIZALITY_ADDON_DISABLE, handleEnableDisable);
};
}, [ type, addonId, display, community, isEnabled, isInstalled, hasSettings ]);
let addon;
if (community) {
addon = vizality.manager.community[toPlural(type)].get(addonId);
} else {
addon = vizality.manager[toPlural(type)].get(addonId);
}
if (!addon) {
return;
}
@ -469,58 +582,68 @@ export default memo(({ addonId, type, display, enabled, installed, settings, upd
}
};
/**
* Handles addon card context menus.
* @param {document#event:mousedown} evt Mousedown event
* @returns {void}
*/
const handleContextMenu = evt => {
return openContextMenu(evt, () =>
<AddonContextMenu
community={community}
addonId={addonId}
type={type}
hasSettings={vizality.manager[toPlural(type)].hasSettings(addonId)}
isEnabled={vizality.manager[toPlural(type)].isEnabled(addonId)}
isInstalled={vizality.manager[toPlural(type)].isInstalled(addonId)}
hasSettings={hasSettings}
isEnabled={isEnabled}
isInstalled={isInstalled}
onToggle={onToggle}
/>
);
};
const getAddonDisplayRender = display => {
const Addon = ({ display }) => {
switch (display) {
case 'compact': return (
<AddonCardCompact
community={community}
addon={addon}
type={type}
hasSettings={vizality.manager[toPlural(type)].hasSettings(addonId)}
isEnabled={vizality.manager[toPlural(type)].isEnabled(addonId)}
isInstalled={vizality.manager[toPlural(type)].isInstalled(addonId)}
hasSettings={hasSettings}
isEnabled={isEnabled}
isInstalled={isInstalled}
onToggle={onToggle}
/>
);
case 'cover': return (
<AddonCardCover
community={community}
addon={addon}
type={type}
hasSettings={vizality.manager[toPlural(type)].hasSettings(addonId)}
isEnabled={vizality.manager[toPlural(type)].isEnabled(addonId)}
isInstalled={vizality.manager[toPlural(type)].isInstalled(addonId)}
hasSettings={hasSettings}
isEnabled={isEnabled}
isInstalled={isInstalled}
onToggle={onToggle}
/>
);
case 'list': return (
<AddonCardList
community={community}
addon={addon}
type={type}
hasSettings={vizality.manager[toPlural(type)].hasSettings(addonId)}
isEnabled={vizality.manager[toPlural(type)].isEnabled(addonId)}
isInstalled={vizality.manager[toPlural(type)].isInstalled(addonId)}
hasSettings={hasSettings}
isEnabled={isEnabled}
isInstalled={isInstalled}
onToggle={onToggle}
/>
);
default: return (
<AddonCard
community={community}
addon={addon}
type={type}
hasSettings={vizality.manager[toPlural(type)].hasSettings(addonId)}
isEnabled={vizality.manager[toPlural(type)].isEnabled(addonId)}
isInstalled={vizality.manager[toPlural(type)].isInstalled(addonId)}
hasSettings={hasSettings}
isEnabled={isEnabled}
isInstalled={isInstalled}
onToggle={onToggle}
/>
);
@ -531,17 +654,16 @@ export default memo(({ addonId, type, display, enabled, installed, settings, upd
<div
className='vz-addon-card'
vz-addon-id={addonId}
vz-addon-type={type}
onContextMenu={handleContextMenu}
onClick={evt => {
/*
* if (evt.target.matches('input') || evt.target.matches('button') || evt.target.matches('svg') || evt.target.matches('a')) {
* return;
* }
* vizality.api.routes.navigateTo(`/${toPlural(type)}/${addonId}`);
*/
if (evt.target.matches('input') || evt.target.matches('button') || evt.target.matches('svg') || evt.target.matches('a')) {
return;
}
vizality.api.routes.navigateTo(`/vizality/${type}/${addonId}`);
}}
>
{getAddonDisplayRender(display)}
<Addon display={display} />
</div>
);
});

@ -1,53 +1,76 @@
import React, { memo, useState } from 'react';
import { ContextMenu, LazyImage } from '@vizality/components';
import { toPlural } from '@vizality/util/string';
import { contextMenu } from '@vizality/webpack';
import React, { memo, useState } from 'react';
import { Messages } from '@vizality/i18n';
import { toPlural } from '@vizality/util/string';
const { closeContextMenu } = contextMenu;
/**
* Context menu for addons.
* Context menu with items corresponding to an addon.
* @component
* @param {object} props
* @param {string} props.addonId Addon ID
* @param {string} props.type Addon type
* @param {Addon#addonId} props.addonId Addon ID
* @param {AddonType} props.type Addon type
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export default memo(({ addonId, type }) => {
const [ enabled, setEnabled ] = useState(vizality.manager[toPlural(type)].isEnabled(addonId));
const [ installed, setInstalled ] = useState(vizality.manager[toPlural(type)].isInstalled(addonId));
const [ isEnabled, setEnabled ] = useState(vizality.manager[toPlural(type)].isEnabled(addonId));
const [ isInstalled, setInstalled ] = useState(vizality.manager[toPlural(type)].isInstalled(addonId));
const hasSettings = vizality.manager[toPlural(type)].hasSettings(addonId);
const hasScreenshots = false;
const hasChangelog = false;
return (
<ContextMenu.Menu navId='vz-addon-context-menu' onClose={closeContextMenu}>
{enabled
? <ContextMenu.Item
id='disable'
label='Disable'
action={async () => {
await vizality.manager[toPlural(type)].disable(addonId);
setEnabled(false);
}}
/>
: <ContextMenu.Item
id='enable'
label='Enable'
action={async () => {
await vizality.manager[toPlural(type)].enable(addonId);
setEnabled(true);
}}
/>
}
<ContextMenu.Item
id='settings'
label='Settings'
action={() => void 0}
/>
<ContextMenu.Item
id='details'
label='Details'
action={() => void 0}
id='overview'
label='Overview'
action={() => vizality.api.routes.navigateTo(`${toPlural(type)}/${addonId}`)}
/>
{installed
{hasScreenshots && (
<ContextMenu.Item
id='screenshots'
label='Screenshots'
action={() => vizality.api.routes.navigateTo(`${toPlural(type)}/${addonId}/screenshots`)}
/>
)}
{hasChangelog && (
<ContextMenu.Item
id='changelog'
label='Changelog'
action={() => vizality.api.routes.navigateTo(`${toPlural(type)}/${addonId}/changelog`)}
/>
)}
{isInstalled && hasSettings && (
<ContextMenu.Item
id='settings'
label='Settings'
action={() => vizality.api.routes.navigateTo(`${toPlural(type)}/${addonId}/settings`)}
/>
)}
<ContextMenu.Separator />
{isInstalled && (
isEnabled
? <ContextMenu.Item
id='disable'
label='Disable'
color={ContextMenu.Item.Colors.DANGER}
action={async () => {
await vizality.manager[toPlural(type)].disable(addonId);
setEnabled(false);
}}
/>
: <ContextMenu.Item
id='enable'
label='Enable'
action={async () => {
await vizality.manager[toPlural(type)].enable(addonId);
setEnabled(true);
}}
/>
)}
{isInstalled
? <ContextMenu.Item
id='uninstall'
label='Uninstall'
@ -62,6 +85,7 @@ export default memo(({ addonId, type }) => {
label='Install'
color={ContextMenu.Item.Colors.GREEN}
action={async () => {
await vizality.manager[toPlural(type)].install(addonId);
setInstalled(true);
}}
/>
@ -75,7 +99,7 @@ export default memo(({ addonId, type }) => {
<ContextMenu.Item
id='copy-id'
label='Copy ID'
action={() => void 0}
action={() => DiscordNative.clipboard.copy(addonId)}
/>
</ContextMenu.Menu>
);

@ -1,15 +1,16 @@
import { Text, LazyImage } from '..';
import React, { memo } from 'react';
import { Text, LazyImage } from '..';
/**
* Renders a message regarding an addon, including the addon icon and name. Typically used in informational toast notifications and modal alerts.
* Message blurb regarding an addon, including the addon icon and name.
* Typically used in notifications and modal confirmations.
* @component
* @param {object} props
* @param {string|React.Component|function(): React.ReactElement} props.message Message text
* @param {object} [props.addon] Addon
* @param {string} [props.iconSize=20] Addon icon size (in pixels)
* @param {Addon} addon Addon
* @param {string|React.Component|function(): React.ReactElement} message Message blurb
* @param {number} [iconSize=20] Addon icon size (in pixels)
*/
export default memo(({ message, addon, iconSize }) => {
export default memo(({ addon, message, iconSize }) => {
iconSize = iconSize || '20';
return (
<Text>
@ -17,13 +18,13 @@ export default memo(({ message, addon, iconSize }) => {
{message}
</span>
<ul className='vz-addon-info-message-ul'>
<li className='vz-addon-info-message-li' vz-addon-id={addon?.id} key={addon?.id}>
<li className='vz-addon-info-message-li' vz-addon-id={addon?.addonId} key={addon?.addonId}>
<div className='vz-addon-info-message-icon'>
{addon && (
<LazyImage
className='vz-addon-info-message-icon-image-wrapper'
imageClassName='vz-addon-info-message-icon-img'
src={addon?.manifest?.icon}
src={addon.manifest.icon}
width={iconSize}
height={iconSize}
/>

@ -1,15 +1,14 @@
import { Spinner, DeferredRender, StickyWrapper, Icon, SearchBar, FilterInput, HelpMessage } from '@vizality/components';
import React, { memo, useState, useEffect, useRef } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
import { Spinner, DeferredRender, Icon, FilterInput, HelpMessage, __TabBar, StickyElement } from '@vizality/components';
import { useForceUpdate, usePreviousProps, useFilter } from '@vizality/hooks';
import { toPlural, toTitleCase } from '@vizality/util/string';
import React, { memo, useState, useEffect } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
import { joinClassNames } from '@vizality/util/dom';
import { useForceUpdate, usePrevious, useFilter, useSticky } from '@vizality/hooks';
import { getModule } from '@vizality/webpack';
import { error } from '@vizality/util/logger';
import { Events } from '@vizality/constants';
import { useRouteMatch } from 'react-router';
import { Messages } from '@vizality/i18n';
import Sticky from 'react-stickynode';
import { motion } from 'framer-motion';
import SortFilterMenu from './menus/SortFilterMenu';
import OverflowMenu from './menus/OverflowMenu';
@ -49,6 +48,7 @@ const FillerAddonCards = memo(() => {
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
const SearchResultsHeader = memo(({ query, resultsCount, limit, type, tab }) => {
const { header } = getModule('header', 'item', 'separator');
const { marginTop20 } = getModule('marginTop20');
return (
<>
@ -58,7 +58,7 @@ const SearchResultsHeader = memo(({ query, resultsCount, limit, type, tab }) =>
</HelpMessage>
)}
<div className='vz-addons-list-search-results-text-wrapper'>
<div className='vz-addons-list-search-results-text'>
<div className={joinClassNames('vz-addons-list-search-results-text', header)}>
<span className='vz-addons-list-search-results-count'>{resultsCount}</span> {toPlural(type)} found{!query && limit && resultsCount > limit && `... Showing ${limit}.`} {query && query !== '' && <>
matching "<span className='vz-addons-list-search-results-matched'>{query}</span>"{limit && resultsCount > limit && `... Showing ${limit}.`}
</>}
@ -123,7 +123,6 @@ const ContentBody = memo(({ community, display, showBanners, filteredResults, ty
*/
const StickyBar = memo(({ query, type, showBanners, resetSearchOptions, handleShowBanners, display, handleTabChange, handleClearQuery, handleQueryChange, handleDisplayChange }) => {
const [ sticky, setSticky ] = useState(false);
const headerRef = useRef(null);
const PopoutDispatcher = getModule('openPopout');
/**
@ -135,35 +134,6 @@ const StickyBar = memo(({ query, type, showBanners, resetSearchOptions, handleSh
return `Layout${toTitleCase(display).replace(' ', '')}`;
};
useEffect(() => {
const header = headerRef?.current;
const observer = new IntersectionObserver(
([ element ]) => {
/**
* e is our target element -- the header;
* other properties available include:
* boundingClientRect
* intersectionRatio
* intersectionRect
* rootBounds
* target
* time
*/
setSticky(element.isIntersecting < 1);
},
{ threshold: [ 1 ] }
);
if (header) {
observer.observe(header);
}
// clean up the observer
return (() => {
observer.unobserve(header);
});
}, [ headerRef ]);
/**
* Configuration for the popouts used in this component.
*/
@ -235,67 +205,73 @@ const StickyBar = memo(({ query, type, showBanners, resetSearchOptions, handleSh
};
return (
<motion.div
layout
ref={headerRef}
className={joinClassNames('vz-sticky-wrapper', 'vz-addons-list-sticky-bar-wrapper', { stuck: sticky })}
<StickyElement
wrapperClassName='vz-addons-list-sticky-bar-wrapper'
className='vz-addons-list-sticky-bar'
>
<div className={joinClassNames('vz-sticky', 'vz-addons-list-sticky-bar')}>
<div type='top-28JiJ-'>
<div onClick={() => handleTabChange('installed')} vz-tab='installed'>
{Messages.VIZALITY_INSTALLED}
</div>
<div onClick={() => handleTabChange('discover')} vz-tab='discover'>
{Messages.DISCOVER}
</div>
<div onClick={() => handleTabChange('browse')} vz-tab='browse'>
Browse
</div>
<__TabBar type='top-pill'>
<__TabBar.NavItem
route={`/vizality/${toPlural(type)}/installed`}
selected={tab === 'installed'}
onClick={() => handleTabChange('installed')}
>
{Messages.VIZALITY_INSTALLED}
</__TabBar.NavItem>
<__TabBar.NavItem
route={`/vizality/${toPlural(type)}/discover`}
>
{Messages.DISCOVER}
</__TabBar.NavItem>
<__TabBar.NavItem
route={`/vizality/${toPlural(type)}/browse`}
selected={tab === 'browse'}
onClick={() => handleTabChange('browse')}
>
Browse
</__TabBar.NavItem>
</__TabBar>
<div className='vz-addons-list-search-options'>
<div className='vz-addons-list-search'>
<FilterInput
value={query}
onChange={handleQueryChange}
/>
</div>
<div className='vz-addons-list-search-options'>
<div className='vz-addons-list-search'>
<FilterInput
value={query}
onChange={handleQueryChange}
fuzzy
/>
</div>
<div className='vz-addons-list-filter-button vz-addons-list-search-options-button'>
<Icon
tooltip={`Sort & Filter`}
size='20'
tooltipPosition={sticky ? 'bottom' : 'top'}
name='FilterAlt'
onClick={renderSortFilterMenu}
/>
</div>
<div className='vz-addons-list-tags-button vz-addons-list-search-options-button'>
<Icon
tooltip='Tags'
name='StoreTag'
tooltipPosition={sticky ? 'bottom' : 'top'}
onClick={renderTagsMenu}
/>
</div>
<div className='vz-addons-list-display-button vz-addons-list-search-options-button'>
<Icon
tooltip='Display'
tooltipPosition={sticky ? 'bottom' : 'top'}
name={formatDisplayIconName(display)}
onClick={renderDisplayMenu}
/>
</div>
<div className='vz-addons-list-more-button vz-addons-list-search-options-button'>
<Icon
tooltip={Messages.MORE}
tooltipPosition={sticky ? 'bottom' : 'top'}
name='OverflowMenu'
onClick={renderOverflowMenu}
/>
</div>
<div className='vz-addons-list-filter-button vz-addons-list-search-options-button'>
<Icon
tooltip={`Sort & Filter`}
size='20'
tooltipPosition={sticky ? 'bottom' : 'top'}
name='FilterAlt'
onClick={renderSortFilterMenu}
/>
</div>
<div className='vz-addons-list-tags-button vz-addons-list-search-options-button'>
<Icon
tooltip='Tags'
name='StoreTag'
tooltipPosition={sticky ? 'bottom' : 'top'}
onClick={renderTagsMenu}
/>
</div>
<div className='vz-addons-list-display-button vz-addons-list-search-options-button'>
<Icon
tooltip='Display'
tooltipPosition={sticky ? 'bottom' : 'top'}
name={formatDisplayIconName(display)}
onClick={renderDisplayMenu}
/>
</div>
<div className='vz-addons-list-more-button vz-addons-list-search-options-button'>
<Icon
tooltip={Messages.MORE}
tooltipPosition={sticky ? 'bottom' : 'top'}
name='OverflowMenu'
onClick={renderOverflowMenu}
/>
</div>
</div>
</motion.div>
</StickyElement>
);
});
@ -304,17 +280,20 @@ const StickyBar = memo(({ query, type, showBanners, resetSearchOptions, handleSh
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export default memo(({ source, type, tab, search, display, limit, showBanners, className, showOptionsBar = true }) => {
const { getSetting, updateSetting } = vizality.api.settings._fluxProps();
const [ currentSource, setCurrentSource ] = useState(source || (type === 'plugin' || type === 'theme' ? [ ...vizality.manager[toPlural(type)].values ] : null));
const [ currentTab, setCurrentTab ] = useState(tab || 'installed');
const { path, url } = useRouteMatch();
type = type || (/(\/plugins\/)/).test(path) ? 'plugin' : (/(\/themes\/)/).test(path) ? 'theme' : 'plugin';
const { getSetting, updateSetting } = vizality.api.settings._fluxProps('addon-manager');
const [ currentTab, setCurrentTab ] = useState(tab || (url?.replace(`/vizality/${toPlural(type)}/`, '')));
community = community || currentTab === 'browse';
const [ currentSource, setCurrentSource ] = useState(source || (type === 'plugin' || type === 'theme' ? [ ...vizality.manager[toPlural(type)].values ] : currentTab === 'browse' ? [ ...vizality.manager.community[toPlural(type)].values() ] : [ ...vizality.manager[toPlural(type)].values ]));
const [ displayType, setDisplay ] = useState(display || getSetting('listDisplay', 'card'));
const [ banners, setShowBanners ] = useState(showBanners || getSetting('showBanners', false));
const [ query, setQuery, filteredResults ] = useFilter({
keys: [ 'manifest.name', 'manifest.author.name', 'manifest.author', 'manifest.description' ],
data: currentSource
});
const [ resultsCount, setResultsCount ] = useState(filteredResults.length || 0);
const prevSource = usePrevious(source);
const [ resultsCount, setResultsCount ] = useState(filteredResults?.length || 0);
const prevSource = usePreviousProps(source);
const forceUpdate = useForceUpdate();
const { colorStandard } = getModule('colorStandard');
@ -328,6 +307,10 @@ export default memo(({ source, type, tab, search, display, limit, showBanners, c
};
useEffect(() => {
console.log('currentTab', currentTab);
console.log('type', type);
console.log(source);
console.log(currentSource);
/**
* There is an issue where it doesn't update currentSource when switching from one
* page to another (Plugins to Themes and Themes to Plugins), but it does properly
@ -351,15 +334,11 @@ export default memo(({ source, type, tab, search, display, limit, showBanners, c
};
}, [ source, type, tab, display, search, limit, showBanners, displayType, currentTab, banners, currentSource, resultsCount ]);
/*
* Including these in this component so we can forceUpdate the switches.
* There's probably a better way to do it.
*/
const resetSearchOptions = () => {
return setQuery('');
};
/**
* @todo Need to come up with a better way to handle tab changes. Hardcoding the source
* update works here, but breaks the modularity of the List component so it can't really
* be adapted for other things.
*
* Handles tab changes.
* @param {string} tab Content tab
*/
@ -405,10 +384,6 @@ export default memo(({ source, type, tab, search, display, limit, showBanners, c
return setQuery(typeof query === 'string' ? query : '');
};
const handleClearQuery = () => {
return setQuery('');
};
return (
<div
className={joinClassNames('vz-addons-list', className, colorStandard)}
@ -424,7 +399,6 @@ export default memo(({ source, type, tab, search, display, limit, showBanners, c
display={displayType}
handleTabChange={handleTabChange}
handleQueryChange={handleQueryChange}
handleClearQuery={handleClearQuery}
handleDisplayChange={handleDisplayChange}
resetSearchOptions={resetSearchOptions}
getSetting={getSetting}
@ -451,7 +425,7 @@ export default memo(({ source, type, tab, search, display, limit, showBanners, c
/>
<ContentBody
filteredResults={filteredResults}
community={currentTab === 'browse'}
community={community}
display={displayType}
showBanners={banners}
type={type}

Loading…
Cancel
Save