Skip to content

Commit 717d538

Browse files
committed
fix: prevent copying assets elsewhere than at least the /assets folder with a proper error message
1 parent aabec17 commit 717d538

File tree

2 files changed

+130
-56
lines changed

2 files changed

+130
-56
lines changed

editor/src/editor/layout/assets-browser.tsx

Lines changed: 124 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { openMultipleFilesAndFoldersDialog } from "../../tools/dialog";
3535
import { findAvailableFilename, normalizedGlob } from "../../tools/fs";
3636
import { loadSavedThumbnailsCache } from "../../tools/assets/thumbnail";
3737
import { assetsCache, saveAssetsCache } from "../../tools/assets/cache";
38+
import { assetsAllSupportedExtensions } from "../../tools/assets/extensions";
3839
import { checkProjectCachedCompressedTextures, processingCompressedTextures } from "../../tools/assets/ktx";
3940

4041
import { ICommandPaletteType } from "../dialogs/command-palette/command-palette";
@@ -44,7 +45,7 @@ import { loadScene } from "../../project/load/scene";
4445
import { saveProject, saveProjectConfiguration } from "../../project/save/save";
4546
import { onProjectConfigurationChangedObservable, projectConfiguration } from "../../project/configuration";
4647

47-
import { showConfirm, showPrompt } from "../../ui/dialog";
48+
import { showAlert, showConfirm, showPrompt } from "../../ui/dialog";
4849

4950
import { Input } from "../../ui/shadcn/ui/input";
5051
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from "../../ui/shadcn/ui/breadcrumb";
@@ -229,6 +230,20 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
229230
listenParticleAssetsEvents(this.props.editor);
230231
}
231232

233+
/**
234+
* Returns wether or not the currently browsed folder is the /assets folder.
235+
*/
236+
public isAssetsFolder(): boolean {
237+
return this.state.browsedPath?.startsWith(join(dirname(projectConfiguration.path!), "/assets")) ?? false;
238+
}
239+
240+
/**
241+
* Returns wether or not the currently browsed folder is the /src folder.
242+
*/
243+
public isSrcFolder(): boolean {
244+
return this.state.browsedPath?.startsWith(join(dirname(projectConfiguration.path!), "/src")) ?? false;
245+
}
246+
232247
private async _refreshFilesTreeNodes(path: string): Promise<void> {
233248
const files = await normalizedGlob(join(dirname(path), "**"), {
234249
ignore: {
@@ -732,66 +747,20 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
732747
Paste
733748
</ContextMenuItem>
734749

735-
<ContextMenuSeparator />
736-
737-
<ContextMenuSub>
738-
<ContextMenuSubTrigger className="flex items-center gap-2">
739-
<AiOutlinePlus className="w-5 h-5" /> Add
740-
</ContextMenuSubTrigger>
741-
<ContextMenuSubContent>
742-
<ContextMenuItem onClick={() => this._handleAddScene()}>Scene</ContextMenuItem>
750+
{(this.isAssetsFolder() || this.isSrcFolder()) && (
751+
<>
743752
<ContextMenuSeparator />
744-
{getMaterialCommands(this.props.editor).map((command) => (
745-
<ContextMenuItem key={command.key} onClick={() => this._handleAddMaterial(command)}>
746-
{command.text}
747-
</ContextMenuItem>
748-
))}
749-
750-
<ContextMenuSeparator />
751-
<ContextMenuItem onClick={() => this._handleAddNodeMaterialFromSnippet()}>Node Material From Snippet...</ContextMenuItem>
752-
<ContextMenuSeparator />
753-
754753
<ContextMenuSub>
755-
<ContextMenuSubTrigger className="flex items-center gap-2">Materials Library</ContextMenuSubTrigger>
754+
<ContextMenuSubTrigger className="flex items-center gap-2">
755+
<AiOutlinePlus className="w-5 h-5" /> Add
756+
</ContextMenuSubTrigger>
756757
<ContextMenuSubContent>
757-
{getMaterialsLibraryCommands(this.props.editor).map((command) => (
758-
<ContextMenuItem key={command.key} onClick={() => this._handleAddMaterial(command)}>
759-
{command.text}
760-
</ContextMenuItem>
761-
))}
758+
{this.isAssetsFolder() && this._getAssetsContextMenuItems()}
759+
{this.isSrcFolder() && this._getSrcContextMenuItems()}
762760
</ContextMenuSubContent>
763761
</ContextMenuSub>
764-
765-
{this.props.editor.state.enableExperimentalFeatures && (
766-
<>
767-
<ContextMenuSeparator />
768-
<ContextMenuItem onClick={() => this._handleAddNodeParticleSystem()}>Node Particle System</ContextMenuItem>
769-
<ContextMenuSeparator />
770-
<ContextMenuItem onClick={() => this._handleAddCinematic()}>Cinematic</ContextMenuItem>
771-
</>
772-
)}
773-
774-
{this.props.editor.state.enableExperimentalFeatures && (
775-
<>
776-
<ContextMenuSeparator />
777-
<ContextMenuItem onClick={() => this._handleAddFullScreenGUI()}>Full Screen GUI</ContextMenuItem>
778-
</>
779-
)}
780-
781-
{this.state.browsedPath?.startsWith(join(dirname(projectConfiguration.path!), "/src")) && (
782-
<>
783-
<ContextMenuSeparator />
784-
<ContextMenuSub>
785-
<ContextMenuSubTrigger className="flex items-center gap-2">Script</ContextMenuSubTrigger>
786-
<ContextMenuSubContent>
787-
<ContextMenuItem onClick={() => this._handleAddScript("class")}>Class-based</ContextMenuItem>
788-
<ContextMenuItem onClick={() => this._handleAddScript("function")}>Function-based</ContextMenuItem>
789-
</ContextMenuSubContent>
790-
</ContextMenuSub>
791-
</>
792-
)}
793-
</ContextMenuSubContent>
794-
</ContextMenuSub>
762+
</>
763+
)}
795764

796765
<ContextMenuSeparator />
797766

@@ -804,6 +773,60 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
804773
);
805774
}
806775

776+
private _getAssetsContextMenuItems(): ReactNode {
777+
return (
778+
<>
779+
<ContextMenuItem onClick={() => this._handleAddScene()}>Scene</ContextMenuItem>
780+
<ContextMenuSeparator />
781+
{getMaterialCommands(this.props.editor).map((command) => (
782+
<ContextMenuItem key={command.key} onClick={() => this._handleAddMaterial(command)}>
783+
{command.text}
784+
</ContextMenuItem>
785+
))}
786+
787+
<ContextMenuSeparator />
788+
<ContextMenuItem onClick={() => this._handleAddNodeMaterialFromSnippet()}>Node Material From Snippet...</ContextMenuItem>
789+
<ContextMenuSeparator />
790+
791+
<ContextMenuSub>
792+
<ContextMenuSubTrigger className="flex items-center gap-2">Materials Library</ContextMenuSubTrigger>
793+
<ContextMenuSubContent>
794+
{getMaterialsLibraryCommands(this.props.editor).map((command) => (
795+
<ContextMenuItem key={command.key} onClick={() => this._handleAddMaterial(command)}>
796+
{command.text}
797+
</ContextMenuItem>
798+
))}
799+
</ContextMenuSubContent>
800+
</ContextMenuSub>
801+
802+
{this.props.editor.state.enableExperimentalFeatures && (
803+
<>
804+
<ContextMenuSeparator />
805+
<ContextMenuItem onClick={() => this._handleAddNodeParticleSystem()}>Node Particle System</ContextMenuItem>
806+
<ContextMenuSeparator />
807+
<ContextMenuItem onClick={() => this._handleAddCinematic()}>Cinematic</ContextMenuItem>
808+
</>
809+
)}
810+
811+
{this.props.editor.state.enableExperimentalFeatures && (
812+
<>
813+
<ContextMenuSeparator />
814+
<ContextMenuItem onClick={() => this._handleAddFullScreenGUI()}>Full Screen GUI</ContextMenuItem>
815+
</>
816+
)}
817+
</>
818+
);
819+
}
820+
821+
private _getSrcContextMenuItems(): ReactNode {
822+
return (
823+
<>
824+
<ContextMenuItem onClick={() => this._handleAddScript("class")}>Class-based Script</ContextMenuItem>
825+
<ContextMenuItem onClick={() => this._handleAddScript("function")}>Function-based Script</ContextMenuItem>
826+
</>
827+
);
828+
}
829+
807830
private _getAssetBrowserItem(filename: string, key: string, selected: boolean): ReactNode {
808831
const extension = extname(filename).toLowerCase();
809832

@@ -887,6 +910,8 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
887910
return;
888911
}
889912

913+
let assetFileNotInAssetsFolder = false;
914+
890915
const filesToCopy: Record<string, string> = {};
891916

892917
for (let i = 0, len = event.dataTransfer.files.length; i < len; ++i) {
@@ -898,9 +923,30 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
898923
const path = webUtils.getPathForFile(file).replace(/\\/g, "/");
899924
const absolutePath = join(this.state.browsedPath, basename(path));
900925

926+
if (!this.isAssetsFolder()) {
927+
const extension = extname(path).toLowerCase();
928+
929+
if (assetsAllSupportedExtensions.includes(extension)) {
930+
assetFileNotInAssetsFolder = true;
931+
continue;
932+
}
933+
}
934+
901935
filesToCopy[path] = absolutePath;
902936
}
903937

938+
if (assetFileNotInAssetsFolder) {
939+
showAlert(
940+
"Warning",
941+
<div>
942+
You tried to import assets in a directory not located at least in the root "assets" folder.
943+
<br />
944+
To prevent broken references, these files were not copied. Please drag'n'drop them at least in the /assets folder.
945+
</div>,
946+
true
947+
);
948+
}
949+
904950
await Promise.all(
905951
Object.entries(filesToCopy).map(async ([source, destination]) => {
906952
const fStat = await stat(source);
@@ -940,6 +986,8 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
940986
title: "Import Files & Folders",
941987
});
942988

989+
let assetFileNotInAssetsFolder = false;
990+
943991
await Promise.all(
944992
files.map(async (file) => {
945993
const destination = join(this.state.browsedPath!, basename(file));
@@ -950,11 +998,31 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
950998
recursive: true,
951999
});
9521000
} else {
1001+
if (!this.isAssetsFolder()) {
1002+
const extension = extname(file).toLowerCase();
1003+
if (assetsAllSupportedExtensions.includes(extension)) {
1004+
assetFileNotInAssetsFolder = true;
1005+
return;
1006+
}
1007+
}
1008+
9531009
await copyFile(file, destination);
9541010
}
9551011
})
9561012
);
9571013

1014+
if (assetFileNotInAssetsFolder) {
1015+
showAlert(
1016+
"Warning",
1017+
<div>
1018+
You tried to import assets in a directory not located at least in the root "assets" folder.
1019+
<br />
1020+
To prevent broken references, these files were not copied. Please drag'n'drop them at least in the /assets folder.
1021+
</div>,
1022+
true
1023+
);
1024+
}
1025+
9581026
this._refreshItems(this.state.browsedPath);
9591027
}
9601028

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const assetsImageExtensions = [".png", ".webp", ".jpg", ".bmp", ".jpeg"];
2+
export const assetsAudioExtensions = [".mp3", ".wav", ".wave", ".ogg"];
3+
export const assetsVideoExtensions = [".mp4", ".webm", ".ogg"];
4+
export const assetsModelExtensions = [".gltf", ".glb", ".obj", ".babylon", ".stl", ".3ds", ".fbx"];
5+
6+
export const assetsAllSupportedExtensions = [...assetsImageExtensions, ...assetsAudioExtensions, ...assetsVideoExtensions, ...assetsModelExtensions];

0 commit comments

Comments
 (0)