Skip to content
Open
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
83 changes: 83 additions & 0 deletions app/components/AssetAttributes/AssetAttributes.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,86 @@
padding: 0;
margin: 0;
}

.headerRow {
display: flex;
justify-content: flex-end;
margin-bottom: 4px;
}

.addButton {
display: flex;
align-items: center;
background-color: #639;
color: white;
border: none;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
font-size: 12px;
gap: 2px;
transition: background-color 0.2s;
}

.addButton:hover {
background-color: #552b80;
}

.attributeRow {
display: flex;
align-items: center;
justify-content: space-between;
}

.deleteButton {
color: #639 !important;
padding: 2px !important;
}

.deleteButton:hover {
background-color: rgb(102 51 153 / 10%) !important;
}

.dialogTitle {
background-color: #aa94d1;
color: white !important;
font-weight: bold;
text-align: center;
}

.dialogContent {
padding: 20px 24px !important;
}

.formLabel {
display: block;
font-weight: bold;
margin-bottom: 8px;
font-size: 14px;
}

.dialogActions {
display: flex;
justify-content: center;
padding: 8px 24px 16px;
gap: 16px;
}

.saveButton,
.cancelButton {
padding: 8px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
background-color: #639;
color: white;
transition: opacity 0.2s;
}

.saveButton:hover,
.cancelButton:hover {
opacity: 0.85;
}
130 changes: 123 additions & 7 deletions app/components/AssetAttributes/AssetAttributes.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,90 @@
import React from 'react';
import { Checkbox, FormControlLabel } from '@mui/material';
import React, { useState } from 'react';
import { Checkbox, FormControlLabel, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Button, Tooltip, } from '@mui/material';
import { Add, Delete } from '@mui/icons-material';
import styles from './AssetAttributes.css';
import AssetsConfig from '../../constants/assets-config';

const assetAttributes = (props) => {
const { asset, configuration, onUpdateAttribute } = props;
const { asset, configuration, onUpdateAttribute, customAttributes, onAddCustomAttribute, onDeleteCustomAttribute, } = props;

const [openAddDialog, setOpenAddDialog] = useState(false);
const [newAttributeName, setNewAttributeName] = useState('');

const updateAttributeValue = (a) => {
if (onUpdateAttribute) {
onUpdateAttribute(a.target.name, a.target.checked);
}
};

const handleAddCustomAttribute = () => {
const trimmedName = newAttributeName.trim();
if(!trimmedName)return;

// Enforcing Attributes Name Length
const safeName = trimmedName.substring(0, AssetsConfig.CUSTOM_ATTRIBUTE_NAME_MAX_LENGTH);
const id = `custom_${safeName.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '')}`;

// Checking for the Duplicate IDs
const allAttributes = [...configuration, ...(customAttributes || [])];
if (allAttributes.some((a) => a.id === id)) {
setOpenAddDialog(false);
setNewAttributeName('');
return;
}

const newAttribute = {
id,
display: safeName,
type: 'bool',
default: false,
appliesTo: ['*'],
source: 'custom',
}

if (onAddCustomAttribute) {
onAddCustomAttribute(newAttribute);
}

setOpenAddDialog(false);
setNewAttributeName('');
}

const handleDeleteCustomAttribute = (attributeId) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to give the user a pop-up warning and confirmation here. Something like:

Are you sure you want to delete this custom attribute?
Note: This will delete the custom attribute for all assets in this project.

I want the user to be sure they know it's not deleting it for just a single attribute - that if they proceed, it disappears for everything.

if (onDeleteCustomAttribute) {
onDeleteCustomAttribute(attributeId);
}
};

const applicableAttributes = configuration
.map((a) => {
// If the asset doesn't have a content type (needed for attribute detection),
// or the attribute doesn't apply to this asset, skip it
if (asset.contentTypes == null ||
(!a.appliesTo.includes('*') && !a.appliesTo.some((x) => asset.contentTypes.includes(x)))) {
return null;
if (a.appliesTo.includes('*')) {
return a;
}
if (asset.contentTypes == null || !a.appliesTo.some((x) => asset.contentTypes.includes(x))) {
return null;
}
return a;
})
.filter((a) => a !== null);

const allApplicableAttributes = [
...applicableAttributes,
...(customAttributes || []),
];

let controls = null;
if (asset) {
controls = applicableAttributes.map((a) => {
controls = allApplicableAttributes.map((a) => {
let control = <span>{a.display}</span>;
if (a.type === 'bool') {
const value =
asset.attributes && asset.attributes[a.id] !== undefined
? asset.attributes[a.id]
: a.default;
control = (
<div className={styles.attributeRow}>
<FormControlLabel
label={a.display}
control={
Expand All @@ -44,6 +97,18 @@ const assetAttributes = (props) => {
/>
}
/>
{a.source === 'custom' && (
<Tooltip title="Remove this custom attribute" enterDelay={300}>
<IconButton
size="small"
onClick={() => handleDeleteCustomAttribute(a.id)}
className={styles.deleteButton}
>
<Delete fontSize="small" />
</IconButton>
</Tooltip>
)}
</div>
);
}
return <li key={a.id}>{control}</li>;
Expand All @@ -52,7 +117,58 @@ const assetAttributes = (props) => {

return (
<div className={styles.container}>
<div className={styles.headerRow}>
<button
className={styles.addButton}
onClick={() => setOpenAddDialog(true)}
>
<Add fontSize="small" />
<span>Add Attribute</span>
</button>
</div>
<ul className={styles.attributesList}>{controls}</ul>

{ /*Custom Attribute Dialog */ }
<Dialog
open={openAddDialog}
onClose={() => {
setOpenAddDialog(false);
setNewAttributeName('');
}}
maxWidth="sm"
fullWidth
>
<DialogTitle className={styles.dialogTitle}>Custom Attributes</DialogTitle>
<DialogContent className={styles.dialogContent}>
<label className={styles.formLabel}>Name of Attributes</label>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor - remove "s" on "Attributes" ("Name of Attribute")

<TextField
autoFocus
placeholder='Attributes like "Experimental"'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to:

'e.g., "Experimental"'

value={newAttributeName}
onChange={(e) => setNewAttributeName(e.target.value)}
fullWidth
variant="outlined"
size="small"
inputProps={{
maxLength: AssetsConfig.CUSTOM_ATTRIBUTE_NAME_MAX_LENGTH,
}}
/>
</DialogContent>
<DialogActions className={styles.dialogActions}>
<button className={styles.saveButton} onClick={handleAddCustomAttribute}>
Save
</button>
<button
className={styles.cancelButton}
onClick={() => {
setOpenAddDialog(false);
setNewAttributeName('');
}}
>
Cancel
</button>
</DialogActions>
</Dialog>
</div>
);
};
Expand Down
6 changes: 6 additions & 0 deletions app/components/AssetDetails/AssetDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const assetDetails = (props) => {
onDeletedNote,
onUpdatedAttribute,
assetAttributes,
customAttributes,
onAddCustomAttribute,
onDeleteCustomAttribute,
sourceControlEnabled,
dynamicDetails,
isExternalRootAsset,
Expand Down Expand Up @@ -154,6 +157,9 @@ const assetDetails = (props) => {
asset={asset}
configuration={assetAttributes}
onUpdateAttribute={updateAssetAttribute}
customAttributes={customAttributes}
onAddCustomAttribute={onAddCustomAttribute}
onDeleteCustomAttribute={onDeleteCustomAttribute}
/>
</AccordionDetails>
</Accordion>
Expand Down
Loading