Skip to content

Commit

Permalink
feat: Add files box when creating items
Browse files Browse the repository at this point in the history
  • Loading branch information
LautaroPetaccio committed Sep 22, 2021
1 parent a013996 commit d365cae
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 92 deletions.
11 changes: 11 additions & 0 deletions src/components/Modals/CreateItemModal/CreateItemModal.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
width: 100%;
}

.CreateItemModal .file-details {
padding: 24px;
margin-top: 20px;
display: flex;
flex-direction: row;
}

.CreateItemModal .file-details .total-size {
color: var(--primary);
}

.CreateItemModal .preview {
padding: 24px;
}
Expand Down
209 changes: 126 additions & 83 deletions src/components/Modals/CreateItemModal/CreateItemModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
Header,
InputOnChangeData,
SelectField,
DropdownProps
DropdownProps,
Table,
TableCell,
Icon as DCLIcon
} from 'decentraland-ui'
import { T, t } from 'decentraland-dapps/dist/modules/translation/utils'
import Modal from 'decentraland-dapps/dist/containers/Modal'
Expand Down Expand Up @@ -707,97 +710,137 @@ export default class CreateItemModal extends React.PureComponent<Props, State> {
const thumbnailStyle = getBackgroundStyle(rarity)
const title = this.renderModalTitle()

const total = 234423 + 23442 + 245535
const files: Record<string, number> = {
'femaleHat.glb': 23442,
'maleHat.glb': 245535,
'thumbnail.png': 54532,
'auto-generated-content.png': 12222
}

return (
<>
<ModalNavigation title={title} onClose={onClose} />
<Modal.Content>
<Form onSubmit={this.handleSubmit} disabled={isDisabled}>
<Column>
<Row className="details">
<Column className="preview" width={192} grow={false}>
<div className="thumbnail-container">
<img className="thumbnail" src={thumbnail || undefined} style={thumbnailStyle} />
{isRepresentation ? null : (
<>
<Icon name="camera" onClick={this.handleOpenThumbnailDialog} />
<input type="file" ref={this.thumbnailInput} onChange={this.handleThumbnailChange} accept="image/png, image/jpeg" />
</>
)}
</div>
{metrics ? (
<div className="metrics">
<div className="metric triangles">{t('model_metrics.triangles', { count: metrics.triangles })}</div>
<div className="metric materials">{t('model_metrics.materials', { count: metrics.materials })}</div>
<div className="metric textures">{t('model_metrics.textures', { count: metrics.textures })}</div>
</div>
) : null}
</Column>
<Column className="data" grow={true}>
{isAddingRepresentation ? null : (
<Section>
<Header sub>{t('create_item_modal.representation_label')}</Header>
<Row>
{this.renderRepresentation(BodyShapeType.BOTH)}
{this.renderRepresentation(BodyShapeType.MALE)}
{this.renderRepresentation(BodyShapeType.FEMALE)}
</Row>
</Section>
)}
{bodyShape && (!metadata || !metadata.changeItemFile) ? (
<Row className="details">
<Column className="preview" width={192} grow={false}>
<div className="thumbnail-container">
<img className="thumbnail" src={thumbnail || undefined} style={thumbnailStyle} />
{isRepresentation ? null : (
<>
{bodyShape === BodyShapeType.BOTH ? (
this.renderFields()
) : (
<>
{isAddingRepresentation ? null : (
<Section>
<Header sub>{t('create_item_modal.existing_item')}</Header>
<Row>
<div className={`option ${isRepresentation === true ? 'active' : ''}`} onClick={this.handleYes}>
{t('global.yes')}
</div>
<div className={`option ${isRepresentation === false ? 'active' : ''}`} onClick={this.handleNo}>
{t('global.no')}
</div>
</Row>
</Section>
)}
{isRepresentation === undefined ? null : isRepresentation ? (
<Section>
<Header sub>
{isAddingRepresentation
? t('create_item_modal.adding_representation', { bodyShape: t(`body_shapes.${bodyShape}`) })
: t('create_item_modal.pick_item', { bodyShape: t(`body_shapes.${bodyShape}`) })}
</Header>
<ItemDropdown
value={item}
filter={this.filterItemsByBodyShape}
onChange={this.handleItemChange}
isDisabled={isAddingRepresentation}
/>
</Section>
) : (
this.renderFields()
)}
</>
)}
<Icon name="camera" onClick={this.handleOpenThumbnailDialog} />
<input type="file" ref={this.thumbnailInput} onChange={this.handleThumbnailChange} accept="image/png, image/jpeg" />
</>
) : (
this.renderFields()
)}
</Column>
</Row>
<Row className="actions" align="right">
<Button primary disabled={isDisabled} loading={isLoading}>
{metadata && metadata.changeItemFile ? t('global.save') : t('global.create')}
</Button>
</Row>
{error ? (
<Row className="error" align="right">
<p className="danger-text">{error}</p>
</div>
{metrics ? (
<div className="metrics">
<div className="metric triangles">{t('model_metrics.triangles', { count: metrics.triangles })}</div>
<div className="metric materials">{t('model_metrics.materials', { count: metrics.materials })}</div>
<div className="metric textures">{t('model_metrics.textures', { count: metrics.textures })}</div>
</div>
) : null}
</Column>
<Column className="data" grow={true}>
{isAddingRepresentation ? null : (
<Section>
<Header sub>{t('create_item_modal.representation_label')}</Header>
<Row>
{this.renderRepresentation(BodyShapeType.BOTH)}
{this.renderRepresentation(BodyShapeType.MALE)}
{this.renderRepresentation(BodyShapeType.FEMALE)}
</Row>
</Section>
)}
{bodyShape && (!metadata || !metadata.changeItemFile) ? (
<>
{bodyShape === BodyShapeType.BOTH ? (
this.renderFields()
) : (
<>
{isAddingRepresentation ? null : (
<Section>
<Header sub>{t('create_item_modal.existing_item')}</Header>
<Row>
<div className={`option ${isRepresentation === true ? 'active' : ''}`} onClick={this.handleYes}>
{t('global.yes')}
</div>
<div className={`option ${isRepresentation === false ? 'active' : ''}`} onClick={this.handleNo}>
{t('global.no')}
</div>
</Row>
</Section>
)}
{isRepresentation === undefined ? null : isRepresentation ? (
<Section>
<Header sub>
{isAddingRepresentation
? t('create_item_modal.adding_representation', { bodyShape: t(`body_shapes.${bodyShape}`) })
: t('create_item_modal.pick_item', { bodyShape: t(`body_shapes.${bodyShape}`) })}
</Header>
<ItemDropdown
value={item}
filter={this.filterItemsByBodyShape}
onChange={this.handleItemChange}
isDisabled={isAddingRepresentation}
/>
</Section>
) : (
this.renderFields()
)}
</>
)}
</>
) : (
this.renderFields()
)}
</Column>
</Row>
<Row className="details file-details">
<Column>
<Row>
<Header size="medium" className="title">
Files detail
</Header>
</Row>
) : null}
</Column>
<Table basic="very" columns={2} compact collapsing={false}>
<Table.Header>
<Table.Row>
<Table.HeaderCell width="4">File name</Table.HeaderCell>
<Table.HeaderCell width="2">Size</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{Object.keys(files).map((filePath, index) => (
<Table.Row key={index}>
<Table.Cell>
<DCLIcon name="file outline" />
{filePath}
</Table.Cell>
<TableCell>{(files[filePath] / 1024).toFixed(2)} kB</TableCell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.Cell></Table.Cell>
<Table.Cell className="total-size">{(total / 1024).toFixed(2)} kB / 2048 kB (Limit)</Table.Cell>
</Table.Row>
</Table.Footer>
</Table>
</Column>
</Row>
<Row className="actions" align="right">
<Button primary disabled={isDisabled} loading={isLoading}>
{metadata && metadata.changeItemFile ? t('global.save') : t('global.create')}
</Button>
</Row>
{error ? (
<Row className="error" align="right">
<p className="danger-text">{error}</p>
</Row>
) : null}
</Form>
</Modal.Content>
</>
Expand Down
22 changes: 17 additions & 5 deletions src/modules/item/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,17 @@ export async function getFiles(contents: Record<string, string>): Promise<Record
* @param hashes - The record of names->hashes.
* @param blobs - The record of names->blobs.
*/
function getUniqueFiles(hashes: Record<string, string>, blobs: Record<string, Blob>): Array<Blob> {
const uniqueFileHases: Array<string> = [...new Set(Object.values(hashes))]
function getUniqueFiles(hashes: Record<string, string>, blobs: Record<string, Blob>): Record<string, Blob> {
const uniqueFileHashes: Array<string> = [...new Set(Object.values(hashes))]
const inverseFileHashesRecord = Object.keys(hashes).reduce((obj: Record<string, string>, key: string) => {
obj[hashes[key]] = key
return obj
}, {})
return uniqueFileHases.map(hash => blobs[inverseFileHashesRecord[hash]])

return uniqueFileHashes.reduce(
(acc, hash) => ({ ...acc, [inverseFileHashesRecord[hash]]: blobs[inverseFileHashesRecord[hash]] }),
{} as Record<string, Blob>
)
}

/**
Expand All @@ -106,7 +110,7 @@ function getUniqueFiles(hashes: Record<string, string>, blobs: Record<string, Bl
* @param item - An item that contains the old and the new hahsed content.
* @param newContents - The new content that is going to be added to the item.
*/
export async function calculateFinalSize(item: Item, newContents: Record<string, Blob>): Promise<number> {
export async function calculateFinalSize(item: Item, newContents: Record<string, Blob>) {
const newHashes = await computeHashes(newContents)
const filesToDownload: Record<string, string> = {}
for (const fileName in item.contents) {
Expand All @@ -124,7 +128,15 @@ export async function calculateFinalSize(item: Item, newContents: Record<string,
} catch (error) {}

const uniqueFiles = getUniqueFiles({ ...newHashes, ...filesToDownload }, { ...newContents, ...blobs })
return imageSize + calculateFilesSize(uniqueFiles)
// uniqueFiles.reduce((acc, file) => ({ ...acc, [file.tex]: file.size }), {})
// return imageSize + calculateFilesSize(uniqueFiles)
return {
total: imageSize + calculateFilesSize(Object.values(uniqueFiles)),
files: {
...Object.keys(uniqueFiles).reduce((acc, path) => ({ ...acc, [path]: uniqueFiles[path].size }), {}),
imageWithBackground: imageSize
}
}
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/modules/item/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ export function* itemSaga(builder: BuilderAPI) {
if (item.isPublished) {
throw new Error(t('sagas.item.cant_save_published'))
}
const finalSize: number = yield call(calculateFinalSize, item, contents)
if (finalSize > MAX_FILE_SIZE) {
const finalSize: { total: number; files: Record<string, number> } = yield call(calculateFinalSize, item, contents)
if (finalSize.total > MAX_FILE_SIZE) {
throw new ItemTooBigError()
}

Expand Down Expand Up @@ -171,8 +171,8 @@ export function* itemSaga(builder: BuilderAPI) {
throw new Error(t('sagas.item.cant_save_without_collection'))
}

const finalSize: number = yield call(calculateFinalSize, item, contents)
if (finalSize > MAX_FILE_SIZE) {
const finalSize: { total: number; files: Record<string, number> } = yield call(calculateFinalSize, item, contents)
if (finalSize.total > MAX_FILE_SIZE) {
throw new ItemTooBigError()
}

Expand Down

0 comments on commit d365cae

Please sign in to comment.