From dd4d67ede4d44ba34e23f95ad7bb3c16fb4e47ae Mon Sep 17 00:00:00 2001 From: Martin Vladic Date: Sun, 17 Nov 2024 12:58:36 +0100 Subject: [PATCH] Implemented: Import images in bulk #532 --- .../project-editor/features/bitmap/bitmap.tsx | 60 ++++++++++++---- packages/project-editor/lvgl/widgets/Base.tsx | 6 +- .../ui-components/FileInput.tsx | 72 +++++++++++++++++++ 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/packages/project-editor/features/bitmap/bitmap.tsx b/packages/project-editor/features/bitmap/bitmap.tsx index 48847784..e73440bc 100644 --- a/packages/project-editor/features/bitmap/bitmap.tsx +++ b/packages/project-editor/features/bitmap/bitmap.tsx @@ -41,7 +41,7 @@ import { getThemedColor } from "project-editor/features/style/theme"; import { showGenericDialog } from "project-editor/core/util"; -import { AbsoluteFileInput } from "project-editor/ui-components/FileInput"; +import { MultipleAbsoluteFileInput } from "project-editor/ui-components/FileInput"; import { getProject, findStyle } from "project-editor/project/project"; import { ProjectEditor } from "project-editor/project-editor-interface"; @@ -241,12 +241,18 @@ export class Bitmap extends EezObject { validators.required, validators.invalidCharacters("."), validators.unique({}, parent) - ] + ], + visible: (values: any) => { + return !( + values.imageFilePaths && + values.imageFilePaths.length > 1 + ); + } }, { - name: "imageFilePath", + name: "imageFilePaths", displayName: "Image", - type: AbsoluteFileInput, + type: MultipleAbsoluteFileInput, validators: [validators.required], options: { filters: [ @@ -287,16 +293,46 @@ export class Bitmap extends EezObject { } }); - const name: string = result.values.name; const bpp: number = result.values.bpp; - return createBitmap( - projectStore, - result.values.imageFilePath, - undefined, - name, - bpp - ); + if (result.values.imageFilePaths.length == 1) { + const name: string = result.values.name; + + return createBitmap( + projectStore, + result.values.imageFilePaths[0], + undefined, + name, + bpp + ); + } else { + projectStore.undoManager.setCombineCommands(true); + + for (let i = 0; i < result.values.imageFilePaths.length; i++) { + const filePath = result.values.imageFilePaths[i]; + + let name = getUniquePropertyValue( + projectStore.project.bitmaps, + "name", + path.parse(filePath).name + ) as string; + + const bitmap = await createBitmap( + projectStore, + filePath, + undefined, + name, + bpp + ); + if (bitmap) { + projectStore.addObject(parent, bitmap); + } + } + + projectStore.undoManager.setCombineCommands(false); + + return undefined; + } }, icon: "material:image", afterLoadHook: (bitmap: Bitmap, project) => { diff --git a/packages/project-editor/lvgl/widgets/Base.tsx b/packages/project-editor/lvgl/widgets/Base.tsx index 8d845391..d19d5011 100644 --- a/packages/project-editor/lvgl/widgets/Base.tsx +++ b/packages/project-editor/lvgl/widgets/Base.tsx @@ -1054,8 +1054,10 @@ export class LVGLWidget extends Widget { referencedObjectCollectionPath: "lvglGroups/groups", propertyGridGroup: generalGroup, hideInPropertyGrid: (widget: LVGLWidget) => - ProjectEditor.getProject(widget).lvglGroups.groups.length == - 0 || widget instanceof LVGLScreenWidget + (ProjectEditor.getProject(widget).lvglGroups.groups + .length == 0 && + !widget.group) || + widget instanceof LVGLScreenWidget }, { name: "groupIndex", diff --git a/packages/project-editor/ui-components/FileInput.tsx b/packages/project-editor/ui-components/FileInput.tsx index bd26b85f..fdbfe9fd 100644 --- a/packages/project-editor/ui-components/FileInput.tsx +++ b/packages/project-editor/ui-components/FileInput.tsx @@ -133,6 +133,78 @@ export class AbsoluteFileInput extends FieldComponent { //////////////////////////////////////////////////////////////////////////////// +export class MultipleAbsoluteFileInput extends FieldComponent { + static contextType = ProjectContext; + declare context: React.ContextType; + + onClear = () => { + this.props.onChange(undefined); + }; + + onSelect = async () => { + const result = await dialog.showOpenDialog({ + properties: ["openFile", "multiSelections"], + filters: this.props.fieldProperties.options.filters + }); + + if (result.filePaths && result.filePaths.length > 0) { + this.props.onChange(result.filePaths); + } + }; + + get value() { + const value = this.props.values[this.props.fieldProperties.name]; + if (!value) { + return ""; + } + + if (value.length > 1) { + return `${value.length} images selected`; + } + + return value; + } + + render() { + let clearButton: JSX.Element | undefined; + + if (this.props.values[this.props.fieldProperties.name]) { + clearButton = ( + + ); + } + + return ( +
+ + <> + {clearButton} + + +
+ ); + } +} + +//////////////////////////////////////////////////////////////////////////////// + export class AbsoluteFileSaveInput extends FieldComponent { static contextType = ProjectContext; declare context: React.ContextType;