Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -138,7 +138,19 @@ public void onReload()
}
else
{
// Reload the database into cache first so wipe/re-import operate on a
// consistent view of what is currently stored.
this.manager.reload();

// Re-import each active game mode's panels from controlPanelTemplate.yml so
// that admin edits to the template take effect on reload. Without this the
// panels stay pinned to whatever was first seeded into the database.
this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
{
this.manager.reimportControlPanels(gameModeAddon);
}
});
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,9 +127,10 @@ public void setup()
@Override
public boolean execute(User user, String label, List<String> args)
{
ControlPanelManager manager = this.<ControlPanelAddon>getAddon().getAddonManager();
ControlPanelAddon addon = this.getAddon();
ControlPanelManager manager = addon.getAddonManager();

String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml";
String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile();

if (manager.hasAnyControlPanel(this.getWorld()))
{
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/world/bentobox/controlpanel/config/Settings.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,28 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
}


/**
* This method returns the templateFile value.
*
* @return the value of templateFile.
*/
public String getTemplateFile()
{
return templateFile;
}


/**
* This method sets the templateFile value.
*
* @param templateFile the templateFile new value.
*/
public void setTemplateFile(String templateFile)
{
this.templateFile = templateFile;
}


// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
Expand All@@ -64,4 +86,12 @@ public void setDisabledGameModes(Set<String> disabledGameModes)
@ConfigComment(" - BSkyBlock")
@ConfigEntry(path = "disabled-gamemodes")
private Set<String> disabledGameModes = new HashSet<>();

@ConfigComment("")
@ConfigComment("The template file that control panels are imported from.")
@ConfigComment("This file is read when panels are first seeded and re-read on every reload,")
@ConfigComment("so editing it and running 'bbox reload' will apply your changes.")
@ConfigComment("It must exist in the addon folder (addons/ControlPanel).")
@ConfigEntry(path = "template-file")
private String templateFile = "controlPanelTemplate.yml";
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon)
{
this.addon = addon;

// save template file into directory.
// Save the default template file into the directory. The configured template file
// may point elsewhere, but the packaged default is always named controlPanelTemplate.yml.
if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists())
{
this.addon.saveResource("controlPanelTemplate.yml", false);
Expand DownExpand Up@@ -128,6 +129,26 @@ public void reload()
}


/**
* This method re-imports the control panels for the given game mode from the
* template file configured via {@code template-file} in config.yml (defaults to
* {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the
* database and cache.
* <p>
* This is what makes edits to the template file take effect on {@code bbox reload}:
* without it, {@link #reload()} only re-reads the database and template changes are
* silently ignored.
*
* @param gameModeAddon Game mode whose panels must be re-imported from the template.
*/
public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon)
{
// Clear existing data so removed/renamed panels do not linger, then re-read the template.
this.wipeData(gameModeAddon, null);
this.importControlPanels(null, gameModeAddon);
}


// ---------------------------------------------------------------------
// Section: Save methods
// ---------------------------------------------------------------------
Expand DownExpand Up@@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN
*/
public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon)
{
this.importControlPanels(user, addon, "controlPanelTemplate");
this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile());
}


Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/config.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,3 +5,9 @@
# disabled-gamemodes:
# - BSkyBlock
disabled-gamemodes: []
#
# The template file that control panels are imported from.
# This file is read when panels are first seeded and re-read on every reload,
# so editing it and running 'bbox reload' will apply your changes.
# It must exist in the addon folder (addons/ControlPanel).
template-file: controlPanelTemplate.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,4 +35,15 @@ void testSetDisabledGameModes() {
assertTrue(settings.getDisabledGameModes().contains("BSkyBlock"));
assertTrue(settings.getDisabledGameModes().contains("AcidIsland"));
}

@Test
void testDefaultTemplateFile() {
assertEquals("controlPanelTemplate.yml", settings.getTemplateFile());
}

@Test
void testSetTemplateFile() {
settings.setTemplateFile("myPanels.yml");
assertEquals("myPanels.yml", settings.getTemplateFile());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ public void setUp() throws Exception {

when(addon.getPlugin()).thenReturn(plugin);
when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings());

File dataFolder = new File("addons/ControlPanel");
dataFolder.mkdirs();
Expand DownExpand Up@@ -260,6 +261,96 @@ void testReload() {
manager.reload();
}

@Test
void testReimportControlPanelsReadsTemplateFile() throws Exception {
// Make import's trailing getAddonManager().save() call resolve to this manager.
when(addon.getAddonManager()).thenReturn(manager);

// Write a template file that reimport should read from disk.
writeTemplate("""
panel-list:
default:
defaultPanel: true
panelName: 'From Template'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

// Template panel is now in the cache.
assertTrue(manager.hasAnyControlPanel(world));
ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Template", panel.getPanelName());
}

@Test
void testReimportControlPanelsRemovesDeletedPanels() throws Exception {
when(addon.getAddonManager()).thenReturn(manager);

// Seed the cache with an existing panel, as if previously stored in the database.
when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel()));
manager.reload();
assertTrue(manager.hasAnyControlPanel(world));

// A template with no panel-list entries should wipe the stored panel on reimport.
writeTemplate("panel-list: {}\n");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

assertFalse(manager.hasAnyControlPanel(world));
}

@Test
void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception {
// Point the config at a custom template file name.
world.bentobox.controlpanel.config.Settings settings =
new world.bentobox.controlpanel.config.Settings();
settings.setTemplateFile("customPanels.yml");
when(addon.getSettings()).thenReturn(settings);
when(addon.getAddonManager()).thenReturn(manager);

// The default template exists but is empty; only the custom file has a panel.
writeTemplate("panel-list: {}\n");
writeNamedTemplate("customPanels.yml", """
panel-list:
default:
defaultPanel: true
panelName: 'From Custom File'
permission: 'default'
buttons:
0:
name: 'Island'
material: GRASS_BLOCK
description: 'Go to your island'
command: '[label] go'
""");

GameModeAddon gameModeAddon = mock(GameModeAddon.class);
manager.reimportControlPanels(gameModeAddon);

ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock.");
assertNotNull(panel);
assertEquals("From Custom File", panel.getPanelName());
}

private void writeTemplate(String content) throws Exception {
writeNamedTemplate("controlPanelTemplate.yml", content);
}

private void writeNamedTemplate(String name, String content) throws Exception {
File templateFile = new File(new File("addons/ControlPanel"), name);
java.nio.file.Files.writeString(templateFile.toPath(), content);
}

private ControlPanelObject createDefaultPanel() {
ControlPanelObject cpo = new ControlPanelObject();
cpo.setUniqueId("BSkyBlock_default");
Expand Down
Loading