Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Market: Select addon version to download #445

Merged
merged 5 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/components/MarketAddonCard/MarketAddonCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,37 @@ const MarketAddonCard = ({
isSelected,
isOfficial,
isVerified,
isInstalled,
isDownloaded,
isOutdated,
isPlaceholder,
isWaiting, // waiting to be installed/updated by update all
isInstalling,
isWaiting, // waiting to be downloaded/updated by update all
isDownloading,
isFailed,
isFinished,
onInstall,
onDownload,
...props
}) => {
let state = 'download'
if (isInstalled && !isOutdated) state = 'downloaded'
if (isInstalled && isOutdated) state = 'update'
if (isDownloaded && !isOutdated) state = 'downloaded'
if (isDownloaded && isOutdated) state = 'update'
if (isWaiting) state = 'pending'
if (isInstalling) state = isInstalled && isOutdated ? 'updating' : 'downloading'
if (isDownloading) state = isDownloaded && isOutdated ? 'updating' : 'downloading'
if (isFailed) state = 'failed'
if (isFinished) state = 'finished'

let stateIcon = null
if (isInstalling) stateIcon = 'sync'
if (isDownloading) stateIcon = 'sync'
if (isFailed) stateIcon = 'error'
if (isFinished) stateIcon = 'check_circle'

let stateVariant = 'light'
if (state === 'install') stateVariant = 'surface'
if (state === 'download') stateVariant = 'surface'
if (state === 'failed') stateVariant = 'danger'
if (state === 'update') stateVariant = 'filled'

const handleActionClick = () => {
if (['download', 'update'].includes(state)) {
onInstall(name, latestVersion)
onDownload(name, latestVersion)
}
}

Expand Down
84 changes: 64 additions & 20 deletions src/pages/MarketPage/AddonDetails/AddonDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, Icon, SaveButton } from '@ynput/ayon-react-components'
import React, { useState } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import * as Styled from './AddonDetails.styled'
import Type from '@/theme/typography.module.css'
import { classNames } from 'primereact/utils'
Expand All @@ -25,23 +25,23 @@ const MetaPanelRow = ({ label, children, valueDirection = 'column', ...props })
</Styled.MetaPanelRow>
)

const AddonDetails = ({ addon = {}, isLoading, onInstall, isUpdatingAll }) => {
const AddonDetails = ({ addon = {}, isLoading, onDownload, isUpdatingAll }) => {
// latestVersion: is the latest version of the addon
// versions: is an array of all versions INSTALLED of the addon
// versions: is an array of all versions DOWNLOADED of the addon
const {
name,
title,
description,
icon,
isInstalled,
isInstalling,
isDownloaded,
isDownloading,
isFinished,
isFailed,
error,
isOutdated,
isProductionOutdated,
// versions = [],
installedVersions = {},
versions = [],
downloadedVersions = {},
latestVersion,
currentLatestVersion,
currentProductionVersion,
Expand All @@ -51,10 +51,21 @@ const AddonDetails = ({ addon = {}, isLoading, onInstall, isUpdatingAll }) => {
warning,
} = addon

const versionKeys = isEmpty(downloadedVersions) ? [] : Object.keys(downloadedVersions)
// keep track of downloaded versions
const [downloadedByAddon, setDownloadedByAddon] = useState({})
const downloaded = downloadedByAddon[name] || []

useEffect(() => {
setDownloadedByAddon((v) => ({
...v,
[name]: [...new Set([...(v[name] || []), ...versionKeys])],
}))
}, [name, setDownloadedByAddon])

const [showAllVersions, setShowAllVersions] = useState(false)

const versionKeys = isEmpty(installedVersions) ? [] : Object.keys(installedVersions)
const versionKeysSorted = versionKeys.sort((a, b) => rcompare(a, b))
const versionKeysSorted = downloaded.sort((a, b) => rcompare(a, b))
const versionsToShow = versionKeysSorted.length
? showAllVersions
? versionKeysSorted
Expand All @@ -73,16 +84,20 @@ const AddonDetails = ({ addon = {}, isLoading, onInstall, isUpdatingAll }) => {
// sets selected addon and redirects to addons
const { onUninstall } = useUninstall(name)

// All the install logic is handled in the parent component (MarketPage.jsx)
// Okay it's actually handled in the hook useInstall.js
const handleInstall = () => {
onInstall && onInstall(name, latestVersion)
// All the download logic is handled in the parent component (MarketPage.jsx)
// Okay it's actually handled in the hook useDownload.js
const handleDownload = (version) => {
onDownload && onDownload(name, version)
// update downloaded versions
if (!downloaded.includes(version)) {
setDownloadedByAddon((v) => ({ ...v, [name]: [...(v[name] || []), version] }))
}
}

let actionButton = null

// Install button (top right)
if (isInstalling) {
// Download button (top right)
if (isDownloading) {
actionButton = (
<SaveButton active saving disabled>
Downloading...
Expand All @@ -100,24 +115,34 @@ const AddonDetails = ({ addon = {}, isLoading, onInstall, isUpdatingAll }) => {
Pending...
</Button>
)
} else if (isInstalled && !isOutdated) {
} else if (isDownloaded && !isOutdated) {
actionButton = <Button onClick={onUninstall}>Uninstall</Button>
} else if (isInstalled && isOutdated && latestVersion) {
} else if (isDownloaded && isOutdated && latestVersion) {
actionButton = (
<Button
variant="filled"
icon={'download'}
onClick={handleInstall}
onClick={() => handleDownload(latestVersion)}
>{`Download v${latestVersion}`}</Button>
)
} else if (latestVersion) {
actionButton = (
<Button variant="filled" icon={'download'} onClick={handleInstall}>
<Button variant="filled" icon={'download'} onClick={() => handleDownload(latestVersion)}>
{`Download v${latestVersion}`}
</Button>
)
}

const versionsOptions = useMemo(
() =>
versions.map((v) => ({
value: v.version,
label: `v${v.version}`,
isDownloaded: downloaded.includes(v.version),
})),
[versions, downloaded],
)

// query string used for duplicating bundles with new version
const addonVersionObject = { [name]: currentLatestVersion }
const duplicateQueryString = encodeURIComponent(JSON.stringify(addonVersionObject))
Expand Down Expand Up @@ -156,7 +181,26 @@ const AddonDetails = ({ addon = {}, isLoading, onInstall, isUpdatingAll }) => {
</Styled.Left>
{/* RIGHT PANEL */}
<Styled.Right className={classNames(Type.bodyMedium, { isLoading })}>
{actionButton}
<Styled.Download>
{actionButton}

<Styled.VersionDropdown
options={versionsOptions}
align="right"
value={[]}
widthExpand
onChange={(v) => handleDownload(v[0])}
itemStyle={{ justifyContent: 'space-between' }}
buttonProps={{ 'data-tooltip': 'Download a specific version' }}
search={versions.length > 10}
itemTemplate={(option) => (
<Styled.VersionDropdownItem>
<Icon icon={option.isDownloaded ? 'check' : 'download'} />
{option.label}
</Styled.VersionDropdownItem>
)}
/>
</Styled.Download>
<Styled.MetaPanel className={classNames({ isPlaceholder: isLoading })}>
<MetaPanelRow label="Downloaded Versions">
{versionsToShow.length
Expand Down
39 changes: 38 additions & 1 deletion src/pages/MarketPage/AddonDetails/AddonDetails.styled.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Panel, getShimmerStyles } from '@ynput/ayon-react-components'
import { Button, Dropdown, Panel, getShimmerStyles } from '@ynput/ayon-react-components'
import ReactMarkdown from 'react-markdown'
import styled from 'styled-components'

Expand Down Expand Up @@ -49,6 +49,43 @@ export const Right = styled.div`
}
`

export const Download = styled.div`
display: flex;
gap: var(--base-gap-small);
width: 100%;

button {
flex: 1;
}
`

export const VersionDropdown = styled(Dropdown)`
button {
background-color: var(--md-sys-color-surface-container-highest);
&:hover {
background-color: var(--md-sys-color-surface-container-highest-hover);
}

& > div {
padding: 0 6px;
border: none;
& > div {
display: none;
}
}
}
`

export const VersionDropdownItem = styled.div`
display: flex;
gap: var(--base-gap-large);
align-items: center;
justify-content: space-between;
height: 32px;
padding: 0px 8px;
padding-right: 16px;
`

// header is in the left column and contains icon, title and verification status
export const Header = styled.div`
display: flex;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useState } from 'react'
import { useInstallAddonsMutation } from '@queries/addons/updateAddons'
import { useDownloadAddonsMutation } from '@queries/addons/updateAddons'
import { useLazyMarketAddonVersionDetailQuery } from '@queries/market/getMarket'
import { toast } from 'react-toastify'

const useInstall = (onInstall) => {
const useDownload = (onDownload) => {
const [error, setError] = useState(null)

const [installAddons] = useInstallAddonsMutation()
const [downloadAddons] = useDownloadAddonsMutation()
const [getAddonVersion] = useLazyMarketAddonVersionDetailQuery()

const installAddon = async (name, version) => {
const downloadAddon = async (name, version) => {
try {
if (!version) return new Error('No version found')
if (!name) return new Error('No name found')
Expand All @@ -19,28 +19,28 @@ const useInstall = (onInstall) => {

if (error) throw new Error(error.message)

if (!data?.url) throw new Error('No install candidate found')
if (!data?.url) throw new Error('No download candidate found')

const { error: installError } = await installAddons({
const { error: downloadError } = await downloadAddons({
addons: [{ url: data.url, name, version }],
}).unwrap()

if (installError) throw new Error(installError)
if (downloadError) throw new Error(downloadError)

onInstall(name)
onDownload(name)
} catch (error) {
console.error(error)

setError(error?.message || 'Error installing addon')
setError(error?.message || 'Error downloading addon')

toast.error(error?.message || 'Error installing addon')
toast.error(error?.message || 'Error downloading addon')
}
}

return {
installAddon,
downloadAddon,
error,
}
}

export default useInstall
export default useDownload
14 changes: 7 additions & 7 deletions src/pages/MarketPage/AddonFilters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const StyledList = styled(Panel)`
`

const AddonFilters = ({ onSelect, onConnection }) => {
const installFilters = [
const downloadFilters = [
{
id: 'all',
name: 'All',
Expand All @@ -47,28 +47,28 @@ const AddonFilters = ({ onSelect, onConnection }) => {
{
id: 'updates',
name: 'Updates Available',
filter: [{ isOutdated: true }, { isInstalled: true }],
filter: [{ isOutdated: true }, { isDownloaded: true }],
tooltip: 'Addons with updates available',
},
{
id: 'production',
name: 'In Production',
filter: [{ currentProductionVersion: (v) => v }, { isInstalled: true }],
filter: [{ currentProductionVersion: (v) => v }, { isDownloaded: true }],
tooltip: 'Addons used in the production bundle',
},
{
id: 'production-outdated',
name: 'Production Outdated',
filter: [
{ isProductionOutdated: true, isInstalled: true, currentProductionVersion: (v) => v },
{ isProductionOutdated: true, isDownloaded: true, currentProductionVersion: (v) => v },
],
tooltip: 'Addons using an outdated version in the production bundle',
},

{
id: 'uninstalled',
name: 'Downloads Available',
filter: [{ isInstalled: false }],
filter: [{ isDownloaded: false }],
tooltip: 'Addons available to download',
},
]
Expand All @@ -83,8 +83,8 @@ const AddonFilters = ({ onSelect, onConnection }) => {
return (
<StyledSection>
<StyledList>
<div className={classNames('title', Type.titleMedium)}>Installed</div>
{installFilters.map((filter) => (
<div className={classNames('title', Type.titleMedium)}>Downloaded</div>
{downloadFilters.map((filter) => (
<div
key={filter.id}
className={classNames('item', { isSelected: selected === filter.id })}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/MarketPage/MarketAddonsList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const MarketAddonsList = ({
selected,
onSelect,
onHover,
onInstall,
onDownload,
isLoading,
onUpdateAll,
isUpdatingAll,
Expand Down Expand Up @@ -127,7 +127,7 @@ const MarketAddonsList = ({
onClick={() => onSelect(name)}
isSelected={selected === name}
onMouseOver={() => onHover(name)}
onInstall={onInstall}
onDownload={onDownload}
name={name}
{...props}
/>
Expand Down
Loading