Compare commits
1 commit
main
...
refactor/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab62d9c96d |
4 changed files with 55 additions and 32 deletions
|
|
@ -155,34 +155,58 @@ function installPetContextMenu(window: BrowserWindow, action: { readonly label:
|
|||
async function buildPetContextMenuTemplate(action: { readonly label: string; readonly click: () => void; readonly defaultPet?: boolean }): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
if (!action.defaultPet) return [{ label: action.label, click: action.click }];
|
||||
const commands = await getDefaultPetPluginCommands();
|
||||
const topLevel: Electron.MenuItemConstructorOptions[] = [];
|
||||
const topLevelStatus: Electron.MenuItemConstructorOptions[] = [];
|
||||
const topLevelActions: Electron.MenuItemConstructorOptions[] = [];
|
||||
const plugins = new Map<string, { name: string; commands: Electron.MenuItemConstructorOptions[]; menuItems: Electron.MenuItemConstructorOptions[] }>();
|
||||
const sorted = [...commands].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
||||
for (const command of sorted) {
|
||||
const item: Electron.MenuItemConstructorOptions = { label: command.commandTitle, click: () => { if (command.form) openPluginCommandForm(command).catch((error) => logError("pet.window", "plugin command form failed", error)); else executeDefaultPetPluginCommand(command.pluginId, command.commandId).catch((error) => logError("pet.window", "plugin command failed", error)); } };
|
||||
if (command.placement === "top" || command.featured) { topLevel.push(item); continue; }
|
||||
if (command.placement === "top" || command.featured) {
|
||||
if (command.commandId === "status") {
|
||||
topLevelStatus.push(item);
|
||||
} else {
|
||||
topLevelActions.push(item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const group = plugins.get(command.pluginId) ?? { name: command.pluginName, commands: [], menuItems: [] };
|
||||
group.commands.push(item);
|
||||
plugins.set(command.pluginId, group);
|
||||
}
|
||||
// Fully dynamic per-plugin menu sections (ui.menu.setItems) — put care actions first, then status labels.
|
||||
// Fully dynamic per-plugin menu sections (ui.menu.setItems) — status labels follow the top-level status command.
|
||||
const menuItems = await getDefaultPetPluginMenuItems();
|
||||
for (const item of menuItems) {
|
||||
const group = plugins.get(item.pluginId) ?? { name: item.pluginName, commands: [], menuItems: [] };
|
||||
group.menuItems.push({ label: item.title, enabled: item.enabled !== false, type: item.checked === true ? "checkbox" : "normal", checked: item.checked === true ? true : undefined, click: () => { executeDefaultPetPluginMenuSelect(item.pluginId, item.itemId).catch((error) => logError("pet.window", "plugin menu select failed", error)); } });
|
||||
plugins.set(item.pluginId, group);
|
||||
}
|
||||
|
||||
const buildPluginSubmenu = (plugin: { name: string; commands: Electron.MenuItemConstructorOptions[]; menuItems: Electron.MenuItemConstructorOptions[] }): Electron.MenuItemConstructorOptions => {
|
||||
const submenu: Electron.MenuItemConstructorOptions[] = [];
|
||||
if (plugin.commands.length > 0) submenu.push(...plugin.commands);
|
||||
if (plugin.commands.length > 0 && plugin.menuItems.length > 0) submenu.push({ type: "separator" });
|
||||
if (plugin.menuItems.length > 0) submenu.push(...plugin.menuItems);
|
||||
return { label: plugin.name, submenu };
|
||||
};
|
||||
|
||||
// Place the virtual-pet stats submenu directly under its top-level status command.
|
||||
const statsPluginId = "openpets.virtual-pet";
|
||||
const statsGroup = plugins.get(statsPluginId);
|
||||
const statsSubmenu = statsGroup && statsGroup.menuItems.length > 0 ? buildPluginSubmenu(statsGroup) : undefined;
|
||||
if (statsGroup) plugins.delete(statsPluginId);
|
||||
|
||||
const template: Electron.MenuItemConstructorOptions[] = [];
|
||||
if (topLevel.length > 0) template.push(...topLevel.slice(0, 8), { type: "separator" });
|
||||
if (plugins.size > 0) {
|
||||
template.push(...[...plugins.values()].map((plugin) => {
|
||||
const submenu: Electron.MenuItemConstructorOptions[] = [];
|
||||
if (plugin.commands.length > 0) submenu.push(...plugin.commands);
|
||||
if (plugin.commands.length > 0 && plugin.menuItems.length > 0) submenu.push({ type: "separator" });
|
||||
if (plugin.menuItems.length > 0) submenu.push(...plugin.menuItems);
|
||||
return { label: plugin.name, submenu };
|
||||
}), { type: "separator" });
|
||||
if (topLevelStatus.length > 0) template.push(...topLevelStatus.slice(0, 8));
|
||||
if (statsSubmenu) template.push(statsSubmenu);
|
||||
if (topLevelActions.length > 0) {
|
||||
if (template.length > 0) template.push({ type: "separator" });
|
||||
template.push(...topLevelActions.slice(0, 8));
|
||||
}
|
||||
if (plugins.size > 0) {
|
||||
if (template.length > 0) template.push({ type: "separator" });
|
||||
template.push(...[...plugins.values()].map(buildPluginSubmenu));
|
||||
}
|
||||
if (template.length > 0) template.push({ type: "separator" });
|
||||
template.push({ label: t("pet.menu.openControlCenter"), click: () => { import("./windows.js").then(({ openControlCenterWindow }) => openControlCenterWindow()).catch((error) => logError("pet.window", "open control center failed", error)); } }, { label: action.label, click: action.click });
|
||||
return template;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,16 +104,16 @@ async function playActionSound(ctx) {
|
|||
function buildStatusMenuItems(ctx, state, now = Date.now()) {
|
||||
const mood = getMood(state, now);
|
||||
const sep = " · ";
|
||||
const levelText = `${ctx.t("menu.level")}: ${state.level}`;
|
||||
const bondText = `${ctx.t("menu.bond")}: ${Math.round(state.affection)}%`;
|
||||
const moodText = `${ctx.t("menu.mood")}: ${ctx.t(`mood.${mood}`)}`;
|
||||
const playText = `${ctx.t("menu.play")}: ${Math.round(state.happiness)}%`;
|
||||
const foodText = `${ctx.t("menu.food")}: ${Math.round(state.hunger)}%`;
|
||||
const energyText = `${ctx.t("menu.energy")}: ${Math.round(state.energy)}%`;
|
||||
const playText = `${ctx.t("menu.play")}: ${Math.round(state.happiness)}%`;
|
||||
const bondText = `${ctx.t("menu.bond")}: ${Math.round(state.affection)}%`;
|
||||
const levelText = `${ctx.t("menu.level")}: ${state.level}`;
|
||||
return [
|
||||
{ id: "stats-row-1", title: `${moodText}${sep}${foodText}`, enabled: false },
|
||||
{ id: "stats-row-2", title: `${energyText}${sep}${playText}`, enabled: false },
|
||||
{ id: "stats-row-3", title: `${bondText}${sep}${levelText}`, enabled: false },
|
||||
{ id: "stats-row-1", title: `${levelText}${sep}${bondText}`, enabled: false },
|
||||
{ id: "stats-row-2", title: `${moodText}${sep}${playText}`, enabled: false },
|
||||
{ id: "stats-row-3", title: `${foodText}${sep}${energyText}`, enabled: false },
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -345,11 +345,11 @@ export function register(OpenPetsPlugin) {
|
|||
|
||||
const icon = ctx.assets.icon("virtual-pet");
|
||||
|
||||
await ctx.commands.register({ id: "feed", title: "$t:command.feed.title", description: "$t:command.feed.description", icon }, () => feed(ctx));
|
||||
await ctx.commands.register({ id: "play", title: "$t:command.play.title", description: "$t:command.play.description", icon }, () => play(ctx));
|
||||
await ctx.commands.register({ id: "pet", title: "$t:command.pet.title", description: "$t:command.pet.description", icon }, () => pet(ctx));
|
||||
await ctx.commands.register({ id: "nap", title: "$t:command.nap.title", description: "$t:command.nap.description", icon }, () => nap(ctx));
|
||||
await ctx.commands.register({ id: "status", title: "$t:command.status.title", description: "$t:command.status.description", icon, placement: "top" }, () => showStatus(ctx));
|
||||
await ctx.commands.register({ id: "status", title: "$t:command.status.title", description: "$t:command.status.description", icon, placement: "top", priority: 100 }, () => showStatus(ctx));
|
||||
await ctx.commands.register({ id: "pet", title: "$t:command.pet.title", description: "$t:command.pet.description", icon, placement: "top", priority: 80 }, () => pet(ctx));
|
||||
await ctx.commands.register({ id: "play", title: "$t:command.play.title", description: "$t:command.play.description", icon, placement: "top", priority: 60 }, () => play(ctx));
|
||||
await ctx.commands.register({ id: "feed", title: "$t:command.feed.title", description: "$t:command.feed.description", icon, placement: "top", priority: 40 }, () => feed(ctx));
|
||||
await ctx.commands.register({ id: "nap", title: "$t:command.nap.title", description: "$t:command.nap.description", icon, placement: "top", priority: 20 }, () => nap(ctx));
|
||||
},
|
||||
async stop() {},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"plugin.name": "Virtual Pet",
|
||||
"plugin.name": "Pet Status",
|
||||
"plugin.description": "Care for a little desktop companion and watch your bond grow through gentle everyday interactions.",
|
||||
"hud.food": "Food",
|
||||
"hud.energy": "Energy",
|
||||
|
|
@ -8,15 +8,15 @@
|
|||
"config.sound.label": "Action sound",
|
||||
"config.sound.description": "Optionally play a sound for direct care actions like feeding, playing, petting, or resting.",
|
||||
|
||||
"command.feed.title": "Feed snack",
|
||||
"command.feed.title": "Feed Snack",
|
||||
"command.feed.description": "Feed your pet a snack.",
|
||||
"command.play.title": "Play game",
|
||||
"command.play.title": "Play Game",
|
||||
"command.play.description": "Play a quick game.",
|
||||
"command.pet.title": "Pet",
|
||||
"command.pet.description": "Give your pet some affection.",
|
||||
"command.nap.title": "Let rest",
|
||||
"command.nap.title": "Let Rest",
|
||||
"command.nap.description": "Let your pet rest for 15 minutes.",
|
||||
"command.status.title": "Check on pet",
|
||||
"command.status.title": "Check On Pet",
|
||||
"command.status.description": "Check how your pet is feeling.",
|
||||
|
||||
"menu.mood": "Mood",
|
||||
|
|
|
|||
|
|
@ -94,10 +94,9 @@ const LOCALES = { en: JSON.parse(await readFile(new URL("./locales/en.json", imp
|
|||
// Expect status labels in the dynamic context menu (no on-pet HUD overlay)
|
||||
const menu = h.calls.menuItems;
|
||||
assert.ok(menu.length > 0, "Should populate the context menu with status labels");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-1" && item.title.includes("80%")), "Menu should show food status");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-2" && item.title.includes("80%")), "Menu should show energy status");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-2" && item.title.includes("80%")), "Menu should show play status");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-3" && item.title.includes("50%")), "Menu should show bond status");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-1" && item.title.includes("Level: 1") && item.title.includes("50%")), "Menu should show level and bond status");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-2" && item.title.includes("Mood:") && item.title.includes("80%")), "Menu should show mood and play status");
|
||||
assert.ok(menu.some((item) => item.id === "stats-row-3" && item.title.includes("80%")), "Menu should show food and energy status");
|
||||
|
||||
h.expectNoErrors();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue