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
45 changes: 27 additions & 18 deletions app/components/AssetTree/AssetNode/AssetNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ const getPaddingLeft = (level) => {
return level * 20;
};

const getNodeColor = (node) => {
if (!node || !node.attributes) {
const getNodeColor = (node, ancestorArchived) => {
if (!node) {
return '#000';
}

if (node.attributes.archived) {
if ((node.attributes && node.attributes.archived) || ancestorArchived) {
return "#777";
}
return '#000';
}

const getNodeFontWeight = (node) => {
Expand All @@ -49,7 +50,7 @@ const StyledTreeNode = styled.div`
padding: 5px 8px;
padding-left: ${(props) => getPaddingLeft(props.$level)}px;
${(props) => (props.selected ? 'background-color: #eee;' : null)}
color: ${(props) => getNodeColor(props.node)};
color: ${(props) => getNodeColor(props.node, props.ancestorArchived)};
font-weight: ${(props) => getNodeFontWeight(props.node)};
`;

Expand Down Expand Up @@ -79,6 +80,7 @@ function AssetNode(props) {
onToggle,
onRightClick,
onClick,
ancestorArchived,
} = props;
const isOpen = root || openNodes.includes(node.uri);
const isChecked = checkboxes && (root || checkedNodes.includes(node.uri));
Expand All @@ -104,12 +106,16 @@ function AssetNode(props) {
!root && checkboxes ? (
<StyledInput checked={checked} onChange={handleChecked} type="checkbox" />
) : null;

const isArchived = (node.attributes && node.attributes.archived) || ancestorArchived;

return (
<>
<StyledTreeNode
$level={level}
type={node.type}
node={node}
ancestorArchived={ancestorArchived}
selected={node && selectedAsset && node.uri === selectedAsset.uri}
onContextMenu={(e) => {
if (onRightClick) {
Expand Down Expand Up @@ -145,20 +151,21 @@ function AssetNode(props) {
(!node.children
? null
: node.children.map((childNode) => (
<AssetNode
key={childNode.uri}
node={childNode}
level={level + 1}
selectedAsset={selectedAsset}
openNodes={openNodes}
checkedNodes={checkedNodes}
onToggle={onToggle}
onClick={onClick}
onRightClick={onRightClick}
checkboxes={checkboxes}
onCheck={props.onCheck}
/>
)))}
<AssetNode
key={childNode.uri}
node={childNode}
level={level + 1}
selectedAsset={selectedAsset}
openNodes={openNodes}
checkedNodes={checkedNodes}
onToggle={onToggle}
onClick={onClick}
onRightClick={onRightClick}
checkboxes={checkboxes}
onCheck={props.onCheck}
ancestorArchived={isArchived}
/>
)))}
</>
);
}
Expand All @@ -175,6 +182,7 @@ AssetNode.propTypes = {
openNodes: PropTypes.array,
checkedNodes: PropTypes.array,
checkboxes: PropTypes.bool,
ancestorArchived: PropTypes.bool,
};

AssetNode.defaultProps = {
Expand All @@ -188,6 +196,7 @@ AssetNode.defaultProps = {
openNodes: [],
checkedNodes: [],
checkboxes: false,
ancestorArchived: false,
};

export default AssetNode;
90 changes: 80 additions & 10 deletions app/components/Project/Project.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import React, { Component, useEffect } from 'react';
import { Tab, Tabs } from '@mui/material';
import {
Tab,
Tabs,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Button,
} from '@mui/material';
import { TabPanel, TabContext } from '@mui/lab';
import { cloneDeep } from 'lodash';
import IconButton from '@mui/material/IconButton';
Expand All @@ -23,15 +32,15 @@ import styles from './Project.css';
import UserContext from '../../contexts/User';

type Props = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason onDirtyStateChange was removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry about that Sir , it was changed accidentally during making these changes... I have added onDirtyStateChange back into the code in the latest commit....

onDirtyStateChange?: (boolean) => void,
onDirtyStateChange?: (boolean) => void,
};

class Project extends Component<Props> {
props: Props;

constructor(props) {
super(props);
this.state = { selectedTab: 'about', showLogUpdatesOnly: false };
this.state = { selectedTab: 'about', showLogUpdatesOnly: false, showUnarchiveConfirmation: false, pendingUnarchive: null };
}

changeHandler = (event, id) => {
Expand Down Expand Up @@ -366,36 +375,75 @@ class Project extends Component<Props> {
* @param {any} value The value of the updated attribute. Its type depends on the configuration of the attribute.
*/
assetUpdateAttributeHandler = (asset, name, value) => {
const project = { ...this.props.project };
const assetsCopy = { ...project.assets };
// Check for unarchive action
if (name === 'archived' && value === false) {
if (AssetUtil.hasArchivedDescendants(asset)) {
this.setState({
showUnarchiveConfirmation: true,
pendingUnarchive: { assetUri: asset.uri, name, value }
});
return;
}
}

const existingAsset = AssetUtil.findDescendantAssetByUri(assetsCopy, asset.uri);
this.performAssetAttributeUpdate(asset.uri, name, value, false);
};

performAssetAttributeUpdate = (assetUri, name, value, clearDescendants) => {

const project = cloneDeep(this.props.project);
const assetsCopy = project.assets;

const existingAsset = AssetUtil.findDescendantAssetByUri(assetsCopy, assetUri);
let actionDescription = '';
if (!existingAsset) {
console.warn('Could not find the asset to update its attribute');
return;
} else {
actionDescription = `Updated ${asset.uri} attribute '${name}' to '${value}'`;
actionDescription = `Updated ${assetUri} attribute '${name}' to '${value}'`;
}

if (!existingAsset.attributes) {
existingAsset.attributes = {};
}

existingAsset.attributes[name] = value;
project.assets = assetsCopy;

if (clearDescendants) {
AssetUtil.clearArchivedAttributeForDescendants(existingAsset);
}

if (this.props.onUpdated) {
this.props.onUpdated(
project,
ActionType.ATTRIBUTE_UPDATED,
EntityType.ASSET,
asset.uri,
assetUri,
`Asset ${ActionType.ATTRIBUTE_UPDATED}`,
actionDescription,
{ name, value }
{ name, value, clearDescendants }
);
}
};

handleCancelUnarchive = () => {
this.setState({ showUnarchiveConfirmation: false, pendingUnarchive: null });
};

handleConfirmUnarchive = (alsoDescendants) => {
const { pendingUnarchive } = this.state;
if (!pendingUnarchive) return;

this.performAssetAttributeUpdate(
pendingUnarchive.assetUri,
pendingUnarchive.name,
pendingUnarchive.value,
alsoDescendants
);

this.setState({ showUnarchiveConfirmation: false, pendingUnarchive: null });
};

/**
* Handler when an asset (either the main assets or the externalAssets in a project) has
* a note removed.
Expand Down Expand Up @@ -941,6 +989,28 @@ class Project extends Component<Props> {
return (
<div className={styles.container} data-tid="container">
{content}
<Dialog
open={this.state.showUnarchiveConfirmation}
onClose={this.handleCancelUnarchive}
>
<DialogTitle style={{ color: 'white' , backgroundColor: '#aa94d1' }} >Unarchive Descendants </DialogTitle>
<DialogContent>
<DialogContentText>
This folder contains files or folders that were explicitly archived. Do you want to unarchive them as well?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleConfirmUnarchive(true)} color="primary">
Yes, Unarchive All
</Button>
<Button onClick={() => this.handleConfirmUnarchive(false)} color="primary" autoFocus>
No, Keep Them Archived
</Button>
<Button onClick={this.handleCancelUnarchive} color="secondary">
Cancel
</Button>
</DialogActions>
</Dialog>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion app/containers/ProjectPage/ProjectPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ class ProjectPage extends Component {
const { projects } = prevState;
const foundIndex = projects.findIndex((x) => x.id === project.id);
projects[foundIndex] = project;
return { projects };
return { projects, selectedProject: project };
});

// The update project request will handle logging if it succeeds. No additional logging call is
Expand Down
3 changes: 3 additions & 0 deletions app/services/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,9 @@ export default class ProjectService {
asset.attributes = {};
}
asset.attributes[details.name] = details.value;
if (details.clearDescendants) {
AssetUtil.clearArchivedAttributeForDescendants(asset);
}
} else {
return null;
}
Expand Down
44 changes: 42 additions & 2 deletions app/utils/asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ export default class AssetUtil {
const notes =
asset && asset.notes
? asset.notes.map((n) => {
return { ...n, uri: asset.uri };
})
return { ...n, uri: asset.uri };
})
: [];
if (!asset || !asset.children) {
return notes.flat();
Expand Down Expand Up @@ -493,4 +493,44 @@ export default class AssetUtil {
}
return !FILE_IGNORE_LIST.includes(fileName);
}

/**
* Determine if an asset has any descendants that are explicitly archived.
* @param {object} asset The asset to check
* @returns true if any descendant is explicitly archived
*/
static hasArchivedDescendants(asset) {
if (!asset || !asset.children) {
return false;
}

// Check if any direct child is archived
const hasArchivedChild = asset.children.some(child =>
child.attributes && child.attributes.archived
);

if (hasArchivedChild) {
return true;
}

// Recursively check children
return asset.children.some(child => AssetUtil.hasArchivedDescendants(child));
}

/**
* Recursively clear the archived attribute for all descendants of an asset.
* @param {object} asset The asset to clear descendants for
*/
static clearArchivedAttributeForDescendants(asset) {
if (!asset || !asset.children) {
return;
}

asset.children.forEach(child => {
if (child.attributes && child.attributes.archived) {
child.attributes.archived = false;
}
AssetUtil.clearArchivedAttributeForDescendants(child);
});
}
}
Loading