diff --git a/.gitignore b/.gitignore
index eb32fe5f..4545d0dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,7 @@ forge/src/main/java/**/mod
 forge/src/main/java/net/londonunderground/mod
 forge/src/main/resources/assets
 forge/src/main/resources/data
+forge/run
 #forge/src/main/resources/META-INF
 
 # java
diff --git a/fabric/src/main/java/com/lx862/jcm/mixin/compat/SimpleDefaultedRegistryMixin.java b/fabric/src/main/java/com/lx862/jcm/mixin/compat/SimpleDefaultedRegistryMixin.java
index 0d0c4569..24866135 100644
--- a/fabric/src/main/java/com/lx862/jcm/mixin/compat/SimpleDefaultedRegistryMixin.java
+++ b/fabric/src/main/java/com/lx862/jcm/mixin/compat/SimpleDefaultedRegistryMixin.java
@@ -19,6 +19,7 @@
  * See <a href="https://github.com/Noaaan/MythicMetals/blob/1.20/src/main/java/nourl/mythicmetals/mixin/DefaultedRegistryMixin.java">here</a>
  * And <a href="https://github.com/orgs/FabricMC/discussions/2361">https://github.com/orgs/FabricMC/discussions/2361</a>
  */
+
 #if MC_VERSION < "11904"
     @Mixin(DefaultedRegistry.class)
 #else
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/PIDSManager.java b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/PIDSManager.java
index 3a990e6c..e831c69c 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/PIDSManager.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/PIDSManager.java
@@ -59,4 +59,4 @@ public static List<PIDSPresetBase> getCustomPresets() {
     public static void reset() {
         presetList.entrySet().stream().filter(e -> !e.getValue().builtin).collect(Collectors.toList()).clear();
     }
-}
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/CustomComponentPIDSPreset.java b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/CustomComponentPIDSPreset.java
index 68cfda4a..4084e198 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/CustomComponentPIDSPreset.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/CustomComponentPIDSPreset.java
@@ -10,6 +10,7 @@
 import com.lx862.jcm.mod.data.pids.preset.components.base.TextureComponent;
 import com.lx862.jcm.mod.render.text.TextRenderingManager;
 import com.lx862.jcm.mod.util.JCMLogger;
+import org.apache.commons.lang3.NotImplementedException;
 import org.mtr.core.operation.ArrivalResponse;
 import org.mtr.libraries.it.unimi.dsi.fastutil.objects.ObjectArrayList;
 import org.mtr.mapping.holder.BlockPos;
@@ -40,7 +41,10 @@ public static CustomComponentPIDSPreset parse(JsonObject rootJsonObject) {
         }
         boolean builtin = rootJsonObject.has("builtin") && rootJsonObject.get("builtin").getAsBoolean();
 
-        String componentFile = ResourceManagerHelper.readResource(new Identifier(rootJsonObject.get("file").getAsString()));
+        Identifier componentFileLocation = Identifier.tryParse(rootJsonObject.get("file").getAsString());
+        if(componentFileLocation == null) return null;
+
+        String componentFile = ResourceManagerHelper.readResource(componentFileLocation);
         JsonArray jsonArray = new JsonParser().parse(componentFile).getAsJsonArray();
 
         CustomComponentPIDSPreset preset = new CustomComponentPIDSPreset(id, name, builtin);
@@ -96,4 +100,11 @@ public int getTextColor() {
     public boolean isRowHidden(int row) {
         return false;
     }
+
+    @Override
+    public JsonObject toJson() {
+        JsonObject jsonObject = super.toJson();
+        jsonObject.addProperty("TODO", "TODO");
+        return jsonObject;
+    }
 }
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/JsonPIDSPreset.java b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/JsonPIDSPreset.java
index d7de8007..20118f8c 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/JsonPIDSPreset.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/JsonPIDSPreset.java
@@ -201,4 +201,4 @@ public int getTextColor() {
     public boolean isRowHidden(int row) {
         return rowHidden.length - 1 < row ? false : rowHidden[row];
     }
-}
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/MutableJsonPIDSPreset.java b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/MutableJsonPIDSPreset.java
new file mode 100644
index 00000000..74eea0e1
--- /dev/null
+++ b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/MutableJsonPIDSPreset.java
@@ -0,0 +1,45 @@
+package com.lx862.jcm.mod.data.pids.preset;
+
+import com.lx862.jcm.mod.render.TextOverflowMode;
+import org.mtr.mapping.holder.Identifier;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class MutableJsonPIDSPreset extends JsonPIDSPreset {
+    public MutableJsonPIDSPreset(String id, @Nullable String name, @Nonnull Identifier background, @Nullable String fontId, TextOverflowMode textOverflowMode, boolean[] rowHidden, boolean showClock, boolean showWeather, boolean topPadding, int textColor) {
+        super(id, name, background, fontId, textOverflowMode, rowHidden, showClock, showWeather, topPadding, textColor);
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setName(String name) {
+        this.id = name;
+    }
+
+    public void setShowWeather(boolean newShowWeather) {
+        showWeather = newShowWeather;
+    }
+
+    public void setShowClock(boolean newShowClock) {
+        showClock = newShowClock;
+    }
+
+    public void setBackground(Identifier newBackground) {
+        background = newBackground;
+    }
+
+    public void setTopPadding(boolean newTopPadding) {
+        topPadding = newTopPadding;
+    }
+
+    public void setColor(int newColor) {
+        textColor = newColor;
+    }
+
+    public void setRowHidden(int i, boolean bool) {
+        rowHidden[i] = bool;
+    }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/PIDSPresetBase.java b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/PIDSPresetBase.java
index 854954f3..16da56b4 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/PIDSPresetBase.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/data/pids/preset/PIDSPresetBase.java
@@ -1,5 +1,6 @@
 package com.lx862.jcm.mod.data.pids.preset;
 
+import com.google.gson.JsonObject;
 import com.lx862.jcm.mod.block.entity.PIDSBlockEntity;
 import com.lx862.jcm.mod.data.pids.preset.components.base.PIDSComponent;
 import com.lx862.jcm.mod.render.RenderHelper;
@@ -18,8 +19,8 @@
 import java.util.List;
 
 public abstract class PIDSPresetBase implements RenderHelper {
-    private final String id;
-    private final String name;
+    protected String id;
+    protected String name;
     public final boolean builtin;
     public PIDSPresetBase(String id, @Nullable String name, boolean builtin) {
         this.id = id;
@@ -47,6 +48,13 @@ public void drawAtlasBackground(GraphicsHolder graphicsHolder, int width, int he
         RenderHelper.drawTexture(graphicsHolder,0, height, 0, width, width, facing, ARGB_WHITE, MAX_RENDER_LIGHT);
     }
 
+    public JsonObject toJson() {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("id", id);
+        jsonObject.addProperty("name", name == null ? id : name);
+        return jsonObject;
+    }
+
     public abstract List<PIDSComponent> getComponents(ObjectArrayList<ArrivalResponse> arrivals, String[] customMessages, boolean[] rowHidden, int x, int y, int screenWidth, int screenHeight, int rows, boolean hidePlatform);
     public abstract String getFont();
     public abstract @Nonnull Identifier getBackground();
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/registry/Blocks.java b/fabric/src/main/java/com/lx862/jcm/mod/registry/Blocks.java
index c70f9a4b..f4170a71 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/registry/Blocks.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/registry/Blocks.java
@@ -5,6 +5,7 @@
 import com.lx862.jcm.mod.util.BlockUtil;
 import com.lx862.jcm.mod.util.JCMLogger;
 import org.mtr.mapping.holder.Block;
+import org.mtr.mapping.holder.Property;
 import org.mtr.mapping.holder.RenderLayer;
 import org.mtr.mapping.mapper.BlockHelper;
 import org.mtr.mapping.registry.BlockRegistryObject;
@@ -50,7 +51,6 @@ public final class Blocks {
             if (isLit) return 15;
             else return 0;
         }).nonOpaque())), ItemGroups.JCM_MAIN);
-
     public static final BlockRegistryObject SUBSIDY_MACHINE = RegistryHelper.registerBlockItem("subsidy_machine", () -> new Block(new SubsidyMachineBlock(BlockHelper.createBlockSettings(false).nonOpaque())), ItemGroups.JCM_MAIN);
     public static final BlockRegistryObject SOUND_LOOPER = RegistryHelper.registerBlockItem("sound_looper", () -> new Block(new SoundLooperBlock(BlockHelper.createBlockSettings(false))), ItemGroups.JCM_MAIN);
     public static final BlockRegistryObject STATION_NAME_STANDING = RegistryHelper.registerBlockItem("station_name_standing", () -> new Block(new StationNameStandingBlock(BlockHelper.createBlockSettings(false).strength(4.0f).nonOpaque())), ItemGroups.JCM_MAIN);
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/EditorSaveScreen.java b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/EditorSaveScreen.java
new file mode 100644
index 00000000..a52924c0
--- /dev/null
+++ b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/EditorSaveScreen.java
@@ -0,0 +1,168 @@
+package com.lx862.jcm.mod.render.gui.screen;
+
+import com.google.gson.*;
+import com.lx862.jcm.mod.Constants;
+import com.lx862.jcm.mod.data.pids.PIDSManager;
+import com.lx862.jcm.mod.data.pids.preset.PIDSPresetBase;
+import com.lx862.jcm.mod.render.GuiHelper;
+import com.lx862.jcm.mod.render.RenderHelper;
+import com.lx862.jcm.mod.render.gui.screen.base.TitledScreen;
+import com.lx862.jcm.mod.render.gui.widget.ContentItem;
+import com.lx862.jcm.mod.render.gui.widget.HorizontalWidgetSet;
+import com.lx862.jcm.mod.render.gui.widget.ListViewWidget;
+import com.lx862.jcm.mod.render.gui.widget.MappedWidget;
+import com.lx862.jcm.mod.resources.JCMResourceManager;
+import com.lx862.jcm.mod.util.JCMLogger;
+import com.lx862.jcm.mod.util.TextCategory;
+import com.lx862.jcm.mod.util.TextUtil;
+import org.mtr.mapping.holder.*;
+import org.mtr.mapping.mapper.ButtonWidgetExtension;
+import org.mtr.mapping.mapper.GuiDrawing;
+
+import java.io.File;
+import java.io.FileReader;
+
+public class EditorSaveScreen extends TitledScreen implements RenderHelper, GuiHelper {
+    private static final Identifier PIDS_PREVIEW_BASE = new Identifier("jsblock:textures/gui/pids_preview.png");
+    private final File resourcePackFolder;
+    private final ListViewWidget listViewWidget;
+    private final PIDSPresetBase oldPreset;
+    private final PIDSPresetBase newPreset;
+    private final String associatedRP;
+    private final Screen previousScreen;
+    public EditorSaveScreen(PIDSPresetBase oldPreset, PIDSPresetBase newPreset, Screen previousScreen) {
+        super(false);
+        this.resourcePackFolder = new File(org.mtr.mapping.holder.MinecraftClient.getInstance().getRunDirectoryMapped(), "resourcepacks");
+        this.listViewWidget = new ListViewWidget();
+        this.newPreset = newPreset;
+        this.oldPreset = oldPreset;
+        this.associatedRP = getAssociatedResourcePack(this.oldPreset.getId());
+        this.previousScreen = previousScreen;
+    }
+
+    @Override
+    protected void init2() {
+        super.init2();
+        int contentWidth = (int)Math.min((width * 0.75), MAX_CONTENT_WIDTH);
+        int listViewHeight = (int)((height - 60) * 0.76);
+        int startX = (width - contentWidth) / 2;
+        int startY = TEXT_PADDING * 6;
+
+        listViewWidget.reset();
+        addConfigEntries();
+        listViewWidget.setXYSize(startX, startY, contentWidth, listViewHeight);
+        addChild(new ClickableWidget(listViewWidget));
+//
+//        if(associatedRP == null) {
+//            openSaveAsScreen();
+//        }
+    }
+
+    @Override
+    public MutableText getScreenTitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.title");
+    }
+
+    @Override
+    public MutableText getScreenSubtitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_save.subtitle", newPreset.getId());
+    }
+
+    public void addConfigEntries() {
+        listViewWidget.addCategory(TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.category.save_edits"));
+        addResourcePackListing(PIDSManager.getPreset(newPreset.getId()));
+    }
+
+    private void addResourcePackListing(PIDSPresetBase preset) {
+        if(associatedRP == null) return;
+
+        ButtonWidgetExtension saveBtn = new ButtonWidgetExtension(0, 0, 60, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.save"), (btn) -> {
+            onClose2();
+        });
+
+        ButtonWidgetExtension saveAsBtn = new ButtonWidgetExtension(0, 0, 60, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.saveas"), (btn) -> openSaveAsScreen());
+
+        HorizontalWidgetSet widgetSet = new HorizontalWidgetSet();
+        widgetSet.addWidget(new MappedWidget(saveBtn));
+        widgetSet.setXYSize(0, 0, 100, 20);
+
+        HorizontalWidgetSet widgetSet2 = new HorizontalWidgetSet();
+        widgetSet2.addWidget(new MappedWidget(saveAsBtn));
+        widgetSet2.setXYSize(0, 0, 100, 20);
+
+        addChild(new ClickableWidget(saveBtn));
+        addChild(new ClickableWidget(saveAsBtn));
+        addChild(new ClickableWidget(widgetSet));
+        addChild(new ClickableWidget(widgetSet2));
+        ContentItem contentItem = new ContentItem(TextUtil.literal(associatedRP), new MappedWidget(widgetSet), 26);
+        ContentItem contentItem2 = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.title.saveas"), new MappedWidget(widgetSet2), 26);
+
+        contentItem.setIconCallback((guiDrawing, startX, startY, width, height) -> {
+            drawPIDSPreview(preset, guiDrawing, startX, startY, width, height, false);
+        });
+        listViewWidget.add(contentItem);
+        listViewWidget.add(contentItem2);
+    }
+
+    private void openSaveAsScreen() {
+        MinecraftClient.getInstance().openScreen(
+                new Screen(new EditorSaveScreenExtended(oldPreset, newPreset, previousScreen).withPreviousScreen(new Screen(this)))
+        );
+    }
+
+    public static void drawPIDSPreview(PIDSPresetBase preset, GuiDrawing guiDrawing, int startX, int startY, int width, int height, boolean backgroundOnly) {
+        final int offset = 6;
+
+        GuiHelper.drawTexture(guiDrawing, PIDS_PREVIEW_BASE, startX, startY, width, height);
+        if(preset == null) return;
+        
+        GuiHelper.drawTexture(guiDrawing, preset.getBackground(), startX+0.5, startY+offset+0.5, width-1, height-offset-4);
+
+        if(!backgroundOnly) {
+            double perRow = height / 8.5;
+            double rowHeight = Math.max(0.5, height / 24.0);
+            for(int i = 0; i < 4; i++) {
+                if(preset.isRowHidden(i)) continue;
+                double curY = startY + offset + ((i+1) * perRow);
+                GuiHelper.drawRectangle(guiDrawing, startX+1.5, curY, width * 0.55, rowHeight, preset.getTextColor());
+                GuiHelper.drawRectangle(guiDrawing, startX + (width * 0.65), curY, rowHeight, rowHeight, preset.getTextColor());
+                GuiHelper.drawRectangle(guiDrawing, startX + (width * 0.75), curY, (width * 0.2)-0.5, rowHeight, preset.getTextColor());
+            }
+        }
+    }
+
+    private String getAssociatedResourcePack(String targetPresetId) {
+        for(File file : resourcePackFolder.listFiles()) {
+            if(file.isDirectory()) {
+                File jsonFile = resourcePackFolder.toPath().resolve(file.getName()).resolve("assets").resolve(Constants.MOD_ID).resolve("joban_custom_resources.json").toFile();
+                if(jsonFile.exists()) {
+                    JsonArray presetsInRP;
+                    try {
+                        presetsInRP = new JsonParser().parse(new FileReader(jsonFile)).getAsJsonObject().get("pids_images").getAsJsonArray();
+                    } catch (Exception e) {
+                        JCMLogger.debug("Cannot read PIDS Preset from Resource Pack!");
+                        continue;
+                    }
+
+                    for(int i = 0; i < presetsInRP.size(); i++) {
+                        PIDSPresetBase pidsPreset = PIDSManager.parsePreset(presetsInRP.get(i).getAsJsonObject());
+                        if(pidsPreset != null && pidsPreset.getId().equals(oldPreset.getId())) {
+                            return file.getName();
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void onClose2() {
+        if(associatedRP != null) {
+            JCMResourceManager.updatePresetInResourcePack(oldPreset, newPreset, associatedRP);
+            JCMResourceManager.reloadResources();
+        }
+
+        super.onClose2();
+    }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/EditorSaveScreenExtended.java b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/EditorSaveScreenExtended.java
new file mode 100644
index 00000000..b114ef8e
--- /dev/null
+++ b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/EditorSaveScreenExtended.java
@@ -0,0 +1,158 @@
+package com.lx862.jcm.mod.render.gui.screen;
+
+import com.lx862.jcm.mod.Constants;
+import com.lx862.jcm.mod.data.pids.PIDSManager;
+import com.lx862.jcm.mod.data.pids.preset.PIDSPresetBase;
+import com.lx862.jcm.mod.render.GuiHelper;
+import com.lx862.jcm.mod.render.RenderHelper;
+import com.lx862.jcm.mod.render.gui.screen.base.TitledScreen;
+import com.lx862.jcm.mod.render.gui.widget.ContentItem;
+import com.lx862.jcm.mod.render.gui.widget.HorizontalWidgetSet;
+import com.lx862.jcm.mod.render.gui.widget.ListViewWidget;
+import com.lx862.jcm.mod.render.gui.widget.MappedWidget;
+import com.lx862.jcm.mod.resources.JCMResourceManager;
+import com.lx862.jcm.mod.util.TextCategory;
+import com.lx862.jcm.mod.util.TextUtil;
+import org.mtr.mapping.holder.*;
+import org.mtr.mapping.mapper.ButtonWidgetExtension;
+import org.mtr.mapping.mapper.GuiDrawing;
+import org.mtr.mapping.mapper.TextFieldWidgetExtension;
+import org.mtr.mapping.tool.TextCase;
+
+import java.io.File;
+
+public class EditorSaveScreenExtended extends TitledScreen implements RenderHelper, GuiHelper {
+    private static final Identifier PIDS_PREVIEW_BASE = new Identifier("jsblock:textures/gui/pids_preview.png");
+    private final TextFieldWidgetExtension searchBox;
+    private final File resourcePackFolder;
+    private final ListViewWidget listViewWidget;
+    private final PIDSPresetBase newPreset;
+    private final PIDSPresetBase oldPreset;
+    private final Screen previousScreen;
+    public EditorSaveScreenExtended(PIDSPresetBase oldPreset, PIDSPresetBase newPreset, Screen previousScreen) {
+        super(false);
+        this.resourcePackFolder = new File(org.mtr.mapping.holder.MinecraftClient.getInstance().getRunDirectoryMapped(), "resourcepacks");
+        this.listViewWidget = new ListViewWidget();
+        this.searchBox = new TextFieldWidgetExtension(0, 0, 0, 22, 60, TextCase.DEFAULT, null, TextUtil.translatable(TextCategory.GUI, "widget.search").getString());
+        this.newPreset = newPreset;
+        this.oldPreset = oldPreset;
+        this.previousScreen = previousScreen;
+    }
+
+    @Override
+    protected void init2() {
+        super.init2();
+        int contentWidth = (int)Math.min((width * 0.75), MAX_CONTENT_WIDTH);
+        int listViewHeight = (int)((height - 60) * 0.76);
+        int startX = (width - contentWidth) / 2;
+        int searchStartY = TEXT_PADDING * 5;
+        int startY = searchStartY + (TEXT_PADDING * 3);
+
+        listViewWidget.reset();
+        addConfigEntries();
+        searchBox.setX2(startX);
+        searchBox.setY2(searchStartY);
+        searchBox.setWidth2(contentWidth);
+        searchBox.setChangedListener2(string -> {
+            listViewWidget.setSearchTerm(string);
+        });
+
+        listViewWidget.setXYSize(startX, startY, contentWidth, listViewHeight);
+        addChild(new ClickableWidget(listViewWidget));
+        addChild(new ClickableWidget(searchBox));
+    }
+
+    @Override
+    public MutableText getScreenTitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.title");
+    }
+
+    @Override
+    public MutableText getScreenSubtitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_save.subtitle", newPreset.getId());
+    }
+
+    public void addConfigEntries() {
+        if(!PIDSManager.getCustomPresets().isEmpty()) {
+            listViewWidget.addCategory(TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.category.save_edits"));
+            File[] resourcePacks = resourcePackFolder.listFiles();
+            if (resourcePacks != null) {
+                for (File pack : resourcePacks) {
+                    if (pack.isDirectory()) {
+                        File jsonFile = pack.toPath().resolve("assets").resolve(Constants.MOD_ID).resolve("joban_custom_resources.json").toFile();
+                        if (jsonFile.exists()) {
+                            String packName = pack.getName();
+                            addResourcePackListing(packName);
+                        }
+                    }
+                }
+            }
+
+            ButtonWidgetExtension exportBtn = new ButtonWidgetExtension(0, 0, 60, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.export"), (btn) -> {
+                openExportScreen();
+            });
+
+            HorizontalWidgetSet widgetSet2 = new HorizontalWidgetSet();
+            widgetSet2.addWidget(new MappedWidget(exportBtn));
+            widgetSet2.setXYSize(0, 0, 100, 20);
+
+            addChild(new ClickableWidget(exportBtn));
+            addChild(new ClickableWidget(widgetSet2));
+
+            ContentItem contentItem2 = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.new"), new MappedWidget(widgetSet2), 26);
+
+            listViewWidget.add(contentItem2);
+        }
+    }
+
+    private void openExportScreen() {
+        MinecraftClient.getInstance().openScreen(
+                new Screen(new ExportScreen(oldPreset, newPreset).withPreviousScreen(previousScreen))
+        );
+    }
+
+    private void addResourcePackListing(String packName) {
+        ButtonWidgetExtension saveBtn = new ButtonWidgetExtension(0, 0, 60, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.save"), (btn) -> {
+            JCMResourceManager.updatePresetInResourcePack(oldPreset, newPreset, packName);
+            onClose2();
+        });
+
+        HorizontalWidgetSet widgetSet = new HorizontalWidgetSet();
+        widgetSet.addWidget(new MappedWidget(saveBtn));
+        widgetSet.setXYSize(0, 0, 100, 20);
+
+        addChild(new ClickableWidget(saveBtn));
+        addChild(new ClickableWidget(widgetSet));
+        ContentItem contentItem = new ContentItem(TextUtil.literal(packName), new MappedWidget(widgetSet), 26);
+
+        listViewWidget.add(contentItem);
+    }
+
+    public static void drawPIDSPreview(PIDSPresetBase preset, GuiDrawing guiDrawing, int startX, int startY, int width, int height, boolean backgroundOnly) {
+        final int offset = 6;
+
+        // Background
+        GuiHelper.drawTexture(guiDrawing, PIDS_PREVIEW_BASE, startX, startY, width, height);
+        if(preset == null) return;
+
+        GuiHelper.drawTexture(guiDrawing, preset.getBackground(), startX+0.5, startY+offset+0.5, width-1, height-offset-4);
+
+        if(!backgroundOnly) {
+            double perRow = height / 8.5;
+            double rowHeight = Math.max(0.5, height / 24.0);
+            for(int i = 0; i < 4; i++) {
+                if(preset.isRowHidden(i)) continue;
+                double curY = startY + offset + ((i+1) * perRow);
+                GuiHelper.drawRectangle(guiDrawing, startX+1.5, curY, width * 0.55, rowHeight, preset.getTextColor());
+                GuiHelper.drawRectangle(guiDrawing, startX + (width * 0.65), curY, rowHeight, rowHeight, preset.getTextColor());
+                GuiHelper.drawRectangle(guiDrawing, startX + (width * 0.75), curY, (width * 0.2)-0.5, rowHeight, preset.getTextColor());
+            }
+        }
+    }
+
+    @Override
+    public void onClose2() {
+        JCMResourceManager.reloadResources();
+        super.onClose2();
+    }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/ExportScreen.java b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/ExportScreen.java
new file mode 100644
index 00000000..8bc65a40
--- /dev/null
+++ b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/ExportScreen.java
@@ -0,0 +1,221 @@
+package com.lx862.jcm.mod.render.gui.screen;
+
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.lx862.jcm.mod.Constants;
+import com.lx862.jcm.mod.data.pids.PIDSManager;
+import com.lx862.jcm.mod.data.pids.preset.PIDSPresetBase;
+import com.lx862.jcm.mod.render.GuiHelper;
+import com.lx862.jcm.mod.render.RenderHelper;
+import com.lx862.jcm.mod.render.gui.screen.base.TitledScreen;
+import com.lx862.jcm.mod.render.gui.widget.ContentItem;
+import com.lx862.jcm.mod.render.gui.widget.ListViewWidget;
+import com.lx862.jcm.mod.render.gui.widget.MappedWidget;
+import com.lx862.jcm.mod.util.JCMLogger;
+import com.lx862.jcm.mod.util.TextCategory;
+import com.lx862.jcm.mod.util.TextUtil;
+import org.mtr.mapping.holder.*;
+import org.mtr.mapping.mapper.ButtonWidgetExtension;
+import org.mtr.mapping.mapper.TextFieldWidgetExtension;
+import org.mtr.mapping.tool.TextCase;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+
+public class ExportScreen extends TitledScreen implements RenderHelper, GuiHelper {
+    private final File resourcePackFolder;
+    private final ListViewWidget listViewWidget;
+    private final PIDSPresetBase oldPreset;
+    private final PIDSPresetBase newPreset;
+    private final String associatedRP;
+    public ExportScreen(PIDSPresetBase oldPreset, PIDSPresetBase newPreset) {
+        super(false);
+        this.resourcePackFolder = new File(MinecraftClient.getInstance().getRunDirectoryMapped(), "resourcepacks");
+        this.listViewWidget = new ListViewWidget();
+        this.newPreset = newPreset;
+        this.oldPreset = oldPreset;
+        this.associatedRP = getAssociatedResourcePack(this.oldPreset.getId());
+    }
+
+    @Override
+    protected void init2() {
+        super.init2();
+        int contentWidth = (int)Math.min((width * 0.75), MAX_CONTENT_WIDTH);
+        int listViewHeight = (int)(6 * 25.4);
+        int startX = (width - contentWidth) / 2;
+        int startY = TEXT_PADDING * 6;
+
+        listViewWidget.reset();
+        addConfigEntries();
+        listViewWidget.setXYSize(startX, startY, contentWidth, listViewHeight);
+        addChild(new ClickableWidget(listViewWidget));
+    }
+
+    @Override
+    public MutableText getScreenTitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.title");
+    }
+
+    @Override
+    public MutableText getScreenSubtitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_save.subtitle", newPreset.getId());
+    }
+
+    public void addConfigEntries() {
+        listViewWidget.addCategory(TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.new2"));
+        addResourcePackListing(PIDSManager.getPreset(newPreset.getId()));
+    }
+
+    private void addResourcePackListing(PIDSPresetBase preset) {
+        if(associatedRP == null) return;
+
+        TextFieldWidgetExtension textField1 = new TextFieldWidgetExtension(0, 0, 160, 20, "", 100, TextCase.DEFAULT, null, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.field.tooltip.rpname").getString());
+        TextFieldWidgetExtension textField2 = new TextFieldWidgetExtension(0, 0, 160, 20, "", 100, TextCase.DEFAULT, null, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.field.tooltip.rpdesc").getString());
+        TextFieldWidgetExtension textField3 = new TextFieldWidgetExtension(0, 0, 160, 20, "", 100, TextCase.DEFAULT, null, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.field.tooltip.rpid").getString());
+
+        textField3.setText2(this.oldPreset.getId());
+
+        ButtonWidgetExtension exportBtn = new ButtonWidgetExtension(0, 0, 60, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.export2"), (btn) -> {
+            export(textField1, textField2, textField3);
+            onClose2();
+        });
+
+        ButtonWidgetExtension backBtn = new ButtonWidgetExtension(0, 0, 60, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.back"), (btn) -> {
+            onClose2();
+        });
+
+        addChild(new ClickableWidget(textField1));
+        addChild(new ClickableWidget(textField2));
+        addChild(new ClickableWidget(textField3));
+        addChild(new ClickableWidget(exportBtn));
+        addChild(new ClickableWidget(backBtn));
+
+        ContentItem contentItem = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.field.rpname"), new MappedWidget(textField1), 26);
+        ContentItem contentItem1 = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.field.rpdesc"), new MappedWidget(textField2), 26);
+        ContentItem contentItem2 = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.field.rpid"), new MappedWidget(textField3), 26);
+        ContentItem contentItem3 = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.title.export"), new MappedWidget(exportBtn), 26);
+        ContentItem contentItem4 = new ContentItem(TextUtil.translatable(TextCategory.GUI,"pids_preset.listview.widget.title.back"), new MappedWidget(backBtn), 26);
+
+        listViewWidget.add(contentItem);
+        listViewWidget.add(contentItem1);
+        listViewWidget.add(contentItem2);
+        listViewWidget.add(contentItem3);
+        listViewWidget.add(contentItem4);
+    }
+
+    private String getAssociatedResourcePack(String targetPresetId) {
+        for(File file : resourcePackFolder.listFiles()) {
+            if(file.isDirectory()) {
+                File jsonFile = resourcePackFolder.toPath().resolve(file.getName()).resolve("assets").resolve(Constants.MOD_ID).resolve("joban_custom_resources.json").toFile();
+                if(jsonFile.exists()) {
+                    JsonArray presetsInRP;
+                    try {
+                        presetsInRP = new JsonParser().parse(new FileReader(jsonFile)).getAsJsonObject().get("pids_images").getAsJsonArray();
+                    } catch (Exception e) {
+                        JCMLogger.debug("Cannot read PIDS Preset from Resource Pack!");
+                        continue;
+                    }
+
+                    for(int i = 0; i < presetsInRP.size(); i++) {
+                        PIDSPresetBase pidsPreset = PIDSManager.parsePreset(presetsInRP.get(i).getAsJsonObject());
+                        if(pidsPreset != null && pidsPreset.getId().equals(oldPreset.getId())) {
+                            return file.getName();
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public void export(TextFieldWidgetExtension textField1, TextFieldWidgetExtension textField2, TextFieldWidgetExtension textField3) {
+        String rpName = textField1.getText2();
+        String rpDesc = textField2.getText2();
+        String rpID = textField3.getText2();
+        int packFormat = 26;
+
+        File rpFolder = new File(resourcePackFolder, rpName);
+        if (!rpFolder.exists()) {
+            rpFolder.mkdirs();
+        }
+
+        JsonObject pack = new JsonObject();
+        pack.addProperty("pack_format", packFormat);
+        pack.addProperty("description", rpDesc);
+
+        JsonObject packMeta = new JsonObject();
+        packMeta.add("pack", pack);
+
+        File packMcmeta = new File(rpFolder, "pack.mcmeta");
+        try {
+            FileWriter writer = new FileWriter(packMcmeta);
+            new GsonBuilder().setPrettyPrinting().create().toJson(packMeta, writer);
+            writer.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        File assetsFolder = new File(rpFolder, "assets");
+        File modFolder = new File(assetsFolder, Constants.MOD_ID);
+        File customResourcesJson = new File(modFolder, "joban_custom_resources.json");
+        if (!modFolder.exists()) {
+            modFolder.mkdirs();
+        }
+
+        JsonArray presetsArray = new JsonArray();
+        JsonObject presetObject = newPreset.toJson();
+        presetObject.addProperty("id", textField3.getText2());
+        presetObject.addProperty("name", textField3.getText2());
+        presetsArray.add(presetObject);
+        JsonObject rootObject = new JsonObject();
+        rootObject.add("pids_images", presetsArray);
+        try {
+            FileWriter writer = new FileWriter(customResourcesJson);
+            new GsonBuilder().setPrettyPrinting().create().toJson(rootObject, writer);
+            writer.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        copyResources(associatedRP, rpFolder);
+    }
+
+    private void copyResources(String oldRPName, File newRpFolder) {
+        File oldRPFolder = new File(resourcePackFolder, oldRPName);
+        File oldAssetsFolder = new File(oldRPFolder, "assets");
+        File oldModFolder = new File(oldAssetsFolder, Constants.MOD_ID);
+
+        String backgroundPath = oldPreset.getBackground().getNamespace() + ":" + oldPreset.getBackground().getPath();
+        String[] folders = backgroundPath.split(":");
+
+        File destFolder = new File(newRpFolder, "assets");
+        for (int i = 0; i < folders.length - 1; i++) {
+            destFolder = new File(destFolder, folders[i]);
+        }
+
+        if (folders.length > 0) {
+            copyResources(oldModFolder, destFolder, folders[folders.length - 1]);
+        } else {
+            JCMLogger.error("Invalid background path: " + backgroundPath);
+        }
+    }
+
+    private void copyResources(File sourceFolder, File destFolder, String fileName) {
+        File sourceFile = new File(sourceFolder, fileName);
+        File destFile = new File(destFolder, fileName);
+        if (!destFile.getParentFile().exists()) {
+            destFile.getParentFile().mkdirs();
+        }
+        try {
+            Files.copy(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        } catch (IOException e) {
+            JCMLogger.error("Failed to copy resource: " + sourceFile + " to " + destFile, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/PIDSPresetScreen.java b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/PIDSPresetScreen.java
index 35117015..58e94384 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/PIDSPresetScreen.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/PIDSPresetScreen.java
@@ -11,10 +11,8 @@
 import com.lx862.jcm.mod.render.gui.widget.MappedWidget;
 import com.lx862.jcm.mod.util.TextCategory;
 import com.lx862.jcm.mod.util.TextUtil;
-import org.mtr.mapping.holder.ClickableWidget;
-import org.mtr.mapping.holder.Identifier;
-import org.mtr.mapping.holder.MutableText;
-import org.mtr.mapping.holder.Text;
+
+import org.mtr.mapping.holder.*;
 import org.mtr.mapping.mapper.ButtonWidgetExtension;
 import org.mtr.mapping.mapper.GuiDrawing;
 import org.mtr.mapping.mapper.TextFieldWidgetExtension;
@@ -89,7 +87,9 @@ private void addPreset(PIDSPresetBase preset) {
         });
 
         ButtonWidgetExtension editBtn = new ButtonWidgetExtension(0, 0, 40, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.listview.widget.edit"), (btn) -> {
-            // TODO: Launch screen to edit PIDS preset
+            MinecraftClient.getInstance().openScreen(
+                new Screen(new VisualEditorScreen(preset.getId(), new Screen(this)).withPreviousScreen(new Screen(this)))
+            );
         });
 
         if(preset.getId().equals(selectedPreset)) {
@@ -97,10 +97,10 @@ private void addPreset(PIDSPresetBase preset) {
             selectBtn.active = false;
         }
 
-//        if(preset.builtin) {
-//            // Can't edit built-in preset
-//            editBtn.active = false;
-//        }
+        if(preset.builtin) {
+            // Can't edit built-in preset
+            editBtn.active = false;
+        }
 
         HorizontalWidgetSet widgetSet = new HorizontalWidgetSet();
         if(!preset.builtin) widgetSet.addWidget(new MappedWidget(editBtn));
@@ -124,7 +124,7 @@ public static void drawPIDSPreview(PIDSPresetBase preset, GuiDrawing guiDrawing,
         // Background
         GuiHelper.drawTexture(guiDrawing, PIDS_PREVIEW_BASE, startX, startY, width, height);
         if(preset == null) return;
-
+        
         GuiHelper.drawTexture(guiDrawing, preset.getBackground(), startX+0.5, startY+offset+0.5, width-1, height-offset-4);
 
         if(!backgroundOnly) {
@@ -149,4 +149,4 @@ private void choose(String id) {
     public boolean isPauseScreen2() {
         return false;
     }
-}
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/VisualEditorScreen.java b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/VisualEditorScreen.java
new file mode 100644
index 00000000..cef1671f
--- /dev/null
+++ b/fabric/src/main/java/com/lx862/jcm/mod/render/gui/screen/VisualEditorScreen.java
@@ -0,0 +1,228 @@
+package com.lx862.jcm.mod.render.gui.screen;
+
+import com.lx862.jcm.mod.Constants;
+import com.lx862.jcm.mod.data.pids.PIDSManager;
+import com.lx862.jcm.mod.data.pids.preset.JsonPIDSPreset;
+import com.lx862.jcm.mod.data.pids.preset.MutableJsonPIDSPreset;
+import com.lx862.jcm.mod.data.pids.preset.PIDSPresetBase;
+import com.lx862.jcm.mod.render.GuiHelper;
+import com.lx862.jcm.mod.render.gui.screen.base.TitledScreen;
+import com.lx862.jcm.mod.util.TextCategory;
+import com.lx862.jcm.mod.util.TextUtil;
+import org.mtr.mapping.holder.*;
+import org.mtr.mapping.mapper.*;
+import org.mtr.mapping.tool.TextCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class VisualEditorScreen extends TitledScreen {
+    private final File resourcePackFolder;
+    private final CheckboxWidgetExtension[] hideRowCheckboxes = new CheckboxWidgetExtension[4];
+    private final TextFieldWidgetExtension idTextField;
+    private final TextFieldWidgetExtension backgroundTextField;
+    private final TextFieldWidgetExtension colorTextField;
+    private final CheckboxWidgetExtension showWeatherCheckbox;
+    private final CheckboxWidgetExtension showClockCheckbox;
+    private final JsonPIDSPreset originalPreset;
+    private final MutableJsonPIDSPreset editedPreset;
+    private final Screen previousScreen;
+    public VisualEditorScreen(String presetId, Screen previousScreen) {
+        super(false);
+        this.resourcePackFolder = new File(org.mtr.mapping.holder.MinecraftClient.getInstance().getRunDirectoryMapped(), "resourcepacks");
+        this.previousScreen = previousScreen;
+        this.originalPreset = (JsonPIDSPreset) PIDSManager.getPreset(presetId);
+        this.editedPreset = originalPreset.toMutable();
+
+        this.backgroundTextField = new TextFieldWidgetExtension(20, 0, 200, 20, Integer.MAX_VALUE, TextCase.DEFAULT, null, null);
+        this.backgroundTextField.setText2(editedPreset.getBackground().getNamespace() + ":" + editedPreset.getBackground().getPath());
+        this.backgroundTextField.setChangedListener2(str -> {
+            Identifier newId = Identifier.tryParse(str);
+            if(newId != null) editedPreset.background = newId;
+        });
+
+        this.idTextField = new TextFieldWidgetExtension(20, 0, 200, 20, Integer.MAX_VALUE, TextCase.DEFAULT, null, null);
+        this.idTextField.setText2(editedPreset.getId());
+        this.idTextField.setChangedListener2(str -> editedPreset.setId(str));
+
+        this.colorTextField = new TextFieldWidgetExtension(20, 0, 200, 20, Integer.MAX_VALUE, TextCase.DEFAULT, null, null);
+        this.colorTextField.setText2(Integer.toHexString(editedPreset.getTextColor() - 0xFF000000));
+        this.colorTextField.setChangedListener2(str -> {
+            try {
+                editedPreset.textColor = (255 << 24) + (int)Integer.parseInt(str, 16);
+            } catch (Exception e) {
+            }
+        });
+
+        this.showWeatherCheckbox = new CheckboxWidgetExtension(20, 0, 200, 20,  TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.weather"), true, isChecked -> {
+            editedPreset.showWeather = isChecked;
+        });
+
+        this.showClockCheckbox = new CheckboxWidgetExtension(20, this.height / 6 + 120, 200, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.clock"), true, isChecked -> {
+            editedPreset.showClock = isChecked;
+        });
+    }
+
+    @Override
+    protected void init2() {
+        super.init2();
+
+        idTextField.setY2(this.height / 6 + 20);
+        addChild(new ClickableWidget(idTextField));
+
+        backgroundTextField.setY2(this.height / 6 + 45);
+        addChild(new ClickableWidget(backgroundTextField));
+
+        colorTextField.setY2(this.height / 6 + 70);
+        colorTextField.setText2(Integer.toHexString(editedPreset.getTextColor()));
+        addChild(new ClickableWidget(colorTextField));
+
+        showWeatherCheckbox.setY2(this.height / 6 + 95);
+        addChild(new ClickableWidget(showWeatherCheckbox));
+
+        showClockCheckbox.setY2(this.height / 6 + 120);
+        addChild(new ClickableWidget(showClockCheckbox));
+
+        int totalWidth = hideRowCheckboxes.length * 30;
+        int horizontalGap = (this.width - totalWidth) / (hideRowCheckboxes.length + 1);
+        int startX = horizontalGap;
+
+        for (int i = 0; i < hideRowCheckboxes.length; i++) {
+            int x = startX + i * (30 + horizontalGap) - 15;
+            int y = this.height - 35;
+            final int index = i;
+            hideRowCheckboxes[i] = new CheckboxWidgetExtension(x, y, 20, 20, TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.hiderow", (i+1)), true, isChecked -> {
+                editedPreset.rowHidden[index] = isChecked;
+            });
+            addChild(new ClickableWidget(hideRowCheckboxes[i]));
+        }
+        populateCurrentPresetFields();
+    }
+
+    @Override
+    public void render(GraphicsHolder graphicsHolder, int mouseX, int mouseY, float delta) {
+        super.render(graphicsHolder, mouseX, mouseY, delta);
+        renderPIDSPreview(graphicsHolder, editedPreset);
+
+        // this was for tooltips
+        // if (idTextField.isMouseOver(mouseX, mouseY)) {
+        // renderWithTooltip(context, Text.of("The name of the template"), mouseX,
+        // mouseY);
+        // } else if (backgroundTextField.isMouseOver(mouseX, mouseY)) {
+        // renderTooltip(matrices, Text.of("The background path of the PID"), mouseX,
+        // mouseY);
+        // } else if (colorTextField.isMouseOver(mouseX, mouseY)) {
+        // renderTooltip(matrices, Text.of("The color of the text shown on the PID"),
+        // mouseX, mouseY);
+        // }
+    }
+
+    @Override
+    public MutableText getScreenTitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.title");
+    }
+
+    @Override
+    public MutableText getScreenSubtitle() {
+        return TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.editingpreset").append(":").append(" ").append(editedPreset.getId());
+    }
+
+    @Override
+    public boolean isPauseScreen2() {
+        return false;
+    }
+
+    @Override
+    public void onClose2() {
+        super.onClose2();
+        MinecraftClient.getInstance().openScreen(
+                new Screen(new EditorSaveScreen(originalPreset, editedPreset, previousScreen).withPreviousScreen(previousScreen))
+        );
+    }
+
+    private void populateCurrentPresetFields() {
+        idTextField.setText2(editedPreset.getId());
+        backgroundTextField.setText2(editedPreset.getBackground().getNamespace() + ":" + editedPreset.getBackground().getPath());
+        colorTextField.setText2(Integer.toHexString(editedPreset.getTextColor()));
+        showWeatherCheckbox.setChecked(editedPreset.getShowWeather());
+        showClockCheckbox.setChecked(editedPreset.getShowClock());
+
+        for(int i = 0; i < 4; i++) {
+            hideRowCheckboxes[i].setChecked(editedPreset.isRowHidden(i));
+        }
+    }
+
+    private void renderPIDSPreview(GraphicsHolder context, JsonPIDSPreset pidsPreset) {
+        Identifier backgroundId = pidsPreset.getBackground();
+        Identifier frameTexture = new Identifier(Constants.MOD_ID, "textures/editor/frame.png");
+        int textColor = pidsPreset.getTextColor();
+
+        GuiDrawing guiDrawing = new GuiDrawing(context);
+
+        int previewWidth = 160;
+        int previewHeight = 90;
+
+        int baseWidth = 427;
+        double scaleFactor = (double)getWidthMapped() / baseWidth;
+        double frameScaleFactor = 1.2;
+        double scaledWidth = previewWidth * scaleFactor;
+        double scaledHeight = previewHeight * scaleFactor;
+
+        int startX = (getWidthMapped() / 2) + 30;
+        int startY = (getHeightMapped() / 4);
+
+        int frameX = startX - (int)(((previewWidth * 0.932 * scaleFactor * frameScaleFactor) - (previewWidth * scaleFactor)) / 2);
+        int frameY = startY - (int)((previewHeight * scaleFactor * frameScaleFactor - previewHeight * scaleFactor) / 2);
+
+        context.drawCenteredText("Preview", (int)(startX + (scaledWidth / 2)), startY - 20, 0xFFFFFF);
+
+        context.push();
+        context.translate((getWidthMapped() / 2.0) + 30, startY, 0);
+        context.scale((float)scaleFactor, (float)scaleFactor, (float)scaleFactor);
+
+        GuiHelper.drawTexture(guiDrawing, frameTexture, frameX, frameY, (int)(previewWidth * 0.932 * scaleFactor * frameScaleFactor), (int)(previewHeight * scaleFactor * frameScaleFactor));
+        // drawTexture unaffected by matrices scale, probably bug in mappings
+        GuiHelper.drawTexture(guiDrawing, backgroundId, startX, startY, previewWidth * scaleFactor, previewHeight * scaleFactor);
+        if (pidsPreset.getShowWeather()) {
+            GuiHelper.drawTexture(guiDrawing, new Identifier(Constants.MOD_ID, "textures/block/pids/weather_sunny.png"), startX + 7, startY, 11 * scaleFactor, 11 * scaleFactor);
+        }
+
+        World world = World.cast(MinecraftClient.getInstance().getWorldMapped());
+
+        if (pidsPreset.getShowClock() && world != null) {
+            long timeNow = WorldHelper.getTimeOfDay(world) + 6000;
+            long hours = timeNow / 1000;
+            long minutes = Math.round((timeNow - (hours * 1000)) / 16.8);
+            String timeString = String.format("%02d:%02d", hours % 24, minutes % 60);
+
+            context.drawText(timeString, 130, 5, 0xFFFFFF, false, GraphicsHolder.getDefaultLight());
+        }
+
+        {
+            context.push();
+            context.translate(0, 1, 0);
+            if(editedPreset.topPadding) context.translate(0, 14, 0);
+            context.scale(1.4F, 1.4F, 1.4F);
+            for(int i = 0; i < 4; i++) {
+                if (!pidsPreset.isRowHidden(i)) {
+                    context.drawText(TextUtil.translatable(TextCategory.GUI, "pids_preset.pids_editor.station"), 5, (int)(i * 14), textColor, false, GraphicsHolder.getDefaultLight());
+                }
+            }
+            context.pop();
+        }
+
+        context.pop();
+    }
+
+    private static boolean isZipContainsFile(File zipFile, String filePath) {
+        try (ZipFile zf = new ZipFile(zipFile)) {
+            ZipEntry entry = zf.getEntry(filePath);
+            return entry != null;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/lx862/jcm/mod/resources/JCMResourceManager.java b/fabric/src/main/java/com/lx862/jcm/mod/resources/JCMResourceManager.java
index a8c80abf..395fac95 100644
--- a/fabric/src/main/java/com/lx862/jcm/mod/resources/JCMResourceManager.java
+++ b/fabric/src/main/java/com/lx862/jcm/mod/resources/JCMResourceManager.java
@@ -35,4 +35,4 @@ private static void parseCustomResources() {
             }
         }));
     }
-}
+}
\ No newline at end of file
diff --git a/fabric/src/main/resources/assets/jsblock/lang/en_us.json b/fabric/src/main/resources/assets/jsblock/lang/en_us.json
index a7f52c54..1f6f836a 100644
--- a/fabric/src/main/resources/assets/jsblock/lang/en_us.json
+++ b/fabric/src/main/resources/assets/jsblock/lang/en_us.json
@@ -106,15 +106,56 @@
   "gui.jsblock.pids.listview.widget.row_hidden": "Hide Arrivals",
   "gui.jsblock.pids_preset.listview.category.builtin": "Built-in Preset",
   "gui.jsblock.pids_preset.listview.category.custom": "Custom Preset",
+  "gui.jsblock.pids_preset.listview.category.save_edits": "Save Changes",
   "gui.jsblock.pids_preset.listview.widget.choose": "Choose",
   "gui.jsblock.pids_preset.listview.widget.edit": "Edit",
   "gui.jsblock.pids_preset.listview.widget.selected": "Selected",
+  "gui.jsblock.pids_preset.listview.widget.save": "Save",
+  "gui.jsblock.pids_preset.listview.widget.export": "Export...",
+  "gui.jsblock.pids_preset.listview.widget.export2": "Export",
+  "gui.jsblock.pids_preset.listview.widget.back": "Back",
+  "gui.jsblock.pids_preset.listview.widget.title.export": "Export the resource pack",
+  "gui.jsblock.pids_preset.listview.widget.title.back": "Go Back",
+  "gui.jsblock.pids_preset.listview.widget.saveas": "Save As...",
+  "gui.jsblock.pids_preset.listview.widget.field.rpname": "Resource Pack Name",
+  "gui.jsblock.pids_preset.listview.widget.field.rpdesc": "Resource Pack Description",
+  "gui.jsblock.pids_preset.listview.widget.field.rpid": "Template ID",
+  "gui.jsblock.pids_preset.listview.widget.field.tooltip.rpname": "My resource pack",
+  "gui.jsblock.pids_preset.listview.widget.field.tooltip.rpdesc": "A very cool resource pack!",
+  "gui.jsblock.pids_preset.listview.widget.field.tooltip.rpid": "id",
+  "gui.jsblock.pids_preset.listview.widget.title.saveas": "Save the changes to a different resource pack",
+  "gui.jsblock.pids_preset.listview.widget.new": "Export as a new resource pack",
+  "gui.jsblock.pids_preset.listview.widget.new2": "Exporting as a new resource pack",
   "gui.jsblock.pids_preset.subtitle": "Selected > %s",
   "gui.jsblock.pids_preset.title": "PIDS Preset",
+  "gui.jsblock.pids_save.subtitle": "Saving > %s",
   "gui.jsblock.sound_looper.listview.title.limit_range": "Limit sound range",
   "gui.jsblock.sound_looper.listview.title.need_redstone": "Require Redstone Power",
   "gui.jsblock.sound_looper.listview.title.pos1": "Position 1 (X, Y, Z)",
   "gui.jsblock.sound_looper.listview.title.pos2": "Position 2 (X, Y, Z)",
+  "gui.jsblock.mismatched_versions": "Please update the Joban Client Mod and try again.",
+  "gui.jsblock.pids.listview.title.hide_platform_number": "Hide Platform Number",
+  "gui.jsblock.pids.listview.title.filtered_platform": "Showing arrivals for %sPlatform %s",
+  "gui.jsblock.pids.listview.title.filtered_platform.nearby": "Nearby ",
+  "gui.jsblock.pids.listview.title.pids_preset": "PIDS Preset",
+  "gui.jsblock.pids.listview.widget.change_platform": "Change...",
+  "gui.jsblock.pids.listview.widget.custom_message": "Custom messages",
+  "gui.jsblock.pids.listview.widget.row_hidden": "Hide Arrivals",
+  "gui.jsblock.pids.listview.widget.choose_preset": "Choose",
+  "gui.jsblock.pids_preset.title": "PIDS Preset",
+  "gui.jsblock.pids_preset.subtitle": "Selected > %s",
+  "gui.jsblock.pids_preset.listview.category.builtin": "Built-in Preset",
+  "gui.jsblock.pids_preset.listview.category.custom": "Custom Preset",
+  "gui.jsblock.pids_preset.listview.widget.edit": "Edit",
+  "gui.jsblock.pids_preset.listview.widget.choose": "Choose",
+  "gui.jsblock.pids_preset.listview.widget.selected": "Selected",
+  "gui.jsblock.pids_preset.pids_editor.title": "PIDS Editor",
+  "gui.jsblock.pids_preset.pids_editor.editingpreset": "Editing preset",
+  "gui.jsblock.pids_preset.pids_editor.weather": "Show Weather",
+  "gui.jsblock.pids_preset.pids_editor.clock": "Show Clock",
+  "gui.jsblock.pids_preset.pids_editor.hiderow": "Hide Row %d",
+  "gui.jsblock.pids_preset.pids_editor.station": "Station",
+  "gui.jsblock.pids_preset.pids_editor.back": "Back",
   "gui.jsblock.sound_looper.listview.title.repeat_tick": "Loop interval in Ticks (1s = 20 Ticks)",
   "gui.jsblock.sound_looper.listview.title.sound_category": "Sound Category",
   "gui.jsblock.sound_looper.listview.title.sound_id": "The identifier for the sound",
diff --git a/fabric/src/main/resources/assets/jsblock/lang/it_it.json b/fabric/src/main/resources/assets/jsblock/lang/it_it.json
index 507d622a..c70f0771 100644
--- a/fabric/src/main/resources/assets/jsblock/lang/it_it.json
+++ b/fabric/src/main/resources/assets/jsblock/lang/it_it.json
@@ -1,11 +1,16 @@
 {
+  "itemGroup.jsblock.main": "Blocchi Mod Joban Client",
+  "itemGroup.jsblock.ceiling": "Blocchi Mod Joban Client - Soffitte",
+  "itemGroup.jsblock.pids": "Blocchi Mod Joban Client - PIDS",
+
   "block.jsblock.apg_door_drl": "APG (Variante Linea Disneyland Resorts)",
   "block.jsblock.apg_glass_drl": "APG (Variante Linea Disneyland Resorts)",
   "block.jsblock.apg_glass_end_drl": "APG (Variante Linea Disneyland Resorts)",
   "block.jsblock.auto_iron_door": "Porta di Ferro Automatica",
   "block.jsblock.block.rv_pids_pole": "Deve essere piazzato sopra i PIDS Railway Vision",
-  "block.jsblock.buffer_stop": "Respingente (Stile Linea East Rail)",
+  "block.jsblock.pids.time": "%02d:%02d",
   "block.jsblock.butterfly_light": "Indicatore chiusura porte (Luce a Farfalla)",
+  "block.jsblock.buffer_stop": "Respingente (Stile Linea East Rail)",
   "block.jsblock.ceiling_slanted": "Soffitta Inclinata",
   "block.jsblock.circle_wall_1": "Muro Circolare (1)",
   "block.jsblock.circle_wall_2": "Muro Circolare (2)",
@@ -16,132 +21,141 @@
   "block.jsblock.circle_wall_7": "Muro Circolare (7)",
   "block.jsblock.departure_pole": "Palo (Per Timer di Partenza)",
   "block.jsblock.departure_timer": "Timer di Partenza",
-  "block.jsblock.exit_sign_even": "Cartello di uscita (Orientazione:numero paro)",
+  "block.jsblock.tcl_emg_stop_button": "Bottone del freno d'Emergenza (Stile Linea Tung Chung)",
+  "block.jsblock.mtr_enquiry_machine": "Macchina di inchiesta",
+  "block.jsblock.rv_enquiry_machine": "Macchina di inchiesta Railway Vision",
+  "block.jsblock.mtr_enquiry_machine_wall": " Macchina di inchiesta MTR (Montata sul muro)",
+  "block.jsblock.kcr_enquiry_machine": "Macchina di inchiesta KCR (Montata sul muro)",
   "block.jsblock.exit_sign_odd": "Cartello di uscita",
+  "block.jsblock.exit_sign_even": "Cartello di uscita (Orientazione:numero paro)",
   "block.jsblock.faresaver": "Salvaprezzo",
   "block.jsblock.fire_alarm": "Allarme Incendio",
+  "block.jsblock.kcr_name_sign": "Cartello con il nome stazione (Stile KCR)",
+  "block.jsblock.kcr_name_sign_station_color": "Cartello con il nome stazione (Stile KCR) (Colore Stazione)",
+  "block.jsblock.kcr_emg_stop_sign": "Cartello Freno di emergenza KCR",
   "block.jsblock.helpline_1": "Assistenza Telefonica (Con Sticker)",
   "block.jsblock.helpline_2": "Assistenza Telefonica",
-  "block.jsblock.helpline_standing": "Assistenza Telefonica (con appoggio)",
   "block.jsblock.helpline_standing_eal": "Assistenza Telefonica (Stile Linea East Rail)",
-  "block.jsblock.kcr_emg_stop_sign": "Cartello Freno di emergenza KCR",
-  "block.jsblock.kcr_enquiry_machine": "Macchina di inchiesta KCR (Montata sul muro)",
-  "block.jsblock.kcr_name_sign": "Cartello con il nome stazione (Stile KCR)",
-  "block.jsblock.kcr_name_sign_station_color": "Cartello con il nome stazione (Stile KCR) (Colore Stazione)",
-  "block.jsblock.kcr_trespass_sign": "Cartello KCR:'Vietato oltrepassare' ",
-  "block.jsblock.light_block": "Risorsa di luce",
-  "block.jsblock.light_lantern": "Lanterna di luce",
+  "block.jsblock.helpline_standing": "Assistenza Telefonica (con appoggio)",
+  "block.jsblock.tml_emg_stop_button": "Bottone del freno d'Emergenza (Con appoggio, TML)",
+  "block.jsblock.sil_emg_stop_button": "Bottone del freno d'Emergenza (Con appoggio, SIL)",
   "block.jsblock.lrt_inter_car_barrier_left": "Barriera carrozza LRT (Sinistra)",
   "block.jsblock.lrt_inter_car_barrier_middle": "Barriera carrozza LRT (Metà)",
   "block.jsblock.lrt_inter_car_barrier_right": "Barriera carrozza LRT (Destra)",
-  "block.jsblock.lrt_trespass_sign": "Cartello LRT:'Vietato oltrepassare' ",
-  "block.jsblock.mtr_enquiry_machine": "Macchina di inchiesta",
-  "block.jsblock.mtr_enquiry_machine_wall": " Macchina di inchiesta MTR (Montata sul muro)",
+  "block.jsblock.light_block": "Risorsa di luce",
+  "block.jsblock.light_lantern": "Lanterna di luce",
+  "block.jsblock.spot_lamp": "Lampadina",
   "block.jsblock.mtr_stairs": "Scale MTR",
-  "block.jsblock.mtr_trespass_sign": "Cartello MTR:'Vietato oltrepassare' ",
   "block.jsblock.operator_button": "Bottone riservato a Operatori",
   "block.jsblock.pids_1a": "Sistema di Visualizzazione delle Informazioni Passeggere (PIDS)",
   "block.jsblock.pids_lcd": "PIDS Vecchia Linea Tsueng Kwan O",
   "block.jsblock.pids_rv": "PIDS della Railway Vision",
   "block.jsblock.pids_rv_sil": "PIDS della Railway Vision (Variante SIL, Stazione Ocean Park e Stazione Wong Chuk Hang)",
   "block.jsblock.pids_rv_sil_2": "PIDS della Railway Vision (Variante SIL, Stazione Admiralty e Stazione South Horizon)",
-  "block.jsblock.rv_enquiry_machine": "Macchina di inchiesta Railway Vision",
   "block.jsblock.rv_pids_pole": "Palo per PIDS della Railway Vision",
+  "block.jsblock.signal_light_red_1": "Segnale a semaforo (Rosso in basso)",
+  "block.jsblock.signal_light_red_2": "Segnale a semaforo (Rosso in alto)",
   "block.jsblock.signal_light_blue": "Segnale a semaforo (Blu in alto)",
   "block.jsblock.signal_light_green": "Segnale a semaforo (Verde in basso)",
   "block.jsblock.signal_light_inverted_1": "Segnale a semaforo (Blu e Rosso Invertiti)",
   "block.jsblock.signal_light_inverted_2": "Segnale a semaforo (Blu e Verde Invertiti)",
-  "block.jsblock.signal_light_red_1": "Segnale a semaforo (Rosso in basso)",
-  "block.jsblock.signal_light_red_2": "Segnale a semaforo (Rosso in alto)",
-  "block.jsblock.sil_emg_stop_button": "Bottone del freno d'Emergenza (Con appoggio, SIL)",
   "block.jsblock.sound_looper": "Ripetitore di suoni",
-  "block.jsblock.spot_lamp": "Lampadina",
   "block.jsblock.station_ceiling_wrl": "Soffitta Stazioni MTR (2009)",
-  "block.jsblock.station_ceiling_wrl_pole": "Pali Soffitta Stazioni MTR (2009)",
   "block.jsblock.station_ceiling_wrl_single": "Soffitta stazione MTR (2009) (Singolo)",
-  "block.jsblock.station_ceiling_wrl_single_pole": "Palo Soffitta stazione MTR Pole (2009) (Singolo)",
-  "block.jsblock.station_ceiling_wrl_single_station_color": "Soffitta stazione MTR (2009, Colore Stazione) (Singolo)",
   "block.jsblock.station_ceiling_wrl_station_color": "Soffitta Stazioni MTR (2009, Colore Stazione)",
+  "block.jsblock.station_ceiling_wrl_single_station_color": "Soffitta stazione MTR (2009, Colore Stazione) (Singolo)",
+  "block.jsblock.station_ceiling_wrl_pole": "Pali Soffitta Stazioni MTR (2009)",
+  "block.jsblock.station_ceiling_wrl_single_pole": "Palo Soffitta stazione MTR Pole (2009) (Singolo)",
   "block.jsblock.station_name_standing": "Nome Stazione (Con Appoggio)",
   "block.jsblock.subsidy_machine": "Macchina ricariche",
-  "block.jsblock.tcl_emg_stop_button": "Bottone del freno d'Emergenza (Stile Linea Tung Chung)",
-  "block.jsblock.thales_ticket_barrier_bare": "Tornello Thales (Parte in Metallo)",
   "block.jsblock.thales_ticket_barrier_entrance": "Tornello Thales (Entrata)",
   "block.jsblock.thales_ticket_barrier_exit": "Tornello Thales (Uscita)",
-  "block.jsblock.tml_emg_stop_button": "Bottone del freno d'Emergenza (Con appoggio, TML)",
+  "block.jsblock.thales_ticket_barrier_bare": "Tornello Thales (Parte in Metallo)",
   "block.jsblock.train_model_e44": "Modellino Treno (E44)",
+  "block.jsblock.mtr_trespass_sign": "Cartello MTR:'Vietato oltrepassare' ",
+  "block.jsblock.kcr_trespass_sign": "Cartello KCR:'Vietato oltrepassare' ",
+  "block.jsblock.lrt_trespass_sign": "Cartello LRT:'Vietato oltrepassare' ",
   "block.jsblock.water_machine": "Fontana d'acqua",
-  "gui.jsblock.atlas_config.not_initialized": "TextureTextRenderer non è abilitato, Il nuovo rendering del testo è stato abilitato?",
-  "gui.jsblock.block_config.discard": "Elimina Impostazioni",
-  "gui.jsblock.block_config.save": "Salva Impostazioni",
-  "gui.jsblock.block_config.subtitle": "A: %d %d %d",
-  "gui.jsblock.block_config.subtitle_with_station": "A: %d %d %d | %s",
+
+  "item.jsblock.apg_door_drl": "APG (Variante Linea Disneyland Resorts)",
+  "item.jsblock.apg_glass_drl": "APG (Variante Linea Disneyland Resorts)",
+  "item.jsblock.apg_glass_end_drl": "APG (Variante Linea Disneyland Resorts)",
+
+  "hud.jsblock.enquiry_machine.success": "Soldi Caricati: $%s",
+  "hud.jsblock.faresaver.success": "Lo sconto di $%d sarà usato alla prossima uscita da una stazione",
+  "hud.jsblock.faresaver.success.sarcasm": "Il così chiamato \"Sconto\" sarà usato alla prossima uscita da una stazione.",
+  "hud.jsblock.faresaver.fail": "Possiedi già uno sconto di $%d per il tuo prossimo viaggio!",
+  "hud.jsblock.faresaver.saved": "Hai salvato $%d.",
+  "hud.jsblock.faresaver.saved_sarcasm": "Hai perso $%d.",
+  "hud.jsblock.light_block.success": "Livello di luce: %d",
+  "hud.jsblock.kcr_name_sign.success": "Direzione Cambiata!",
+  "hud.jsblock.kcr_emg_stop_sign.success": "Direzione Cambiata!",
+  "hud.jsblock.operator_button.fail": "Hai bisogno della chiave da macchinista per usarlo!",
+  "hud.jsblock.subsidy_machine.success": "$%d sono stati aggiunti alla tua card, adesso hai $%d",
+  "hud.jsblock.subsidy_machine.fail": "Prima di riprovare, Attendi %d secondi.",
+  
   "gui.jsblock.brand": "Mod Joban Client",
   "gui.jsblock.butterfly_light.countdown": "Tempo di fermata rimasto quando inizia a lampeggiare",
-  "gui.jsblock.config.crash_log": "Mostra l'ultimo crash log",
-  "gui.jsblock.config.discard": "Cancella configurazione",
   "gui.jsblock.config.fail": "La Configurazione non può essere salvata!",
+  "gui.jsblock.config.version": "Versione %s",
+  "gui.jsblock.config.save": "Salva configurazione",
+  "gui.jsblock.config.discard": "Cancella configurazione",
+  "gui.jsblock.atlas_config.not_initialized": "TextureTextRenderer non è abilitato, Il nuovo rendering del testo è stato abilitato?",
+  "gui.jsblock.config.reset": "Azzerra configurazione",
+  "gui.jsblock.config.crash_log": "Mostra l'ultimo crash log",
   "gui.jsblock.config.latest_log": "Mostra l'ultimo log",
-  "gui.jsblock.config.listview.category.debug": "Debug",
-  "gui.jsblock.config.listview.category.experimental": "Sperimentale",
   "gui.jsblock.config.listview.category.general": "Generale",
-  "gui.jsblock.config.listview.title.custom_font": "Usa carattere personalizzato",
-  "gui.jsblock.config.listview.title.debug_mode": "Abilita modalità debug",
+  "gui.jsblock.config.listview.category.experimental": "Sperimentale",
+  "gui.jsblock.config.listview.category.debug": "Debug",
   "gui.jsblock.config.listview.title.disable_rendering": "Disabilita Rendering",
+  "gui.jsblock.config.listview.title.custom_font": "Usa carattere personalizzato",
   "gui.jsblock.config.listview.title.new_text_rendering": "Abilita il nuovo rendering sperimentale del testo",
+  "gui.jsblock.config.listview.title.debug_mode": "Abilita modalità debug",
   "gui.jsblock.config.listview.title.open_atlas_screen": "Visualizza l'atlas del testo del pacchetto di risorse",
   "gui.jsblock.config.listview.widget.open": "Apri",
-  "gui.jsblock.config.reset": "Azzerra configurazione",
-  "gui.jsblock.config.save": "Salva configurazione",
-  "gui.jsblock.config.version": "Versione %s",
+  "gui.jsblock.block_config.subtitle": "A: %d %d %d",
+  "gui.jsblock.block_config.subtitle_with_station": "A: %d %d %d | %s",
+  "gui.jsblock.block_config.save": "Salva Impostazioni",
+  "gui.jsblock.block_config.discard": "Elimina Impostazioni",
   "gui.jsblock.faresaver.currency": "Valuta",
   "gui.jsblock.faresaver.discount": "Quantità Sconto",
-  "gui.jsblock.pids.listview.title.filtered_platform": "Mostrando arrivi del %sPlatform %s",
+  "gui.jsblock.sound_looper.listview.title.sound_category": "Categoria/Sorgente Suono",
+  "gui.jsblock.sound_looper.listview.title.sound_volume": "Volume Suono (%)",
+  "gui.jsblock.sound_looper.listview.title.limit_range": "Limita la gamma sonora",
+  "gui.jsblock.sound_looper.listview.title.need_redstone": "Richiede l'alimentazione di pietrarossa",
+  "gui.jsblock.sound_looper.listview.title.pos1": "Posizione 1 (X, Y, Z)",
+  "gui.jsblock.sound_looper.listview.title.pos2": "Posizione 2 (X, Y, Z)",
+  "gui.jsblock.sound_looper.listview.title.repeat_tick": "Intervallo di ripetizione in Ticks (1s = 20 Ticks)",
+  "gui.jsblock.sound_looper.listview.title.sound_id": "L'Identificatore per il suono",
+  "gui.jsblock.mismatched_versions": "Per favore aggiorna la Mod Joban Client e prova di nuovo.",
   "gui.jsblock.pids.listview.title.hide_platform_number": "Nascondi numero binario",
   "gui.jsblock.pids.listview.title.pids_preset": "configurazione PIDS",
-  "gui.jsblock.pids.listview.widget.change_platform": "Cambia...",
-  "gui.jsblock.pids.listview.widget.choose_preset": "Seleziona",
   "gui.jsblock.pids.listview.widget.custom_message": "Messaggi personalizzati",
   "gui.jsblock.pids.listview.widget.row_hidden": "Nascondi arrivi",
+  "gui.jsblock.pids.listview.widget.choose_preset": "Seleziona",
+  "gui.jsblock.pids_preset.title": "Configurazione PIDS",
+  "gui.jsblock.pids_preset.subtitle": "Selezionato > %s",
   "gui.jsblock.pids_preset.listview.category.builtin": "Configurazioni pre-installate",
   "gui.jsblock.pids_preset.listview.category.custom": "Configurazione Personalizzata",
-  "gui.jsblock.pids_preset.listview.widget.choose": "Seleziona",
   "gui.jsblock.pids_preset.listview.widget.edit": "Modifica",
+  "gui.jsblock.pids_preset.listview.widget.choose": "Seleziona",
   "gui.jsblock.pids_preset.listview.widget.selected": "Selezionato",
-  "gui.jsblock.pids_preset.subtitle": "Selezionato > %s",
-  "gui.jsblock.pids_preset.title": "Configurazione PIDS",
-  "gui.jsblock.sound_looper.listview.title.limit_range": "Limita la gamma sonora",
-  "gui.jsblock.sound_looper.listview.title.need_redstone": "Richiede l'alimentazione di pietrarossa",
-  "gui.jsblock.sound_looper.listview.title.pos1": "Posizione 1 (X, Y, Z)",
-  "gui.jsblock.sound_looper.listview.title.pos2": "Posizione 2 (X, Y, Z)",
-  "gui.jsblock.sound_looper.listview.title.repeat_tick": "Intervallo di ripetizione in Ticks (1s = 20 Ticks)",
-  "gui.jsblock.sound_looper.listview.title.sound_category": "Categoria/Sorgente Suono",
-  "gui.jsblock.sound_looper.listview.title.sound_id": "L'Identificatore per il suono",
-  "gui.jsblock.sound_looper.listview.title.sound_volume": "Volume Suono (%)",
-  "gui.jsblock.subsidy_machine.cooldown": "Intervallo (Secondi)",
+  "gui.jsblock.pids.listview.title.filtered_platform": "Mostrando arrivi del %sPlatform %s",
+  "gui.jsblock.pids.listview.title.filtered_platform.nearby": "Vicino ",
+  "gui.jsblock.pids.listview.widget.change_platform": "Cambia...",
+  "gui.jsblock.pids_preset.pids_editor.title": "Editor PIDS",
+  "gui.jsblock.pids_preset.pids_editor.editingpreset": "Modificando preimpostazione",
+  "gui.jsblock.pids_preset.pids_editor.weather": "Mostra meteo",
+  "gui.jsblock.pids_preset.pids_editor.clock": "Mostra orologio",
+  "gui.jsblock.pids_preset.pids_editor.hiderow": "Nascondi riga %d",
+  "gui.jsblock.pids_preset.pids_editor.station": "Stazione",
+  "gui.jsblock.pids_preset.pids_editor.back": "Indietro",
   "gui.jsblock.subsidy_machine.currency": "$",
   "gui.jsblock.subsidy_machine.price": "Quantità di ricarica",
-  "gui.jsblock.widget.button.false": "no",
+  "gui.jsblock.subsidy_machine.cooldown": "Intervallo (Secondi)",
   "gui.jsblock.widget.button.true": "Sì",
-  "gui.jsblock.widget.numeric_text_field.decrement": "▼",
-  "gui.jsblock.widget.numeric_text_field.increment": "▲",
+  "gui.jsblock.widget.button.false": "no",
   "gui.jsblock.widget.search": "Cerca qui...",
-  "hud.jsblock.enquiry_machine.success": "Soldi Caricati: $%s",
-  "hud.jsblock.faresaver.fail": "Possiedi già uno sconto di $%d per il tuo prossimo viaggio!",
-  "hud.jsblock.faresaver.saved": "Hai salvato $%d.",
-  "hud.jsblock.faresaver.saved_sarcasm": "Hai perso $%d.",
-  "hud.jsblock.faresaver.success": "Lo sconto di $%d sarà usato alla prossima uscita da una stazione",
-  "hud.jsblock.faresaver.success.sarcasm": "Il così chiamato \"Sconto\" sarà usato alla prossima uscita da una stazione.",
-  "hud.jsblock.kcr_emg_stop_sign.success": "Direzione Cambiata!",
-  "hud.jsblock.kcr_name_sign.success": "Direzione Cambiata!",
-  "hud.jsblock.light_block.success": "Livello di luce: %d",
-  "hud.jsblock.operator_button.fail": "Hai bisogno della chiave da macchinista per usarlo!",
-  "hud.jsblock.subsidy_machine.fail": "Prima di riprovare, Attendi %d secondi.",
-  "hud.jsblock.subsidy_machine.success": "$%d sono stati aggiunti alla tua card, adesso hai $%d",
-  "item.jsblock.apg_door_drl": "APG (Variante Linea Disneyland Resorts)",
-  "item.jsblock.apg_glass_drl": "APG (Variante Linea Disneyland Resorts)",
-  "item.jsblock.apg_glass_end_drl": "APG (Variante Linea Disneyland Resorts)",
-  "itemGroup.jsblock.ceiling": "Blocchi Mod Joban Client - Soffitte",
-  "itemGroup.jsblock.main": "Blocchi Mod Joban Client",
-  "itemGroup.jsblock.pids": "Blocchi Mod Joban Client - PIDS"
+  "gui.jsblock.widget.numeric_text_field.increment": "▲",
+  "gui.jsblock.widget.numeric_text_field.decrement": "▼"
 }
\ No newline at end of file
diff --git a/fabric/src/main/resources/assets/jsblock/textures/editor/frame.png b/fabric/src/main/resources/assets/jsblock/textures/editor/frame.png
new file mode 100644
index 00000000..a3a7e08d
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/editor/frame.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/clock.png b/fabric/src/main/resources/assets/jsblock/textures/ve/clock.png
new file mode 100644
index 00000000..b9a45307
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/clock.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/line1.png b/fabric/src/main/resources/assets/jsblock/textures/ve/line1.png
new file mode 100644
index 00000000..091c1c4c
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/line1.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/line2.png b/fabric/src/main/resources/assets/jsblock/textures/ve/line2.png
new file mode 100644
index 00000000..e03a15ad
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/line2.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/line3.png b/fabric/src/main/resources/assets/jsblock/textures/ve/line3.png
new file mode 100644
index 00000000..8bcbe736
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/line3.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/line4.png b/fabric/src/main/resources/assets/jsblock/textures/ve/line4.png
new file mode 100644
index 00000000..57b89bd3
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/line4.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/missing.png b/fabric/src/main/resources/assets/jsblock/textures/ve/missing.png
new file mode 100644
index 00000000..f4a5d600
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/missing.png differ
diff --git a/fabric/src/main/resources/assets/jsblock/textures/ve/weather.png b/fabric/src/main/resources/assets/jsblock/textures/ve/weather.png
new file mode 100644
index 00000000..2093fe40
Binary files /dev/null and b/fabric/src/main/resources/assets/jsblock/textures/ve/weather.png differ