feat(FileSelectionDialogModule): add move-to-folder dialog, recency sort and lastModified tracking
This commit is contained in:
@@ -3,6 +3,10 @@ package de.extio.game_engine.storage;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record StorageItemDescriptor(UUID id, String name, List<String> path) {
|
||||
public record StorageItemDescriptor(UUID id, String name, List<String> path, Long lastModified) {
|
||||
|
||||
public StorageItemDescriptor(final UUID id, final String name, final List<String> path) {
|
||||
this(id, name, path, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import de.extio.game_engine.event.EventService;
|
||||
import de.extio.game_engine.i18n.LocalizationService;
|
||||
import de.extio.game_engine.renderer.work.RenderingBoPool;
|
||||
import de.extio.game_engine.storage.dialog.FileSelectionDialogModule;
|
||||
import de.extio.game_engine.storage.dialog.FileSelectionMoveDialogModule;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(name = "game-engine.storage.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@@ -25,7 +26,15 @@ public class StorageServiceAutoConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = "game-engine.storage.dialog.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true)
|
||||
FileSelectionDialogModule fileSelectionDialog(final ApplicationContext applicationContext, final EventService eventService, final LocalizationService localizationService, final RenderingBoPool renderingBoPool, final StorageService storageService) {
|
||||
return new FileSelectionDialogModule(applicationContext, eventService, localizationService, renderingBoPool, storageService);
|
||||
FileSelectionMoveDialogModule fileSelectionMoveDialog(final ApplicationContext applicationContext, final EventService eventService, final LocalizationService localizationService, final RenderingBoPool renderingBoPool, final StorageService storageService) {
|
||||
return new FileSelectionMoveDialogModule(applicationContext, eventService, localizationService, renderingBoPool, storageService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = "game-engine.storage.dialog.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true)
|
||||
FileSelectionDialogModule fileSelectionDialog(final ApplicationContext applicationContext, final EventService eventService, final LocalizationService localizationService, final RenderingBoPool renderingBoPool, final StorageService storageService, final FileSelectionMoveDialogModule fileSelectionMoveDialog) {
|
||||
return new FileSelectionDialogModule(applicationContext, eventService, localizationService, renderingBoPool, storageService, fileSelectionMoveDialog);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -129,6 +130,30 @@ public class StorageServiceImpl implements StorageService {
|
||||
return DATA_LOCATION.resolve(id.toString());
|
||||
}
|
||||
|
||||
private StorageItemDescriptor withLastModified(final StorageItemDescriptor descriptor) {
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final var lastModified = Files.getLastModifiedTime(this.getFilePath(descriptor.id())).toMillis();
|
||||
return new StorageItemDescriptor(descriptor.id(), descriptor.name(), descriptor.path(), lastModified);
|
||||
}
|
||||
catch (final IOException e) {
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
private List<StorageItemDescriptor> withLastModified(final List<StorageItemDescriptor> descriptors) {
|
||||
if (descriptors == null) {
|
||||
return null;
|
||||
}
|
||||
final var result = new ArrayList<StorageItemDescriptor>(descriptors.size());
|
||||
for (final var descriptor : descriptors) {
|
||||
result.add(this.withLastModified(descriptor));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addToIndexAndSave(final UUID id, final String name, final List<String> path) {
|
||||
final StorageItemDescriptor descriptor = new StorageItemDescriptor(id, name, path == null ? List.of() : path);
|
||||
this.index.add(descriptor);
|
||||
@@ -383,29 +408,29 @@ public class StorageServiceImpl implements StorageService {
|
||||
@Override
|
||||
public Optional<StorageItemDescriptor> searchById(final UUID id) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
return this.index.getById(id);
|
||||
return this.index.getById(id).map(this::withLastModified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<StorageItemDescriptor> searchByPath(final List<String> path, final String name) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
return this.index.find(path, name);
|
||||
return this.index.find(path, name).map(this::withLastModified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StorageItemDescriptor> searchByPattern(final List<String> basePath, final String pattern, final boolean recursive) {
|
||||
Objects.requireNonNull(pattern, "pattern");
|
||||
return this.index.findByFilenamePattern(basePath, pattern, recursive);
|
||||
return this.withLastModified(this.index.findByFilenamePattern(basePath, pattern, recursive));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StorageItemDescriptor> listPath(final List<String> path, final boolean recursive) {
|
||||
return this.index.list(path, recursive);
|
||||
return this.withLastModified(this.index.list(path, recursive));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StorageItemDescriptor> listAll() {
|
||||
return this.index.listAll();
|
||||
return this.withLastModified(this.index.listAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.storage.StorageItemDescriptor;
|
||||
import de.extio.game_engine.storage.StorageResource;
|
||||
import de.extio.game_engine.storage.StorageService;
|
||||
import de.extio.game_engine.storage.dialog.FileSelectionMoveDialogModule.FileSelectionMoveDialogResponse;
|
||||
|
||||
public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
|
||||
@@ -91,6 +92,8 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
|
||||
private static final String BUTTON_DELETE = "FileSelectionDialog_Button_Delete";
|
||||
|
||||
private static final String BUTTON_MOVE = "FileSelectionDialog_Button_Move";
|
||||
|
||||
private static final String BUTTON_OK = "FileSelectionDialog_Button_Ok";
|
||||
|
||||
private static final String BUTTON_CANCEL = "FileSelectionDialog_Button_Cancel";
|
||||
@@ -105,12 +108,15 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
|
||||
private final StorageService storageService;
|
||||
|
||||
public FileSelectionDialogModule(final ApplicationContext applicationContext, final EventService eventService, final LocalizationService localizationService, final RenderingBoPool renderingBoPool, final StorageService storageService) {
|
||||
private final FileSelectionMoveDialogModule moveDialogModule;
|
||||
|
||||
public FileSelectionDialogModule(final ApplicationContext applicationContext, final EventService eventService, final LocalizationService localizationService, final RenderingBoPool renderingBoPool, final StorageService storageService, final FileSelectionMoveDialogModule moveDialogModule) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.eventService = eventService;
|
||||
this.localizationService = localizationService;
|
||||
this.renderingBoPool = renderingBoPool;
|
||||
this.storageService = storageService;
|
||||
this.moveDialogModule = moveDialogModule;
|
||||
}
|
||||
|
||||
private Window dialogWindow;
|
||||
@@ -182,6 +188,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
@Override
|
||||
public void onShow() {
|
||||
this.eventService.register(UiControlEvent.class, this.getId(), this::onUiControlEvent);
|
||||
this.eventService.register(FileSelectionMoveDialogResponse.class, this.getId(), this::onMoveResponse);
|
||||
this.getModuleService().changeDisplayState(this.dialogWindow.getId(), true);
|
||||
if (this.modal) {
|
||||
this.getModuleService().hideExcept(this.getId(), this.dialogWindow.getId());
|
||||
@@ -191,6 +198,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
@Override
|
||||
public void onHide() {
|
||||
this.eventService.unregister(UiControlEvent.class, this.getId());
|
||||
this.eventService.unregister(FileSelectionMoveDialogResponse.class, this.getId());
|
||||
this.getModuleService().changeDisplayState(this.dialogWindow.getId(), false);
|
||||
if (this.modal) {
|
||||
this.getModuleService().restoreVisibility();
|
||||
@@ -257,6 +265,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
case BUTTON_NEW_ITEM -> this.selectNewItem();
|
||||
case BUTTON_RENAME -> this.renameSelection();
|
||||
case BUTTON_DELETE -> this.deleteSelection();
|
||||
case BUTTON_MOVE -> this.openMoveDialog();
|
||||
case BUTTON_OK -> this.onOk();
|
||||
case BUTTON_CANCEL -> this.onCancel();
|
||||
default -> {
|
||||
@@ -346,7 +355,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
}
|
||||
yOffset += FIELD_HEIGHT + SPACING;
|
||||
|
||||
final boolean showFolderField = this.recursive && this.mode != FileSelectionMode.LOAD;
|
||||
final boolean showFolderField = this.recursive;
|
||||
if (showFolderField) {
|
||||
final var folderLabel = this.renderingBoPool.acquire("FileSelectionDialog_Label_NewFolder", ControlRenderingBo.class)
|
||||
.setType(LabelControl.class)
|
||||
@@ -379,7 +388,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
this.buildItemsList(contentWidth);
|
||||
|
||||
var buttonX = Window.MARGIN_LEFT;
|
||||
if (this.mode != FileSelectionMode.LOAD && this.recursive) {
|
||||
if (this.recursive) {
|
||||
final var newFolderButton = this.renderingBoPool.acquire(BUTTON_NEW_FOLDER, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-190"))
|
||||
@@ -413,6 +422,17 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
this.dialogWindow.putRenderingBo(deleteButton);
|
||||
buttonX += BUTTON_WIDTH + SPACING;
|
||||
|
||||
final var moveButton = this.renderingBoPool.acquire(BUTTON_MOVE, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-644"))
|
||||
.setVisible(true)
|
||||
.setEnabled(this.selectedDescriptor != null)
|
||||
.withDimensionAbsolute(BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
.withPositionRelative(buttonX, bottomRowY)
|
||||
.setLayer(RenderingBoLayer.UI1);
|
||||
this.dialogWindow.putRenderingBo(moveButton);
|
||||
buttonX += BUTTON_WIDTH + SPACING;
|
||||
|
||||
final var okButton = this.renderingBoPool.acquire(BUTTON_OK, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-3"))
|
||||
@@ -464,7 +484,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
}
|
||||
|
||||
final var sortedFolders = folderNames.stream().sorted(String::compareToIgnoreCase).toList();
|
||||
currentItems.sort(Comparator.comparing(StorageItemDescriptor::name, String.CASE_INSENSITIVE_ORDER));
|
||||
currentItems.sort(this.timestampThenNameComparator());
|
||||
|
||||
final var entries = new ArrayList<DisplayEntry>();
|
||||
if (this.recursive && this.currentPath.size() > this.basePath.size()) {
|
||||
@@ -547,7 +567,24 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
}
|
||||
return this.storageService.listPath(queryPath, this.recursive);
|
||||
}
|
||||
|
||||
|
||||
private Comparator<StorageItemDescriptor> timestampThenNameComparator() {
|
||||
return (a, b) -> {
|
||||
final var timestampA = a.lastModified();
|
||||
final var timestampB = b.lastModified();
|
||||
if (timestampA != null && timestampB != null && !timestampA.equals(timestampB)) {
|
||||
return Long.compare(timestampB, timestampA);
|
||||
}
|
||||
if (timestampA != null) {
|
||||
return -1;
|
||||
}
|
||||
if (timestampB != null) {
|
||||
return 1;
|
||||
}
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(a.name(), b.name());
|
||||
};
|
||||
}
|
||||
|
||||
private void createFolder() {
|
||||
if (!this.canCreateFolder()) {
|
||||
return;
|
||||
@@ -594,7 +631,25 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
this.nameFieldValue = "";
|
||||
this.buildDialog();
|
||||
}
|
||||
|
||||
|
||||
private void openMoveDialog() {
|
||||
if (this.selectedDescriptor == null) {
|
||||
return;
|
||||
}
|
||||
this.moveDialogModule.open(this.selectedDescriptor, this.basePath, this.dialogWindow);
|
||||
}
|
||||
|
||||
private void onMoveResponse(final FileSelectionMoveDialogResponse event) {
|
||||
if (event == null || !event.confirmed() || this.requestId == null || this.selectedDescriptor == null || event.targetPath() == null) {
|
||||
return;
|
||||
}
|
||||
this.storageService.moveById(this.selectedDescriptor.id(), event.targetPath(), this.selectedDescriptor.name());
|
||||
this.selectedDescriptor = null;
|
||||
this.selectionIsNew = false;
|
||||
this.nameFieldValue = "";
|
||||
this.buildDialog();
|
||||
}
|
||||
|
||||
private void onOk() {
|
||||
if (!this.canConfirm()) {
|
||||
return;
|
||||
@@ -609,7 +664,9 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
this.eventService.fire(new FileSelectionDialogResponse(this.requestId, false, this.mode, null, null, null, null, false));
|
||||
if (this.requestId != null) {
|
||||
this.eventService.fire(new FileSelectionDialogResponse(this.requestId, false, this.mode, null, null, null, null, false));
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
|
||||
@@ -642,7 +699,7 @@ public class FileSelectionDialogModule extends AbstractClientModule {
|
||||
}
|
||||
|
||||
private boolean canCreateFolder() {
|
||||
if (!this.recursive || this.mode == FileSelectionMode.LOAD) {
|
||||
if (!this.recursive) {
|
||||
return false;
|
||||
}
|
||||
if (this.newFolderValue == null || this.newFolderValue.isBlank()) {
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
package de.extio.game_engine.storage.dialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import de.extio.game_engine.event.Event;
|
||||
import de.extio.game_engine.event.EventService;
|
||||
import de.extio.game_engine.i18n.LocalizationService;
|
||||
import de.extio.game_engine.module.AbstractClientModule;
|
||||
import de.extio.game_engine.renderer.RendererControl;
|
||||
import de.extio.game_engine.renderer.container.ScrollArea;
|
||||
import de.extio.game_engine.renderer.container.Window;
|
||||
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
|
||||
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.ButtonControl;
|
||||
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.LabelControl;
|
||||
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.LabelData;
|
||||
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TextfieldControl;
|
||||
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TextfieldData;
|
||||
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
|
||||
import de.extio.game_engine.renderer.model.bo.HorizontalAlignment;
|
||||
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||
import de.extio.game_engine.renderer.work.RenderingBoPool;
|
||||
import de.extio.game_engine.spatial2.model.Area2;
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.storage.StorageItemDescriptor;
|
||||
import de.extio.game_engine.storage.StorageService;
|
||||
|
||||
public class FileSelectionMoveDialogModule extends AbstractClientModule {
|
||||
|
||||
public record FileSelectionMoveDialogResponse(
|
||||
boolean confirmed,
|
||||
List<String> targetPath) implements Event {
|
||||
}
|
||||
|
||||
private static final int WINDOW_WIDTH = 700;
|
||||
|
||||
private static final int WINDOW_HEIGHT = 620;
|
||||
|
||||
private static final int TITLE_HEIGHT = 46;
|
||||
|
||||
private static final int LABEL_HEIGHT = 26;
|
||||
|
||||
private static final int FIELD_HEIGHT = 36;
|
||||
|
||||
private static final int BUTTON_HEIGHT = 42;
|
||||
|
||||
private static final int BUTTON_WIDTH = 150;
|
||||
|
||||
private static final int SPACING = 12;
|
||||
|
||||
private static final int LIST_PADDING = 8;
|
||||
|
||||
private static final int ROW_SPACING = 6;
|
||||
|
||||
private static final String LOC_TITLE = "ecyoa-645";
|
||||
|
||||
private static final String FOLDER_PREFIX = "FileSelectionMoveDialog_Folder_";
|
||||
|
||||
private static final String NAME_FIELD_ID = "FileSelectionMoveDialog_NameField";
|
||||
|
||||
private static final String BUTTON_MOVE = "FileSelectionMoveDialog_Button_Move";
|
||||
|
||||
private static final String BUTTON_CANCEL = "FileSelectionMoveDialog_Button_Cancel";
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final EventService eventService;
|
||||
|
||||
private final LocalizationService localizationService;
|
||||
|
||||
private final RenderingBoPool renderingBoPool;
|
||||
|
||||
private final StorageService storageService;
|
||||
|
||||
public FileSelectionMoveDialogModule(final ApplicationContext applicationContext, final EventService eventService, final LocalizationService localizationService, final RenderingBoPool renderingBoPool, final StorageService storageService) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.eventService = eventService;
|
||||
this.localizationService = localizationService;
|
||||
this.renderingBoPool = renderingBoPool;
|
||||
this.storageService = storageService;
|
||||
}
|
||||
|
||||
private Window moveWindow;
|
||||
|
||||
private ScrollArea folderScrollArea;
|
||||
|
||||
private StorageItemDescriptor descriptor;
|
||||
|
||||
private List<String> basePath = List.of();
|
||||
|
||||
private List<String> currentPath = List.of();
|
||||
|
||||
private String nameFieldValue = "";
|
||||
|
||||
private final Map<String, List<String>> folderPathByControlId = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean isProtected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
this.moveWindow = this.applicationContext.getBean(Window.class);
|
||||
this.moveWindow.setNormalizedDimension(ImmutableCoordI2.create(WINDOW_WIDTH, WINDOW_HEIGHT));
|
||||
this.moveWindow.setNormalizedPosition(this.centeredPosition(this.moveWindow.getNormalizedDimension()));
|
||||
this.moveWindow.setDraggable(true);
|
||||
this.moveWindow.setCloseButton(true);
|
||||
this.moveWindow.setOnCloseAction(this::onCancel);
|
||||
|
||||
this.folderScrollArea = this.applicationContext.getBean(ScrollArea.class);
|
||||
this.moveWindow.addComponent(this.folderScrollArea);
|
||||
|
||||
this.getModuleService().changeActiveState(this.getId(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnload() {
|
||||
this.getModuleService().unloadModule(this.moveWindow.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShow() {
|
||||
this.eventService.register(UiControlEvent.class, this.getId(), this::onUiControlEvent);
|
||||
this.getModuleService().changeDisplayState(this.moveWindow.getId(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHide() {
|
||||
this.eventService.unregister(UiControlEvent.class, this.getId());
|
||||
this.getModuleService().changeDisplayState(this.moveWindow.getId(), false);
|
||||
this.resetState();
|
||||
}
|
||||
|
||||
public void open(final StorageItemDescriptor descriptor, final List<String> basePath, final Window parentWindow) {
|
||||
this.descriptor = descriptor;
|
||||
this.basePath = basePath != null ? List.copyOf(basePath) : List.of();
|
||||
this.currentPath = this.basePath;
|
||||
this.nameFieldValue = "";
|
||||
this.folderPathByControlId.clear();
|
||||
this.moveWindow.setParent(parentWindow);
|
||||
this.buildDialog();
|
||||
this.getModuleService().changeDisplayState(this.getId(), true);
|
||||
}
|
||||
|
||||
private void onUiControlEvent(final UiControlEvent event) {
|
||||
if (event.getId() == null) {
|
||||
return;
|
||||
}
|
||||
if (event.getId().startsWith(FOLDER_PREFIX)) {
|
||||
final var targetPath = this.folderPathByControlId.get(event.getId());
|
||||
if (targetPath != null) {
|
||||
this.currentPath = targetPath;
|
||||
this.buildDialog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (event.getId()) {
|
||||
case NAME_FIELD_ID -> {
|
||||
if (event.getPayload() instanceof final String text) {
|
||||
final var value = text != null ? text : "";
|
||||
if (!Objects.equals(this.nameFieldValue, value)) {
|
||||
this.nameFieldValue = value;
|
||||
this.buildDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
case BUTTON_MOVE -> this.onMove();
|
||||
case BUTTON_CANCEL -> this.onCancel();
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildDialog() {
|
||||
this.moveWindow.clearRenderingBos();
|
||||
this.clearScrollArea();
|
||||
this.folderPathByControlId.clear();
|
||||
|
||||
final var contentWidth = WINDOW_WIDTH - Window.MARGIN_LEFT - Window.MARGIN_RIGHT;
|
||||
int yOffset = Window.MARGIN_TOP;
|
||||
|
||||
final var titleBo = this.renderingBoPool.acquire("FileSelectionMoveDialog_Title", DrawFontRenderingBo.class)
|
||||
.setText(this.localizationService.translate(LOC_TITLE))
|
||||
.setSize(28)
|
||||
.setAlignment(HorizontalAlignment.CENTER)
|
||||
.withDimensionAbsolute(contentWidth, TITLE_HEIGHT)
|
||||
.withPositionRelative(Window.MARGIN_LEFT, yOffset);
|
||||
this.moveWindow.putRenderingBo(titleBo);
|
||||
yOffset += TITLE_HEIGHT + SPACING;
|
||||
|
||||
final var pathLabel = this.renderingBoPool.acquire("FileSelectionMoveDialog_Label_Path", ControlRenderingBo.class)
|
||||
.setType(LabelControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-188") + ": " + this.formatPath(this.currentPath))
|
||||
.setFontSize(18)
|
||||
.setVisible(true)
|
||||
.setEnabled(false)
|
||||
.withDimensionAbsolute(contentWidth, LABEL_HEIGHT)
|
||||
.withPositionRelative(Window.MARGIN_LEFT, yOffset)
|
||||
.setLayer(RenderingBoLayer.UI0);
|
||||
this.moveWindow.putRenderingBo(pathLabel);
|
||||
yOffset += LABEL_HEIGHT + SPACING / 2;
|
||||
|
||||
final var buttonRowY = WINDOW_HEIGHT - Window.MARGIN_BOTTOM - BUTTON_HEIGHT;
|
||||
final var nameFieldY = buttonRowY - SPACING - FIELD_HEIGHT;
|
||||
final var nameLabelY = nameFieldY - SPACING / 2 - LABEL_HEIGHT;
|
||||
final var scrollHeight = Math.max(120, nameLabelY - SPACING - yOffset);
|
||||
this.folderScrollArea.setRelativeArea(new Area2(ImmutableCoordI2.create(Window.MARGIN_LEFT, yOffset), ImmutableCoordI2.create(contentWidth, scrollHeight)));
|
||||
this.buildFolderList(contentWidth);
|
||||
|
||||
final var nameLabel = this.renderingBoPool.acquire("FileSelectionMoveDialog_Label_Name", ControlRenderingBo.class)
|
||||
.setType(LabelControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-646") + ":")
|
||||
.setFontSize(18)
|
||||
.setVisible(true)
|
||||
.setEnabled(false)
|
||||
.withDimensionAbsolute(contentWidth, LABEL_HEIGHT)
|
||||
.withPositionRelative(Window.MARGIN_LEFT, nameLabelY)
|
||||
.setLayer(RenderingBoLayer.UI0);
|
||||
this.moveWindow.putRenderingBo(nameLabel);
|
||||
|
||||
final var nameField = this.renderingBoPool.acquire(NAME_FIELD_ID, ControlRenderingBo.class)
|
||||
.setType(TextfieldControl.class)
|
||||
.setCaption(this.nameFieldValue != null ? this.nameFieldValue : "")
|
||||
.setControlData(new TextfieldData(false, null))
|
||||
.setVisible(true)
|
||||
.setEnabled(true)
|
||||
.withDimensionAbsolute(contentWidth, FIELD_HEIGHT)
|
||||
.withPositionRelative(Window.MARGIN_LEFT, nameFieldY)
|
||||
.setLayer(RenderingBoLayer.UI1);
|
||||
this.moveWindow.putRenderingBo(nameField);
|
||||
|
||||
final var moveButton = this.renderingBoPool.acquire(BUTTON_MOVE, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-644"))
|
||||
.setVisible(true)
|
||||
.setEnabled(this.canMove())
|
||||
.withDimensionAbsolute(BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
.withPositionRelative(Window.MARGIN_LEFT, buttonRowY)
|
||||
.setLayer(RenderingBoLayer.UI1);
|
||||
this.moveWindow.putRenderingBo(moveButton);
|
||||
|
||||
final var cancelButton = this.renderingBoPool.acquire(BUTTON_CANCEL, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-89"))
|
||||
.setVisible(true)
|
||||
.setEnabled(true)
|
||||
.withDimensionAbsolute(BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
.withPositionRelative(WINDOW_WIDTH - Window.MARGIN_RIGHT - BUTTON_WIDTH, buttonRowY)
|
||||
.setLayer(RenderingBoLayer.UI1);
|
||||
this.moveWindow.putRenderingBo(cancelButton);
|
||||
|
||||
this.folderScrollArea.draw();
|
||||
this.moveWindow.draw();
|
||||
}
|
||||
|
||||
private void buildFolderList(final int contentWidth) {
|
||||
final var descriptors = this.storageService.listPath(this.basePath, true);
|
||||
final var folderNames = new HashSet<String>();
|
||||
for (final var item : descriptors) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
final var path = this.safePath(item.path());
|
||||
if (this.isPathPrefix(this.currentPath, path) && path.size() > this.currentPath.size()) {
|
||||
folderNames.add(path.get(this.currentPath.size()));
|
||||
}
|
||||
}
|
||||
|
||||
final var leftPad = LIST_PADDING;
|
||||
final var availableWidth = contentWidth - ScrollArea.SCROLLBAR_WIDTH_WITH_MARGIN - leftPad * 2;
|
||||
|
||||
int yOffset = 0;
|
||||
if (this.currentPath.size() > this.basePath.size()) {
|
||||
final var upPath = List.copyOf(this.currentPath.subList(0, this.currentPath.size() - 1));
|
||||
final var controlId = FOLDER_PREFIX + "up_" + upPath.hashCode();
|
||||
this.folderPathByControlId.put(controlId, upPath);
|
||||
final var upButton = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption("..")
|
||||
.setTooltip(this.formatPath(upPath))
|
||||
.setVisible(true)
|
||||
.setEnabled(true)
|
||||
.withDimensionAbsolute(availableWidth, FIELD_HEIGHT)
|
||||
.withPositionRelative(leftPad, yOffset)
|
||||
.setLayer(RenderingBoLayer.UI1);
|
||||
this.folderScrollArea.putRenderingBo(upButton);
|
||||
yOffset += FIELD_HEIGHT + ROW_SPACING;
|
||||
}
|
||||
final var sortedFolders = folderNames.stream().sorted(String::compareToIgnoreCase).toList();
|
||||
for (int index = 0; index < sortedFolders.size(); index++) {
|
||||
final var folderName = sortedFolders.get(index);
|
||||
final var targetPath = this.appendPath(this.currentPath, folderName);
|
||||
final var controlId = FOLDER_PREFIX + index + "_" + targetPath.hashCode();
|
||||
this.folderPathByControlId.put(controlId, targetPath);
|
||||
final var folderButton = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class)
|
||||
.setType(ButtonControl.class)
|
||||
.setCaption(folderName + "/")
|
||||
.setTooltip(this.formatPath(targetPath))
|
||||
.setVisible(true)
|
||||
.setEnabled(true)
|
||||
.withDimensionAbsolute(availableWidth, FIELD_HEIGHT)
|
||||
.withPositionRelative(leftPad, yOffset)
|
||||
.setLayer(RenderingBoLayer.UI1);
|
||||
this.folderScrollArea.putRenderingBo(folderButton);
|
||||
yOffset += FIELD_HEIGHT + ROW_SPACING;
|
||||
}
|
||||
if (sortedFolders.isEmpty() && this.currentPath.size() <= this.basePath.size()) {
|
||||
final var emptyLabel = this.renderingBoPool.acquire("FileSelectionMoveDialog_Label_Empty", ControlRenderingBo.class)
|
||||
.setType(LabelControl.class)
|
||||
.setCaption(this.localizationService.translate("ecyoa-193"))
|
||||
.setControlData(new LabelData(null, null, HorizontalAlignment.CENTER))
|
||||
.setFontSize(18)
|
||||
.setVisible(true)
|
||||
.setEnabled(false)
|
||||
.withDimensionAbsolute(availableWidth, FIELD_HEIGHT)
|
||||
.withPositionRelative(leftPad, yOffset + 10)
|
||||
.setLayer(RenderingBoLayer.UI0);
|
||||
this.folderScrollArea.putRenderingBo(emptyLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMove() {
|
||||
if (this.descriptor == null || !this.canMove()) {
|
||||
return;
|
||||
}
|
||||
this.eventService.fire(new FileSelectionMoveDialogResponse(true, this.resolveTargetPath()));
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
if (this.descriptor != null) {
|
||||
this.eventService.fire(new FileSelectionMoveDialogResponse(false, null));
|
||||
this.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
this.getModuleService().changeDisplayState(this.getId(), false);
|
||||
}
|
||||
|
||||
private void resetState() {
|
||||
this.descriptor = null;
|
||||
this.basePath = List.of();
|
||||
this.currentPath = List.of();
|
||||
this.nameFieldValue = "";
|
||||
this.folderPathByControlId.clear();
|
||||
this.clearScrollArea();
|
||||
}
|
||||
|
||||
private void clearScrollArea() {
|
||||
for (final var boId : this.folderScrollArea.getRenderingBoIds().toArray(new String[0])) {
|
||||
this.folderScrollArea.removeRenderingBo(boId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canMove() {
|
||||
if (this.descriptor == null) {
|
||||
return false;
|
||||
}
|
||||
final var targetPath = this.resolveTargetPath();
|
||||
if (targetPath.equals(this.safePath(this.descriptor.path()))) {
|
||||
return false;
|
||||
}
|
||||
return !this.targetContainsSameName(targetPath);
|
||||
}
|
||||
|
||||
private boolean targetContainsSameName(final List<String> targetPath) {
|
||||
final var name = this.descriptor != null ? this.descriptor.name() : null;
|
||||
if (name == null || name.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
final var descriptors = this.storageService.listPath(this.basePath, true);
|
||||
for (final var item : descriptors) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
if (this.safePath(item.path()).equals(targetPath) && name.equalsIgnoreCase(item.name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<String> resolveTargetPath() {
|
||||
final var name = this.nameFieldValue != null ? this.nameFieldValue.trim() : "";
|
||||
if (name.isEmpty()) {
|
||||
return this.currentPath;
|
||||
}
|
||||
return this.appendPath(this.currentPath, name);
|
||||
}
|
||||
|
||||
private String formatPath(final List<String> path) {
|
||||
if (path == null || path.isEmpty()) {
|
||||
return "/";
|
||||
}
|
||||
return "/" + String.join("/", path);
|
||||
}
|
||||
|
||||
private List<String> safePath(final List<String> path) {
|
||||
return path != null ? List.copyOf(path) : List.of();
|
||||
}
|
||||
|
||||
private boolean isPathPrefix(final List<String> prefix, final List<String> candidate) {
|
||||
if (prefix.size() > candidate.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < prefix.size(); i++) {
|
||||
if (!Objects.equals(prefix.get(i), candidate.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> appendPath(final List<String> base, final String segment) {
|
||||
final var result = new ArrayList<String>(base.size() + 1);
|
||||
result.addAll(base);
|
||||
result.add(segment);
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
private ImmutableCoordI2 centeredPosition(final CoordI2 dimension) {
|
||||
final var referenceResolution = RendererControl.REFERENCE_RESOLUTION;
|
||||
final var x = (referenceResolution.getX() - dimension.getX()) / 2;
|
||||
final var y = (referenceResolution.getY() - dimension.getY()) / 2;
|
||||
return ImmutableCoordI2.create(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user