Skip to content

Commit

Permalink
feat: Handle and show errors when uploading file (#2425)
Browse files Browse the repository at this point in the history
* feat: Add max emote duration error to the ItemsCreationStep

* feat: Add translations

* fix: 'CreateSingleItemModa' dimmed too much when setting the price to emotes

* feat: Update error messages

* feat: Refactor the getItemData method to get the emote metrics without a WearablePreview component

* chore: Update pre-commit TS_FILES filter
  • Loading branch information
cyaiox authored Nov 21, 2022
1 parent 26856ea commit 6af1769
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fi
echo "Running prettier"
npm run pre-commit:fix:prettier -- $FILES

TS_FILES=$(echo $FILES | { grep -E '(js|ts|tsx)$' || true; })
TS_FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g' | { grep -E '(js|ts|tsx)$' || true; })

if [[ ! -z "$TS_FILES" ]];then
echo "Running lints"
Expand Down
2 changes: 1 addition & 1 deletion src/components/FileImport/FileImport.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
align-items: center;
justify-content: center;
flex-direction: column;
border-radius: 6px;
border-radius: 8px;
height: 276px;
width: 100%;
background-color: rgba(var(--dark-raw), 0.48);
Expand Down
33 changes: 33 additions & 0 deletions src/components/ItemImport/ItemImport.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.zipInfo {
position: absolute;
width: 100%;
left: 0;
bottom: 32px;
color: var(--secondary-text);
display: flex;
align-items: center;
Expand All @@ -18,3 +22,32 @@
margin-top: 15px;
width: 100%;
}

.errorContainer {
display: flex;
position: absolute;
top: 0;
left: 0;
padding: 24px 30px;
margin: 16px;
width: calc(100% - 32px);
border-radius: 8px;
background-color: rgba(115, 110, 125, 0.16);
align-items: center;
text-align: justify;
}

.errorMessage {
font-size: 15px;
font-weight: 400;
line-height: 24px;
}

.errorIcon {
margin-right: 24px;
height: 48px;
width: 48px;
background: url('../../icons/alert.svg') no-repeat;
background-size: contain;
filter: invert(65%) sepia(60%) saturate(599%) hue-rotate(323deg) brightness(104%) contrast(98%); /* #fc9965 */
}
13 changes: 7 additions & 6 deletions src/components/ItemImport/ItemImport.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { t, T } from 'decentraland-dapps/dist/modules/translation/utils'
import { Loader, Row } from 'decentraland-ui'
import { Loader } from 'decentraland-ui'
import FileImport from 'components/FileImport'
import { InfoIcon } from 'components/InfoIcon'
import { Props } from './ItemImport.types'
Expand All @@ -19,6 +19,12 @@ export default class ItemImport extends React.PureComponent<Props, any> {
<Loader active size="big" />
</div>
) : null}
{error && (
<div className={styles.errorContainer}>
<div className={styles.errorIcon} />
<div className={styles.errorMessage}>{error}</div>
</div>
)}
<T
id="asset_pack.import.cta"
values={{
Expand All @@ -34,11 +40,6 @@ export default class ItemImport extends React.PureComponent<Props, any> {
)
}}
/>
{error ? (
<Row className={styles.error} align="center">
<p className="danger-text">{error}</p>
</Row>
) : null}
{moreInformation ? (
<div className={styles.zipInfo}>
<InfoIcon className={styles.infoIcon} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
.ui.modal.CreateSingleItemModal > .content {
/* This CSS is used to hide the CreateSingleItem modal when setting the price for an Item */
.CreateSingleItemModalContainer > .ui.page.modals:not(:only-child):nth-child(1) {
visibility: hidden !important;
}

.CreateSingleItemModal.ui.modal > .content {
margin-bottom: 32px;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import './CreateSingleItemModal.css'
export default class CreateSingleItemModal extends React.PureComponent<Props, State> {
state: State = this.getInitialState()
thumbnailInput = React.createRef<HTMLInputElement>()
modalContainer = React.createRef<HTMLDivElement>()

getInitialState() {
const { metadata } = this.props
Expand Down Expand Up @@ -594,11 +595,11 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
this.setState({ previewController: controller })

if (weareblePreviewUpdated) {
await this.getMetricsAndScreenshot()
if (type === ItemType.EMOTE) {
const length = await controller.emote.getLength()
await controller.emote.goTo(Math.floor(Math.random() * length))
}
return this.getMetricsAndScreenshot()
}
}

Expand Down Expand Up @@ -640,7 +641,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St

return (
<ImportStep
category={category as WearableCategory}
category={category as WearableCategory | EmoteCategory}
metadata={metadata}
title={title}
wearablePreviewComponent={<div className="importer-thumbnail-container">{this.renderWearablePreview()}</div>}
Expand Down Expand Up @@ -973,6 +974,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
item={item!}
itemSortedContents={itemSortedContents}
onClose={onClose}
mountNode={this.modalContainer.current ?? undefined}
// If the Set Price step is skipped, the item must be saved
onSkip={this.handleSubmit}
/>
Expand All @@ -997,9 +999,11 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
render() {
const { name, onClose } = this.props
return (
<Modal name={name} onClose={onClose}>
{this.renderView()}
</Modal>
<div ref={this.modalContainer} className="CreateSingleItemModalContainer">
<Modal name={name} onClose={onClose} mountNode={this.modalContainer.current ?? undefined}>
{this.renderView()}
</Modal>
</div>
)
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import * as React from 'react'
import uuid from 'uuid'
import { loadFile, WearableConfig } from '@dcl/builder-client'
import { loadFile, WearableCategory, WearableConfig } from '@dcl/builder-client'
import { ModalNavigation } from 'decentraland-ui'
import Modal from 'decentraland-dapps/dist/containers/Modal'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { getExtension } from 'lib/file'
import { EngineType, getIsEmote } from 'lib/getModelData'
import { EngineType, getEmoteMetrics, getIsEmote } from 'lib/getModelData'
import { cleanAssetName, rawMappingsToObjectURL } from 'modules/asset/utils'
import { FileTooBigError, WrongExtensionError, InvalidFilesError, MissingModelFileError } from 'modules/item/errors'
import {
FileTooBigError,
WrongExtensionError,
InvalidFilesError,
MissingModelFileError,
EmoteDurationTooLongError,
InvalidModelFilesRepresentation
} from 'modules/item/errors'
import { BodyShapeType, IMAGE_EXTENSIONS, Item, ItemType, ITEM_EXTENSIONS, MODEL_EXTENSIONS } from 'modules/item/types'
import {
getBodyShapeType,
Expand All @@ -18,9 +25,11 @@ import {
isImageFile,
isModelFile,
isModelPath,
MAX_FILE_SIZE
MAX_FILE_SIZE,
MAX_EMOTE_DURATION
} from 'modules/item/utils'
import { blobToDataURL } from 'modules/media/utils'
import { AnimationMetrics } from 'modules/models/types'
import ItemImport from 'components/ItemImport'
import { AcceptedFileProps, ModelData } from '../CreateSingleItemModal.types'
import { Props, State } from './ImportStep.types'
Expand Down Expand Up @@ -100,7 +109,16 @@ export default class ImportStep extends React.PureComponent<Props, State> {
[modelPath]: file
}

return this.processModel(modelPath, contents)
const { model, contents: proccessedContent, type } = await this.processModel(modelPath, contents)

if (type === ItemType.EMOTE) {
const info: AnimationMetrics = await getEmoteMetrics(contents[model])
if (info.duration > MAX_EMOTE_DURATION) {
throw new EmoteDurationTooLongError()
}
}

return { model, contents: proccessedContent, type }
}

handleDropAccepted = async (acceptedFiles: File[]) => {
Expand Down Expand Up @@ -171,7 +189,7 @@ export default class ImportStep extends React.PureComponent<Props, State> {
if (!isRepresentation) {
acceptedFileProps.contents = this.cleanContentModelKeys(contents, BodyShapeType.BOTH)
} else {
throw new Error(t('create_single_item_modal.error.invalid_model_files_representation'))
throw new InvalidModelFilesRepresentation()
}
}
}
Expand All @@ -192,7 +210,6 @@ export default class ImportStep extends React.PureComponent<Props, State> {
...acceptedFileProps,
bodyShape: isEmote ? BodyShapeType.BOTH : acceptedFileProps.bodyShape
})
this.setState({ error: '', isLoading: false })
} catch (error) {
this.setState({ error: error.message, isLoading: false })
}
Expand Down Expand Up @@ -223,6 +240,20 @@ export default class ImportStep extends React.PureComponent<Props, State> {
return { model, contents, type: isEmote ? ItemType.EMOTE : ItemType.WEARABLE }
}

renderMoreInformation() {
return (
<span>
{t('create_single_item_modal.import_information', {
link: (
<a href="https://docs.decentraland.org/decentraland/creating-wearables/" target="_blank" rel="noopener noreferrer">
{t('create_single_item_modal.import_information_link_label')}
</a>
)
})}
</span>
)
}

render() {
const { category, metadata, title, wearablePreviewComponent, isLoading, isRepresentation, onClose } = this.props
const { error } = this.state
Expand All @@ -232,15 +263,16 @@ export default class ImportStep extends React.PureComponent<Props, State> {
<ModalNavigation title={title} onClose={onClose} />
<Modal.Content className="ImportStep">
<ItemImport
error={error}
isLoading={isLoading}
acceptedExtensions={
isRepresentation || metadata?.changeItemFile
? isImageCategory(category!)
? isImageCategory(category as WearableCategory)
? IMAGE_EXTENSIONS
: MODEL_EXTENSIONS
: ITEM_EXTENSIONS
}
error={error}
moreInformation={this.renderMoreInformation()}
onDropAccepted={this.handleDropAccepted}
onDropRejected={this.handleDropRejected}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import { WearableCategory } from '@dcl/schemas'
import { EmoteCategory, WearableCategory } from '@dcl/schemas'
import { Item } from 'modules/item/types'
import { AcceptedFileProps } from '../CreateSingleItemModal.types'

export type Props = {
category?: WearableCategory
category?: WearableCategory | EmoteCategory
metadata?: CreateSingleItemModalMetadata
title: string
wearablePreviewComponent?: React.ReactNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,12 @@ export default class EditPriceAndBeneficiaryModal extends React.PureComponent<Pr
}

render() {
const { name, isLoading, onClose, onSkip } = this.props
const { name, isLoading, mountNode, onClose, onSkip } = this.props
const { isFree, isOwnerBeneficiary, price = '' } = this.state
const beneficiary = this.getBeneficiary()

return (
<Modal name={name} size="tiny" onClose={onClose}>
<Modal name={name} size="tiny" onClose={onClose} mountNode={mountNode}>
<ModalNavigation title={t('edit_price_and_beneficiary_modal.title')} onClose={onClose} />

<Form onSubmit={this.handleSubmit}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type Props = ModalProps & {
isLoading: boolean
metadata: EditPriceAndBeneficiaryModalMetadata
itemSortedContents?: Record<string, Blob>
mountNode?: HTMLDivElement | undefined
onSave: typeof saveItemRequest
onSetPriceAndBeneficiary: typeof setPriceAndBeneficiaryRequest
onSkip?: () => void
Expand All @@ -29,7 +30,7 @@ export type EditPriceAndBeneficiaryModalMetadata = {
itemId: string
}

export type OwnProps = Pick<Props, 'metadata' | 'item'>
export type OwnProps = Pick<Props, 'metadata' | 'item' | 'mountNode'>
export type MapStateProps = Pick<Props, 'item' | 'isLoading'>
export type MapDispatchProps = Pick<Props, 'onSave' | 'onSetPriceAndBeneficiary'>
export type MapDispatch = Dispatch<SaveItemRequestAction | SetPriceAndBeneficiaryRequestAction>
34 changes: 19 additions & 15 deletions src/lib/getModelData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,24 @@ export async function getIsEmote(url: string, options: Partial<Options> = {}) {
return gltf.animations.length > 0
}

export async function getEmoteMetrics(blob: Blob) {
const { gltf, renderer } = await loadGltf(URL.createObjectURL(blob))
document.body.removeChild(renderer.domElement)
const animation = gltf.animations[0]
let frames = 0
for (let i = 0; i < animation.tracks.length; i++) {
const track = animation.tracks[i]
frames = Math.max(frames, track.times.length)
}

return {
sequences: gltf.animations.length,
duration: animation.duration,
frames,
fps: frames / animation.duration
}
}

export async function getItemData({
type,
model,
Expand Down Expand Up @@ -252,21 +270,7 @@ export async function getItemData({
throw Error('WearablePreview controller needed')
}
if (type === ItemType.EMOTE) {
const { gltf, renderer } = await loadGltf(URL.createObjectURL(contents[model]))
document.body.removeChild(renderer.domElement)
const animation = gltf.animations[0]
let frames = 0
for (let i = 0; i < animation.tracks.length; i++) {
const track = animation.tracks[i]
frames = Math.max(frames, track.times.length)
}

info = {
sequences: gltf.animations.length,
duration: animation.duration,
frames,
fps: frames / animation.duration
}
info = await getEmoteMetrics(contents[model])
} else {
info = await wearablePreviewController.scene.getMetrics()
}
Expand Down
Loading

0 comments on commit 6af1769

Please sign in to comment.