HyCodeYourTale
classpublicPriority 3

ImageImportPage

com.hypixel.hytale.builtin.buildertools.imageimport.ImageImportPage

extends InteractiveCustomUIPage

1

Methods

1

Public Methods

11

Fields

1

Constructors

Constants

BuilderCodec<ImageImportPage.PageData>CODEC= BuilderCodec.builder(ImageImportPage.PageData.class, ImageImportPage.PageData::new) .add...
intDEFAULT_MAX_SIZE= 128
PathIMPORTS_DIR= Paths.get("imports", "images")
StringKEY_BROWSE= "Browse"
StringKEY_BROWSER_CANCEL= "BrowserCancel"
StringKEY_BROWSER_SELECT= "BrowserSelect"
StringKEY_IMAGE_PATH= "@ImagePath"
StringKEY_IMPORT= "Import"
StringKEY_MAX_SIZE= "@MaxSize"
StringKEY_ORIENTATION= "@Orientation"
StringKEY_ORIGIN= "@Origin"
intMAX_SIZE= 512
intMIN_SIZE= 1
StringPASTE_TOOL_ID= "EditorTool_Paste"

Constructors

public
ImageImportPage(PlayerRef playerRef)

Methods

Public Methods (1)

public
void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, ImageImportPage.PageData data)

Fields

Private/Package Fields (11)

privateServerFileBrowser browser
privateString imagePath
privateboolean isError
privateboolean isProcessing
privateint maxDimension
privateImageImportPage.Orientation orientation
privateString orientationStr
privateImageImportPage.Origin origin
privateString originStr
privateboolean showBrowser
privateString statusMessage

Inheritance

Parent
Current
Interface
Child

Use mouse wheel to zoom, drag to pan. Click nodes to navigate.

Related Classes

Source Code

package com.hypixel.hytale.builtin.buildertools.imageimport;

import com.hypixel.hytale.builtin.buildertools.BlockColorIndex;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.common.util.StringUtil;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
import com.hypixel.hytale.protocol.packets.interface_.Page;
import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import com.hypixel.hytale.server.core.ui.DropdownEntryInfo;
import com.hypixel.hytale.server.core.ui.LocalizableString;
import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig;
import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser;
import com.hypixel.hytale.server.core.ui.builder.EventData;
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;

public class ImageImportPage extends InteractiveCustomUIPage<ImageImportPage.PageData> {
   private static final int DEFAULT_MAX_SIZE = 128;
   private static final int MIN_SIZE = 1;
   private static final int MAX_SIZE = 512;
   private static final String PASTE_TOOL_ID = "EditorTool_Paste";
   private static final Path IMPORTS_DIR = Paths.get("imports", "images");
   @Nonnull
   private String imagePath = "";
   private int maxDimension = 128;
   @Nonnull
   private String orientationStr = "wall_xy";
   @Nonnull
   private ImageImportPage.Orientation orientation = ImageImportPage.Orientation.VERTICAL_XY;
   @Nonnull
   private String originStr = "bottom_center";
   @Nonnull
   private ImageImportPage.Origin origin = ImageImportPage.Origin.BOTTOM_CENTER;
   @Nullable
   private String statusMessage = null;
   private boolean isError = false;
   private boolean isProcessing = false;
   private boolean showBrowser = false;
   @Nonnull
   private final ServerFileBrowser browser;

   public ImageImportPage(@Nonnull PlayerRef playerRef) {
      super(playerRef, CustomPageLifetime.CanDismiss, ImageImportPage.PageData.CODEC);
      FileBrowserConfig config = FileBrowserConfig.builder()
         .listElementId("#BrowserPage #FileList")
         .searchInputId("#BrowserPage #SearchInput")
         .currentPathId("#BrowserPage #CurrentPath")
         .roots(List.of(new FileBrowserConfig.RootEntry("Imports", IMPORTS_DIR)))
         .allowedExtensions(".png", ".jpg", ".jpeg", ".gif", ".bmp")
         .enableRootSelector(false)
         .enableSearch(true)
         .enableDirectoryNav(true)
         .maxResults(50)
         .build();

      try {
         Files.createDirectories(IMPORTS_DIR);
      } catch (IOException var4) {
      }

      this.browser = new ServerFileBrowser(config);
   }

   @Override
   public void build(
      @Nonnull Ref<EntityStore> ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store<EntityStore> store
   ) {
      commandBuilder.append("Pages/ImageImportPage.ui");
      commandBuilder.set("#ImagePath #Input.Value", this.imagePath);
      commandBuilder.set("#MaxSizeInput #Input.Value", this.maxDimension);
      List<DropdownEntryInfo> orientationEntries = new ArrayList<>();
      orientationEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.imageImport.orientation.wall_xy"), "wall_xy"));
      orientationEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.imageImport.orientation.wall_xz"), "wall_xz"));
      orientationEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.imageImport.orientation.floor"), "floor"));
      commandBuilder.set("#OrientationInput #Input.Entries", orientationEntries);
      commandBuilder.set("#OrientationInput #Input.Value", this.orientationStr);
      List<DropdownEntryInfo> originEntries = new ArrayList<>();
      originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_front_left"), "bottom_front_left"));
      originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_center"), "bottom_center"));
      originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.center"), "center"));
      originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.top_center"), "top_center"));
      commandBuilder.set("#OriginInput #Input.Entries", originEntries);
      commandBuilder.set("#OriginInput #Input.Value", this.originStr);
      this.updateStatus(commandBuilder);
      eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ImagePath #Input", EventData.of("@ImagePath", "#ImagePath #Input.Value"), false);
      eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#MaxSizeInput #Input", EventData.of("@MaxSize", "#MaxSizeInput #Input.Value"), false);
      eventBuilder.addEventBinding(
         CustomUIEventBindingType.ValueChanged, "#OrientationInput #Input", EventData.of("@Orientation", "#OrientationInput #Input.Value"), false
      );
      eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#OriginInput #Input", EventData.of("@Origin", "#OriginInput #Input.Value"), false);
      eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImportButton", EventData.of("Import", "true"));
      eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImagePath #BrowseButton", EventData.of("Browse", "true"));
      commandBuilder.set("#FormContainer.Visible", !this.showBrowser);
      commandBuilder.set("#BrowserPage.Visible", this.showBrowser);
      if (this.showBrowser) {
         this.buildBrowserPage(commandBuilder, eventBuilder);
      }
   }

   private void buildBrowserPage(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
      this.browser.buildSearchInput(commandBuilder, eventBuilder);
      this.browser.buildCurrentPath(commandBuilder);
      this.browser.buildFileList(commandBuilder, eventBuilder);
      eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #SelectButton", EventData.of("BrowserSelect", "true"));
      eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #CancelButton", EventData.of("BrowserCancel", "true"));
   }

   private void updateStatus(@Nonnull UICommandBuilder commandBuilder) {
      if (this.statusMessage != null) {
         commandBuilder.set("#StatusText.Text", this.statusMessage);
         commandBuilder.set("#StatusText.Visible", true);
         commandBuilder.set("#StatusText.Style.TextColor", this.isError ? "#e74c3c" : "#cfd8e3");
      } else {
         commandBuilder.set("#StatusText.Visible", false);
      }
   }

   private void setError(@Nonnull String message) {
      this.statusMessage = message;
      this.isError = true;
      this.isProcessing = false;
      this.rebuild();
   }

   private void setStatus(@Nonnull String message) {
      this.statusMessage = message;
      this.isError = false;
      this.rebuild();
   }

   public void handleDataEvent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull ImageImportPage.PageData data) {
      boolean needsUpdate = false;
      if (data.browse != null && data.browse) {
         this.showBrowser = true;
         this.rebuild();
      } else if (data.browserCancel != null && data.browserCancel) {
         this.showBrowser = false;
         this.rebuild();
      } else if (data.browserSelect != null && data.browserSelect) {
         if (!this.browser.getSelectedItems().isEmpty()) {
            String selectedPath = this.browser.getSelectedItems().iterator().next();
            this.imagePath = this.browser.getRoot().resolve(selectedPath).toString();
         }

         this.showBrowser = false;
         this.rebuild();
      } else {
         if (this.showBrowser && (data.file != null || data.searchQuery != null || data.searchResult != null)) {
            boolean handled = false;
            if (data.searchQuery != null) {
               this.browser.setSearchQuery(data.searchQuery.trim().toLowerCase());
               handled = true;
            }

            if (data.file != null) {
               String fileName = data.file;
               if ("..".equals(fileName)) {
                  this.browser.navigateUp();
                  handled = true;
               } else {
                  Path targetPath = this.browser.resolveFromCurrent(fileName);
                  if (targetPath != null && Files.isDirectory(targetPath)) {
                     this.browser.navigateTo(Paths.get(fileName));
                     handled = true;
                  } else if (targetPath != null && Files.isRegularFile(targetPath)) {
                     this.imagePath = targetPath.toString();
                     this.showBrowser = false;
                     this.rebuild();
                     return;
                  }
               }
            }

            if (data.searchResult != null) {
               Path resolvedPath = this.browser.resolveSecure(data.searchResult);
               if (resolvedPath != null && Files.isRegularFile(resolvedPath)) {
                  this.imagePath = resolvedPath.toString();
                  this.showBrowser = false;
                  this.rebuild();
                  return;
               }
            }

            if (handled) {
               UICommandBuilder commandBuilder = new UICommandBuilder();
               UIEventBuilder eventBuilder = new UIEventBuilder();
               this.browser.buildFileList(commandBuilder, eventBuilder);
               this.browser.buildCurrentPath(commandBuilder);
               this.sendUpdate(commandBuilder, eventBuilder, false);
               return;
            }
         }

         if (data.imagePath != null) {
            this.imagePath = StringUtil.stripQuotes(data.imagePath.trim());
            this.statusMessage = null;
            needsUpdate = true;
         }

         if (data.maxSize != null) {
            this.maxDimension = Math.max(1, Math.min(512, data.maxSize));
            needsUpdate = true;
         }

         if (data.orientation != null) {
            this.orientationStr = data.orientation.trim().toLowerCase();
            String var8 = this.orientationStr;

            this.orientation = switch (var8) {
               case "wall_xz", "xz", "vertical_xz" -> ImageImportPage.Orientation.VERTICAL_XZ;
               case "floor", "horizontal", "horizontal_xz" -> ImageImportPage.Orientation.HORIZONTAL_XZ;
               default -> ImageImportPage.Orientation.VERTICAL_XY;
            };
            needsUpdate = true;
         }

         if (data.origin != null) {
            this.originStr = data.origin.trim().toLowerCase();
            String var9 = this.originStr;

            this.origin = switch (var9) {
               case "bottom_front_left" -> ImageImportPage.Origin.BOTTOM_FRONT_LEFT;
               case "center" -> ImageImportPage.Origin.CENTER;
               case "top_center" -> ImageImportPage.Origin.TOP_CENTER;
               default -> ImageImportPage.Origin.BOTTOM_CENTER;
            };
            needsUpdate = true;
         }

         if (data.doImport != null && data.doImport && !this.isProcessing) {
            this.performImport(ref, store);
         } else {
            if (needsUpdate) {
               this.sendUpdate();
            }
         }
      }
   }

   private void performImport(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
      if (this.imagePath.isEmpty()) {
         this.setError("Please enter a path to an image file");
      } else {
         Path path = Paths.get(this.imagePath);
         if (!SingleplayerModule.isOwner(this.playerRef)) {
            Path normalizedPath = path.toAbsolutePath().normalize();
            Path normalizedImports = IMPORTS_DIR.toAbsolutePath().normalize();
            if (!normalizedPath.startsWith(normalizedImports)) {
               this.setError("Files must be in the server's imports/images directory");
               return;
            }
         }

         if (!Files.exists(path)) {
            this.setError("File not found: " + this.imagePath);
         } else {
            this.isProcessing = true;
            this.setStatus("Processing...");
            Player playerComponent = store.getComponent(ref, Player.getComponentType());
            PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType());
            if (playerComponent != null && playerRefComponent != null) {
               String finalPath = this.imagePath;
               int finalMaxSize = this.maxDimension;
               ImageImportPage.Orientation finalOrientation = this.orientation;
               ImageImportPage.Origin finalOrigin = this.origin;
               BuilderToolsPlugin.addToQueue(
                  playerComponent,
                  playerRefComponent,
                  (r, builderState, componentAccessor) -> {
                     try {
                        BufferedImage image = null;

                        try {
                           image = ImageIO.read(Paths.get(finalPath).toFile());
                        } catch (Exception var38) {
                        }

                        if (image == null) {
                           this.setError("Unable to read image file (unsupported format or corrupted). Try PNG format.");
                           return;
                        }

                        int width = image.getWidth();
                        int height = image.getHeight();
                        float scale = 1.0F;
                        if (width > finalMaxSize || height > finalMaxSize) {
                           scale = (float)finalMaxSize / (float)Math.max(width, height);
                           width = Math.round((float)width * scale);
                           height = Math.round((float)height * scale);
                        }

                        BlockColorIndex colorIndex = BuilderToolsPlugin.get().getBlockColorIndex();
                        if (colorIndex.isEmpty()) {
                           this.setError("Block color index not initialized");
                           return;
                        }

                        int sizeX;
                        int sizeY;
                        int sizeZ;
                        switch (finalOrientation) {
                           case VERTICAL_XY:
                              sizeX = width;
                              sizeY = height;
                              sizeZ = 1;
                              break;
                           case VERTICAL_XZ:
                              sizeX = width;
                              sizeY = 1;
                              sizeZ = height;
                              break;
                           case HORIZONTAL_XZ:
                              sizeX = width;
                              sizeY = 1;
                              sizeZ = height;
                              break;
                           default:
                              sizeX = width;
                              sizeY = height;
                              sizeZ = 1;
                        }

                        int offsetX = 0;
                        int offsetY = 0;
                        int offsetZ = 0;
                        switch (finalOrigin) {
                           case BOTTOM_FRONT_LEFT:
                           default:
                              break;
                           case BOTTOM_CENTER:
                              offsetX = -sizeX / 2;
                              offsetZ = -sizeZ / 2;
                              break;
                           case CENTER:
                              offsetX = -sizeX / 2;
                              offsetY = -sizeY / 2;
                              offsetZ = -sizeZ / 2;
                              break;
                           case TOP_CENTER:
                              offsetX = -sizeX / 2;
                              offsetY = -sizeY;
                              offsetZ = -sizeZ / 2;
                        }

                        BlockSelection selection = new BlockSelection(width * height, 0);
                        selection.setPosition(0, 0, 0);
                        int blockCount = 0;
                        float finalScale = scale;

                        for (int imgY = 0; imgY < height; imgY++) {
                           for (int imgX = 0; imgX < width; imgX++) {
                              int srcX = Math.min((int)((float)imgX / finalScale), image.getWidth() - 1);
                              int srcY = Math.min((int)((float)imgY / finalScale), image.getHeight() - 1);
                              int rgba = image.getRGB(srcX, srcY);
                              int alpha = rgba >> 24 & 0xFF;
                              if (alpha >= 128) {
                                 int red = rgba >> 16 & 0xFF;
                                 int green = rgba >> 8 & 0xFF;
                                 int blue = rgba & 0xFF;
                                 int blockId = colorIndex.findClosestBlock(red, green, blue);
                                 if (blockId > 0) {
                                    int blockX;
                                    int blockY;
                                    int blockZ;
                                    switch (finalOrientation) {
                                       case VERTICAL_XY:
                                          blockX = imgX;
                                          blockY = height - 1 - imgY;
                                          blockZ = 0;
                                          break;
                                       case VERTICAL_XZ:
                                          blockX = imgX;
                                          blockY = 0;
                                          blockZ = height - 1 - imgY;
                                          break;
                                       case HORIZONTAL_XZ:
                                          blockX = imgX;
                                          blockY = 0;
                                          blockZ = imgY;
                                          break;
                                       default:
                                          blockX = imgX;
                                          blockY = height - 1 - imgY;
                                          blockZ = 0;
                                    }

                                    selection.addBlockAtLocalPos(blockX + offsetX, blockY + offsetY, blockZ + offsetZ, blockId, 0, 0, 0);
                                    blockCount++;
                                 }
                              }
                           }
                        }

                        selection.setSelectionArea(
                           new Vector3i(offsetX, offsetY, offsetZ), new Vector3i(sizeX - 1 + offsetX, sizeY - 1 + offsetY, sizeZ - 1 + offsetZ)
                        );
                        builderState.setSelection(selection);
                        builderState.sendSelectionToClient();
                        this.statusMessage = String.format("Success! %d blocks copied to clipboard (%dx%dx%d)", blockCount, sizeX, sizeY, sizeZ);
                        this.isProcessing = false;
                        playerRefComponent.sendMessage(
                           Message.translation("server.builderTools.imageImport.success")
                              .param("count", blockCount)
                              .param("width", sizeX)
                              .param("height", sizeY)
                              .param("depth", sizeZ)
                        );
                        playerComponent.getPageManager().setPage(r, store, Page.None);
                        this.switchToPasteTool(playerComponent, playerRefComponent);
                     } catch (Exception var39) {
                        ((HytaleLogger.Api)BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var39)).log("Image import error");
                        this.setError("Error: " + var39.getMessage());
                     }
                  }
               );
            } else {
               this.setError("Player not found");
            }
         }
      }
   }

   private void switchToPasteTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) {
      Inventory inventory = playerComponent.getInventory();
      ItemContainer hotbar = inventory.getHotbar();
      ItemContainer storage = inventory.getStorage();
      ItemContainer tools = inventory.getTools();
      int hotbarSize = hotbar.getCapacity();

      for (short slot = 0; slot < hotbarSize; slot++) {
         ItemStack itemStack = hotbar.getItemStack(slot);
         if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
            inventory.setActiveHotbarSlot((byte)slot);
            playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot));
            return;
         }
      }

      short emptySlot = -1;

      for (short slotx = 0; slotx < hotbarSize; slotx++) {
         ItemStack itemStack = hotbar.getItemStack(slotx);
         if (itemStack == null || itemStack.isEmpty()) {
            emptySlot = slotx;
            break;
         }
      }

      if (emptySlot != -1) {
         for (short slotxx = 0; slotxx < storage.getCapacity(); slotxx++) {
            ItemStack itemStack = storage.getItemStack(slotxx);
            if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
               storage.moveItemStackFromSlotToSlot(slotxx, 1, hotbar, emptySlot);
               inventory.setActiveHotbarSlot((byte)emptySlot);
               playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot));
               return;
            }
         }

         ItemStack pasteToolStack = null;

         for (short slotxxx = 0; slotxxx < tools.getCapacity(); slotxxx++) {
            ItemStack itemStack = tools.getItemStack(slotxxx);
            if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
               pasteToolStack = itemStack;
               break;
            }
         }

         if (pasteToolStack != null) {
            hotbar.setItemStackForSlot(emptySlot, new ItemStack(pasteToolStack.getItemId()));
            inventory.setActiveHotbarSlot((byte)emptySlot);
            playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot));
         }
      }
   }

   public static enum Orientation {
      VERTICAL_XY,
      VERTICAL_XZ,
      HORIZONTAL_XZ;

      private Orientation() {
      }
   }

   public static enum Origin {
      BOTTOM_FRONT_LEFT,
      BOTTOM_CENTER,
      CENTER,
      TOP_CENTER;

      private Origin() {
      }
   }

   public static class PageData {
      static final String KEY_IMAGE_PATH = "@ImagePath";
      static final String KEY_MAX_SIZE = "@MaxSize";
      static final String KEY_ORIENTATION = "@Orientation";
      static final String KEY_ORIGIN = "@Origin";
      static final String KEY_IMPORT = "Import";
      static final String KEY_BROWSE = "Browse";
      static final String KEY_BROWSER_SELECT = "BrowserSelect";
      static final String KEY_BROWSER_CANCEL = "BrowserCancel";
      public static final BuilderCodec<ImageImportPage.PageData> CODEC = BuilderCodec.builder(ImageImportPage.PageData.class, ImageImportPage.PageData::new)
         .addField(new KeyedCodec<>("@ImagePath", Codec.STRING), (entry, s) -> entry.imagePath = s, entry -> entry.imagePath)
         .addField(new KeyedCodec<>("@MaxSize", Codec.INTEGER), (entry, i) -> entry.maxSize = i, entry -> entry.maxSize)
         .addField(new KeyedCodec<>("@Orientation", Codec.STRING), (entry, s) -> entry.orientation = s, entry -> entry.orientation)
         .addField(new KeyedCodec<>("@Origin", Codec.STRING), (entry, s) -> entry.origin = s, entry -> entry.origin)
         .addField(
            new KeyedCodec<>("Import", Codec.STRING),
            (entry, s) -> entry.doImport = "true".equalsIgnoreCase(s),
            entry -> entry.doImport != null && entry.doImport ? "true" : null
         )
         .addField(
            new KeyedCodec<>("Browse", Codec.STRING),
            (entry, s) -> entry.browse = "true".equalsIgnoreCase(s),
            entry -> entry.browse != null && entry.browse ? "true" : null
         )
         .addField(
            new KeyedCodec<>("BrowserSelect", Codec.STRING),
            (entry, s) -> entry.browserSelect = "true".equalsIgnoreCase(s),
            entry -> entry.browserSelect != null && entry.browserSelect ? "true" : null
         )
         .addField(
            new KeyedCodec<>("BrowserCancel", Codec.STRING),
            (entry, s) -> entry.browserCancel = "true".equalsIgnoreCase(s),
            entry -> entry.browserCancel != null && entry.browserCancel ? "true" : null
         )
         .addField(new KeyedCodec<>("File", Codec.STRING), (entry, s) -> entry.file = s, entry -> entry.file)
         .addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery)
         .addField(new KeyedCodec<>("SearchResult", Codec.STRING), (entry, s) -> entry.searchResult = s, entry -> entry.searchResult)
         .build();
      @Nullable
      private String imagePath;
      @Nullable
      private Integer maxSize;
      @Nullable
      private String orientation;
      @Nullable
      private String origin;
      @Nullable
      private Boolean doImport;
      @Nullable
      private Boolean browse;
      @Nullable
      private Boolean browserSelect;
      @Nullable
      private Boolean browserCancel;
      @Nullable
      private String file;
      @Nullable
      private String searchQuery;
      @Nullable
      private String searchResult;

      public PageData() {
      }
   }
}