diff --git a/src/common/mockFactory.ts b/src/common/mockFactory.ts index 1db2f08d61..8dfb8ef3e1 100644 --- a/src/common/mockFactory.ts +++ b/src/common/mockFactory.ts @@ -810,14 +810,16 @@ export default class MockFactory { */ public static projectActions(): IProjectActions { return { - loadProject: jest.fn((project: IProject) => Promise.resolve()), - saveProject: jest.fn((project: IProject) => Promise.resolve()), - deleteProject: jest.fn((project: IProject) => Promise.resolve()), + loadProject: jest.fn(() => Promise.resolve()), + saveProject: jest.fn(() => Promise.resolve()), + deleteProject: jest.fn(() => Promise.resolve()), closeProject: jest.fn(() => Promise.resolve()), - loadAssets: jest.fn((project: IProject) => Promise.resolve()), - exportProject: jest.fn((project: IProject) => Promise.resolve()), - loadAssetMetadata: jest.fn((project: IProject, asset: IAsset) => Promise.resolve()), - saveAssetMetadata: jest.fn((project: IProject, assetMetadata: IAssetMetadata) => Promise.resolve()), + loadAssets: jest.fn(() => Promise.resolve()), + exportProject: jest.fn(() => Promise.resolve()), + loadAssetMetadata: jest.fn(() => Promise.resolve()), + saveAssetMetadata: jest.fn(() => Promise.resolve()), + updateProjectTag: jest.fn(() => Promise.resolve()), + deleteProjectTag: jest.fn(() => Promise.resolve()), }; } diff --git a/src/react/components/common/tagInput/tagInput.tsx b/src/react/components/common/tagInput/tagInput.tsx index b1430c4ca0..56db6e8954 100644 --- a/src/react/components/common/tagInput/tagInput.tsx +++ b/src/react/components/common/tagInput/tagInput.tsx @@ -1,4 +1,4 @@ -import React, { KeyboardEvent } from "react"; +import React, { KeyboardEvent, RefObject } from "react"; import ReactDOM from "react-dom"; import Align from "rc-align"; import { randomIntInRange } from "../../../../common/utils"; @@ -10,6 +10,7 @@ import TagInputItem, { ITagInputItemProps, ITagClickProps } from "./tagInputItem import TagInputToolbar from "./tagInputToolbar"; import { toast } from "react-toastify"; import { strings } from "../../../../common/strings"; +import { string } from "prop-types"; // tslint:disable-next-line:no-var-requires const tagColors = require("../../common/tagColors.json"); @@ -72,7 +73,7 @@ export class TagInput extends React.Component { portalElement: defaultDOMNode(), }; - private tagItemRefs: { [id: string]: TagInputItem } = {}; + private tagItemRefs: Map> = new Map>(); private portalDiv = document.createElement("div"); public render() { @@ -109,7 +110,7 @@ export class TagInput extends React.Component { } {this.getColorPickerPortal()}
- {this.getTagItems()} + {this.renderTagItems()}
{ this.state.addTags && @@ -154,11 +155,13 @@ export class TagInput extends React.Component { } } - private getTagNode = (tag: ITag) => { + private getTagNode = (tag: ITag): Element => { if (!tag) { return defaultDOMNode(); } - return ReactDOM.findDOMNode(this.tagItemRefs[tag.name]) as Element; + + const itemRef = this.tagItemRefs.get(tag.name); + return (itemRef ? ReactDOM.findDOMNode(itemRef.current) : defaultDOMNode()) as Element; } private onEditTag = (tag: ITag) => { @@ -298,12 +301,15 @@ export class TagInput extends React.Component { return this.state.editingTagNode || document; } - private getTagItems = () => { - let props = this.getTagItemProps(); + private renderTagItems = () => { + let props = this.createTagItemProps(); const query = this.state.searchQuery; + this.tagItemRefs.clear(); + if (query.length) { props = props.filter((prop) => prop.tag.name.toLowerCase().includes(query.toLowerCase())); } + return props.map((prop) => { } private setTagItemRef = (item, tag) => { - if (item) { - this.tagItemRefs[tag.name] = item; - } + this.tagItemRefs.set(tag.name, item); + return item; } - private getTagItemProps = (): ITagInputItemProps[] => { + private createTagItemProps = (): ITagInputItemProps[] => { const tags = this.state.tags; const selectedRegionTagSet = this.getSelectedRegionTagSet(); - return tags.map((tag) => { - const item: ITagInputItemProps = { + + return tags.map((tag) => ( + { tag, index: tags.findIndex((t) => t.name === tag.name), isLocked: this.props.lockedTags && this.props.lockedTags.findIndex((t) => t === tag.name) > -1, @@ -331,9 +337,8 @@ export class TagInput extends React.Component { appliedToSelectedRegions: selectedRegionTagSet.has(tag.name), onClick: this.handleClick, onChange: this.updateTag, - }; - return item; - }); + } as ITagInputItemProps + )); } private getSelectedRegionTagSet = (): Set => { @@ -351,6 +356,7 @@ export class TagInput extends React.Component { private onAltClick = (tag: ITag, clickedColor: boolean) => { const { editingTag } = this.state; const newEditingTag = editingTag && editingTag.name === tag.name ? null : tag; + this.setState({ editingTag: newEditingTag, editingTagNode: this.getTagNode(newEditingTag), @@ -360,16 +366,16 @@ export class TagInput extends React.Component { } private handleClick = (tag: ITag, props: ITagClickProps) => { + // Lock tags if (props.ctrlKey && this.props.onCtrlTagClick) { this.props.onCtrlTagClick(tag); this.setState({ clickedColor: props.clickedColor }); - } else if (props.altKey) { + } else if (props.altKey) { // Edit tag this.onAltClick(tag, props.clickedColor); - } else { + } else { // Select tag const { editingTag, selectedTag } = this.state; const inEditMode = editingTag && tag.name === editingTag.name; const alreadySelected = selectedTag && selectedTag.name === tag.name; - const newEditingTag = inEditMode ? null : editingTag; this.setState({ @@ -398,12 +404,15 @@ export class TagInput extends React.Component { this.props.onTagDeleted(tag.name); return; } + const index = this.state.tags.indexOf(tag); const tags = this.state.tags.filter((t) => t.name !== tag.name); + this.setState({ tags, selectedTag: this.getNewSelectedTag(tags, index), }, () => this.props.onChange(tags)); + if (this.props.lockedTags.find((l) => l === tag.name)) { this.props.onLockedTagsChange( this.props.lockedTags.filter((lockedTag) => lockedTag !== tag.name), diff --git a/src/react/components/pages/editorPage/canvas.test.tsx b/src/react/components/pages/editorPage/canvas.test.tsx index d4ca52d109..a37c6f6a85 100644 --- a/src/react/components/pages/editorPage/canvas.test.tsx +++ b/src/react/components/pages/editorPage/canvas.test.tsx @@ -179,7 +179,7 @@ describe("Editor Canvas", () => { }); const canvas = wrapper.instance() as Canvas; expect(wrapper.state().currentAsset).toEqual(assetMetadata); - expect(() => canvas.updateCanvasToolsRegions()).not.toThrowError(); + expect(() => canvas.updateCanvasToolsRegionTags()).not.toThrowError(); }); it("canvas content source is updated when asset is deactivated", () => { diff --git a/src/react/components/pages/editorPage/canvas.tsx b/src/react/components/pages/editorPage/canvas.tsx index e73ce4efbd..0205e6e186 100644 --- a/src/react/components/pages/editorPage/canvas.tsx +++ b/src/react/components/pages/editorPage/canvas.tsx @@ -4,7 +4,7 @@ import { CanvasTools } from "vott-ct"; import { RegionData } from "vott-ct/lib/js/CanvasTools/Core/RegionData"; import { EditorMode, IAssetMetadata, - IProject, IRegion, RegionType, IBoundingBox, ISize, + IProject, IRegion, RegionType, } from "../../../../models/applicationState"; import CanvasHelpers from "./canvasHelpers"; import { AssetPreview, ContentSource } from "../../common/assetPreview/assetPreview"; @@ -73,7 +73,8 @@ export default class Canvas extends React.Component } public componentDidUpdate = async (prevProps: Readonly, prevState: Readonly) => { - if (this.props.selectedAsset.asset.id !== prevProps.selectedAsset.asset.id) { + // Handles asset changing + if (this.props.selectedAsset !== prevProps.selectedAsset) { this.setState({ currentAsset: this.props.selectedAsset }); } @@ -83,9 +84,16 @@ export default class Canvas extends React.Component this.editor.AS.setSelectionMode({ mode: this.props.selectionMode, template: options }); } + const assetIdChanged = this.state.currentAsset.asset.id !== prevState.currentAsset.asset.id; + + // When the selected asset has changed but is still the same asset id + if (!assetIdChanged && this.state.currentAsset !== prevState.currentAsset) { + this.refreshCanvasToolsRegions(); + } + // When the project tags change re-apply tags to regions if (this.props.project.tags !== prevProps.project.tags) { - this.updateCanvasToolsRegions(); + this.updateCanvasToolsRegionTags(); } // Handles when the canvas is enabled & disabled @@ -192,20 +200,12 @@ export default class Canvas extends React.Component return this.state.currentAsset.regions.filter((r) => selectedRegions.find((id) => r.id === id)); } - public updateCanvasToolsRegions = (asset?: IAssetMetadata): void => { - if (asset) { - this.setState({ - currentAsset: asset, - }); - this.clearAllRegions(); - this.addRegionsToCanvasTools(asset.regions); - } else { - for (const region of this.state.currentAsset.regions) { - this.editor.RM.updateTagsById( - region.id, - CanvasHelpers.getTagsDescriptor(this.props.project.tags, region), - ); - } + public updateCanvasToolsRegionTags = (): void => { + for (const region of this.state.currentAsset.regions) { + this.editor.RM.updateTagsById( + region.id, + CanvasHelpers.getTagsDescriptor(this.props.project.tags, region), + ); } } @@ -501,7 +501,7 @@ export default class Canvas extends React.Component this.editor.RM.updateTagsById(update.id, CanvasHelpers.getTagsDescriptor(this.props.project.tags, update)); } this.updateAssetRegions(updatedRegions); - this.updateCanvasToolsRegions(); + this.updateCanvasToolsRegionTags(); } /** diff --git a/src/react/components/pages/editorPage/editorPage.test.tsx b/src/react/components/pages/editorPage/editorPage.test.tsx index 4716f33bad..195a40e52d 100644 --- a/src/react/components/pages/editorPage/editorPage.test.tsx +++ b/src/react/components/pages/editorPage/editorPage.test.tsx @@ -126,12 +126,10 @@ describe("Editor Page Component", () => { const wrapper = createComponent(store, props); const editorPage = wrapper.find(EditorPage).childAt(0); - expect(getState(wrapper).project).toBeNull(); editorPage.props().project = testProject; await MockFactory.flushUi(); expect(editorPage.props().project).toEqual(testProject); - expect(getState(wrapper).project).toEqual(testProject); }); it("Loads and merges project assets with asset provider assets when state changes", async () => { @@ -671,7 +669,7 @@ describe("Editor Page Component", () => { }); const wrapper = createComponent(store, MockFactory.editorPageProps()); - expect(getState(wrapper).project.tags).toEqual(project.tags); + expect(wrapper.props().project.tags).toEqual(project.tags); }); it("create a new tag from text box", () => { @@ -681,16 +679,16 @@ describe("Editor Page Component", () => { currentProject: project, }); const wrapper = createComponent(store, MockFactory.editorPageProps()); - expect(getState(wrapper).project.tags).toEqual(project.tags); + expect(wrapper.props().project.tags).toEqual(project.tags); const newTagName = "My new tag"; wrapper.find("div.tag-input-toolbar-item.plus").simulate("click"); wrapper.find(".tag-input-box").simulate("keydown", { key: "Enter", target: { value: newTagName } }); - const stateTags = getState(wrapper).project.tags; + const projectTags = wrapper.props().project.tags; - expect(stateTags).toHaveLength(project.tags.length + 1); - expect(stateTags[stateTags.length - 1].name).toEqual(newTagName); + expect(projectTags).toHaveLength(project.tags.length + 1); + expect(projectTags[projectTags.length - 1].name).toEqual(newTagName); }); it("Remove a tag", async () => { @@ -703,15 +701,15 @@ describe("Editor Page Component", () => { const wrapper = createComponent(store, MockFactory.editorPageProps()); await waitForSelectedAsset(wrapper); - expect(getState(wrapper).project.tags).toEqual(project.tags); + expect(wrapper.props().project.tags).toEqual(project.tags); wrapper.find(".tag-content").last().simulate("click"); wrapper.find("i.tag-input-toolbar-icon.fas.fa-trash").simulate("click"); wrapper.find("button.btn.btn-danger").simulate("click"); await MockFactory.flushUi(); - const stateTags = getState(wrapper).project.tags; - expect(stateTags).toHaveLength(project.tags.length - 1); + const projectTags = wrapper.props().project.tags; + expect(projectTags).toHaveLength(project.tags.length - 1); }); it("Adds tag to locked tags when CmdOrCtrl clicked", async () => { diff --git a/src/react/components/pages/editorPage/editorPage.tsx b/src/react/components/pages/editorPage/editorPage.tsx index 24931b5ade..fc221d2e1a 100644 --- a/src/react/components/pages/editorPage/editorPage.tsx +++ b/src/react/components/pages/editorPage/editorPage.tsx @@ -29,7 +29,6 @@ import EditorSideBar from "./editorSideBar"; import { EditorToolbar } from "./editorToolbar"; import Alert from "../../common/alert/alert"; import Confirm from "../../common/confirm/confirm"; -import ProjectService from "../../../../services/projectService"; // tslint:disable-next-line:no-var-requires const tagColors = require("../../common/tagColors.json"); @@ -52,8 +51,6 @@ export interface IEditorPageProps extends RouteComponentProps, React.Props { public state: IEditorPageState = { - project: this.props.project, selectedTag: null, lockedTags: [], selectionMode: SelectionMode.RECT, @@ -135,7 +131,7 @@ export default class EditorPage extends React.Component) { if (this.props.project && this.state.assets.length === 0) { await this.loadProjectAssets(); } @@ -143,10 +139,13 @@ export default class EditorPage extends React.Component
+ onConfirm={this.onTagRenamed} /> + onConfirm={this.onTagDeleted} />
=> { - const { project, selectedAsset } = this.state; - const newProject = { - ...project, - tags: project.tags.map((t) => (t.name === tagName) ? {...t, name: newTagName} : t), - }; + const assetUpdates = await this.props.actions.updateProjectTag(this.props.project, tagName, newTagName); + const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id); - const assetService = new AssetService(newProject); - const asset = await assetService.renameTag(project.assets, tagName, newTagName, selectedAsset); - this.setState({ - project: newProject, - selectedAsset: asset || selectedAsset, - }, async () => { - await this.props.actions.saveProject(newProject); - if (this.canvas.current) { - this.canvas.current.updateCanvasToolsRegions(asset); + if (selectedAsset) { + if (selectedAsset) { + this.setState({ selectedAsset }); } - }); + } } /** @@ -348,23 +338,12 @@ export default class EditorPage extends React.Component => { - const { project, selectedAsset } = this.state; - const newProject = { - ...project, - tags: project.tags.filter((t) => t.name !== tagName), - }; + const assetUpdates = await this.props.actions.deleteProjectTag(this.props.project, tagName); + const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id); - const assetService = new AssetService(newProject); - const asset = await assetService.deleteTag(project.assets, tagName, selectedAsset); - this.setState({ - project: newProject, - selectedAsset: asset || selectedAsset, - }, async () => { - await this.props.actions.saveProject(newProject); - if (this.canvas.current) { - this.canvas.current.updateCanvasToolsRegions(asset); - } - }); + if (selectedAsset) { + this.setState({ selectedAsset }); + } } private onCtrlTagClicked = (tag: ITag): void => { @@ -502,17 +481,13 @@ export default class EditorPage extends React.Component { + private onTagsChanged = async (tags) => { const project = { ...this.props.project, tags, }; - this.setState({ project }, async () => { - await this.props.actions.saveProject(project); - if (this.canvas.current) { - this.canvas.current.updateCanvasToolsRegions(); - } - }); + + await this.props.actions.saveProject(project); } private onLockedTagsChanged = (lockedTags: string[]) => { @@ -677,4 +652,19 @@ export default class EditorPage extends React.Component { + const updatedAssets = [...this.state.assets]; + updatedAssets.forEach((asset) => { + const projectAsset = this.props.project.assets[asset.id]; + if (projectAsset) { + asset.state = projectAsset.state; + } + }); + + this.setState({ assets: updatedAssets }); + } } diff --git a/src/redux/actions/actionTypes.ts b/src/redux/actions/actionTypes.ts index 6f1ad9623c..2116460825 100644 --- a/src/redux/actions/actionTypes.ts +++ b/src/redux/actions/actionTypes.ts @@ -16,6 +16,8 @@ export enum ActionTypes { CLOSE_PROJECT_SUCCESS = "CLOSE_PROJECT_SUCCESS", LOAD_PROJECT_ASSETS_SUCCESS = "LOAD_PROJECT_ASSETS_SUCCESS", EXPORT_PROJECT_SUCCESS = "EXPORT_PROJECT_SUCCESS", + UPDATE_PROJECT_TAG_SUCCESS = "UPDATE_PROJECT_TAG_SUCCESS", + DELETE_PROJECT_TAG_SUCCESS = "DELETE_PROJECT_TAG_SUCCESS", // Connections LOAD_CONNECTION_SUCCESS = "LOAD_CONNECTION_SUCCESS", diff --git a/src/redux/actions/projectActions.ts b/src/redux/actions/projectActions.ts index 0b9b1b3670..5d853ec6ad 100644 --- a/src/redux/actions/projectActions.ts +++ b/src/redux/actions/projectActions.ts @@ -30,6 +30,8 @@ export default interface IProjectActions { loadAssets(project: IProject): Promise; loadAssetMetadata(project: IProject, asset: IAsset): Promise; saveAssetMetadata(project: IProject, assetMetadata: IAssetMetadata): Promise; + updateProjectTag(project: IProject, oldTagName: string, newTagName: string): Promise; + deleteProjectTag(project: IProject, tagName): Promise; } /** @@ -187,6 +189,69 @@ export function saveAssetMetadata( }; } +/** + * Updates a project and all asset references from oldTagName to newTagName + * @param project The project to update tags + * @param oldTagName The old tag name + * @param newTagName The new tag name + */ +export function updateProjectTag(project: IProject, oldTagName: string, newTagName: string) + : (dispatch: Dispatch, getState: () => IApplicationState) => Promise { + return async (dispatch: Dispatch, getState: () => IApplicationState) => { + // Find tags to rename + const assetService = new AssetService(project); + const assetUpdates = await assetService.renameTag(oldTagName, newTagName); + + // Save updated assets + await assetUpdates.forEachAsync(async (assetMetadata) => { + await saveAssetMetadata(project, assetMetadata)(dispatch); + }); + + const currentProject = getState().currentProject; + const updatedProject = { + ...currentProject, + tags: project.tags.map((t) => (t.name === oldTagName) ? { ...t, name: newTagName } : t), + }; + + // Save updated project tags + await saveProject(updatedProject)(dispatch, getState); + dispatch(updateProjectTagAction(updatedProject)); + + return assetUpdates; + }; +} + +/** + * Updates a project and all asset references from oldTagName to newTagName + * @param project The project to delete tags + * @param tagName The tag to delete + */ +export function deleteProjectTag(project: IProject, tagName) + : (dispatch: Dispatch, getState: () => IApplicationState) => Promise { + return async (dispatch: Dispatch, getState: () => IApplicationState) => { + // Find tags to rename + const assetService = new AssetService(project); + const assetUpdates = await assetService.deleteTag(tagName); + + // Save updated assets + await assetUpdates.forEachAsync(async (assetMetadata) => { + await saveAssetMetadata(project, assetMetadata)(dispatch); + }); + + const currentProject = getState().currentProject; + const updatedProject = { + ...currentProject, + tags: project.tags.filter((t) => t.name !== tagName), + }; + + // Save updated project tags + await saveProject(updatedProject)(dispatch, getState); + dispatch(deleteProjectTagAction(updatedProject)); + + return assetUpdates; + }; +} + /** * Initialize export provider, get export data and dispatch export project action * @param project - Project to export @@ -267,6 +332,20 @@ export interface IExportProjectAction extends IPayloadAction { type: ActionTypes.EXPORT_PROJECT_SUCCESS; } +/** + * Update Project Tag action type + */ +export interface IUpdateProjectTagAction extends IPayloadAction { + type: ActionTypes.UPDATE_PROJECT_TAG_SUCCESS; +} + +/** + * Delete project tag action type + */ +export interface IDeleteProjectTagAction extends IPayloadAction { + type: ActionTypes.DELETE_PROJECT_TAG_SUCCESS; +} + /** * Instance of Load Project action */ @@ -303,3 +382,13 @@ export const saveAssetMetadataAction = */ export const exportProjectAction = createPayloadAction(ActionTypes.EXPORT_PROJECT_SUCCESS); +/** + * Instance of Update project tag action + */ +export const updateProjectTagAction = + createPayloadAction(ActionTypes.UPDATE_PROJECT_TAG_SUCCESS); +/** + * Instance of Delete project tag action + */ +export const deleteProjectTagAction = + createPayloadAction(ActionTypes.DELETE_PROJECT_TAG_SUCCESS); diff --git a/src/services/assetService.test.ts b/src/services/assetService.test.ts index 02680fcd4d..e9037908c2 100644 --- a/src/services/assetService.test.ts +++ b/src/services/assetService.test.ts @@ -373,7 +373,7 @@ describe("Asset Service", () => { const project = populateProjectAssets(); const assetService = new AssetService(project); - await assetService.deleteTag(project.assets, tag1, assetMetadata); + await assetService.deleteTag(tag1); expect(saveMetadata).toBeCalledWith(expectedAssetMetadata); }); @@ -393,7 +393,7 @@ describe("Asset Service", () => { const expectedAssetMetadata: IAssetMetadata = MockFactory.createTestAssetMetadata(asset, []); const project = populateProjectAssets(); const assetService = new AssetService(project); - await assetService.deleteTag(project.assets, tag1, assetMetadata); + await assetService.deleteTag(tag1); expect(saveMetadata).toBeCalledWith(expectedAssetMetadata); }); @@ -425,7 +425,7 @@ describe("Asset Service", () => { const project = populateProjectAssets(); const assetService = new AssetService(project); - await assetService.renameTag(project.assets, tag1, newTag, assetMetadata); + await assetService.renameTag(tag1, newTag); expect(saveMetadata).toBeCalledWith(expectedAssetMetadata); }); }); diff --git a/src/services/assetService.ts b/src/services/assetService.ts index 4552624eae..2ab0ffabd0 100644 --- a/src/services/assetService.ts +++ b/src/services/assetService.ts @@ -206,64 +206,38 @@ export class AssetService { /** * Delete a tag from asset metadata files - * @param assets The assets containing tag to delete * @param tagName Name of tag to delete - * @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid - * needing to reload the asset in the editor page */ - public async deleteTag(assets: {[id: string]: IAsset}, tagName: string, - currentAsset?: IAssetMetadata): Promise { + public async deleteTag(tagName: string): Promise { const transformer = (tags) => tags.filter((t) => t !== tagName); - return await this.updateAssetTags(assets, tagName, transformer, currentAsset); + return await this.getUpdatedAssets(tagName, transformer); } /** * Rename a tag within asset metadata files - * @param assets The assets containing tag to rename * @param tagName Name of tag to rename - * @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid - * needing to reload the asset in the editor page */ - public async renameTag(assets: {[id: string]: IAsset}, tagName: string, newTagName: string, - currentAsset?: IAssetMetadata): Promise { + public async renameTag(tagName: string, newTagName: string): Promise { const transformer = (tags) => tags.map((t) => (t === tagName) ? newTagName : t); - return await this.updateAssetTags(assets, tagName, transformer, currentAsset); + return await this.getUpdatedAssets(tagName, transformer); } /** * Update tags within asset metadata files - * @param assets The assets containing tags to update * @param tagName Name of tag to update within project * @param transformer Function that accepts array of tags from a region and returns a modified array of tags - * @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid - * needing to reload the asset in the editor page - * @returns Modified `currentAsset`. Returns `null` if asset did not need to be modified - * or if `currentAsset` is null or undefined */ - private async updateAssetTags( - assets: {[id: string]: IAsset}, - tagName: string, - transformer: (tags: string[]) => string[], - currentAsset?: IAssetMetadata): Promise { - if (!assets) { - return; - } - const assetKeys = Object.keys(assets); - let result: IAssetMetadata; - + private async getUpdatedAssets(tagName: string, transformer: (tags: string[]) => string[]) + : Promise { // Loop over assets and update if necessary - await assetKeys.forEachAsync(async (assetKey) => { - const asset = assets[assetKey]; + const updates = await _.values(this.project.assets).mapAsync(async (asset) => { const assetMetadata = await this.getAssetMetadata(asset); - const updatedAssetMetadata = this.updateTagInAssetMetadata(assetMetadata, tagName, transformer); - if (updatedAssetMetadata) { - await this.save(updatedAssetMetadata); - if (currentAsset && currentAsset.asset.id === updatedAssetMetadata.asset.id) { - result = updatedAssetMetadata; - } - } + const isUpdated = this.updateTagInAssetMetadata(assetMetadata, tagName, transformer); + + return isUpdated ? assetMetadata : null; }); - return result; + + return updates.filter((assetMetadata) => !!assetMetadata); } /** @@ -273,9 +247,12 @@ export class AssetService { * @param transformer Function that accepts array of tags from a region and returns a modified array of tags * @returns Modified asset metadata object or null if object does not need to be modified */ - private updateTagInAssetMetadata(assetMetadata: IAssetMetadata, tagName: string, - transformer: (tags: string[]) => string[]): IAssetMetadata { + private updateTagInAssetMetadata( + assetMetadata: IAssetMetadata, + tagName: string, + transformer: (tags: string[]) => string[]): boolean { let foundTag = false; + for (const region of assetMetadata.regions) { if (region.tags.find((t) => t === tagName)) { foundTag = true; @@ -285,15 +262,15 @@ export class AssetService { if (foundTag) { assetMetadata.regions = assetMetadata.regions.filter((region) => region.tags.length > 0); assetMetadata.asset.state = (assetMetadata.regions.length) ? AssetState.Tagged : AssetState.Visited; - return assetMetadata; + return true; } - return null; + + return false; } private async getRegionsFromTFRecord(asset: IAsset): Promise { const objectArray = await this.getTFRecordMetadata(asset); const regions: IRegion[] = []; - const tags: string[] = []; // Add Regions from TFRecord in Regions for (let index = 0; index < objectArray.textArray.length; index++) {