Skip to content
Open
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
197 changes: 197 additions & 0 deletions dashboard/src/components/extension/PluginPagesSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<script setup>
import { computed, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useModuleI18n } from "@/i18n/composables";
import { usePluginI18n } from "@/utils/pluginI18n";

const props = defineProps({
plugins: { type: Array, required: true },
});

const route = useRoute();
const router = useRouter();
const { tm } = useModuleI18n("features/extension");
const { pluginName } = usePluginI18n();

const expanded = ref(false);

// Activated plugins that ship web pages; disabled ones are hidden since their
// pages cannot be opened anyway.
const pagePlugins = computed(() =>
props.plugins.filter(
(plugin) =>
plugin.activated && Array.isArray(plugin?.pages) && plugin.pages.length > 0,
),
);

const openPluginPage = (plugin, event) => {
const pages = plugin?.pages;
if (!Array.isArray(pages) || pages.length === 0 || !plugin?.name) return;
if (!plugin.activated) return;

const openInNewTab =
event.button === 1 || event.ctrlKey || event.metaKey || event.shiftKey;

if (event.type === "auxclick" && !openInNewTab) {
// Right-click (button === 2): leave the native context menu alone.
return;
}

if (openInNewTab) {
const url = router.resolve({
name: "ExtensionPluginPages",
params: { pluginName: plugin.name, pageName: pages[0] },
}).href;
window.open(url, "_blank", "noopener");
return;
}

router.push({
name: "ExtensionPluginPages",
params: { pluginName: plugin.name, pageName: pages[0] },
});
};

// Suppress the browser's native middle-click autoscroll indicator so the
// custom middle-click new-tab behavior is the only effect.
const onMouseDown = (event) => {
if (event.button === 1) event.preventDefault();
};

// Convert vertical wheel input into horizontal scrolling for the switcher row,
// so a mouse wheel can sweep through many plugin entries. Once the row hits
// either end the wheel falls through to the page's vertical scroll again.
const onWheel = (event) => {
const el = event.currentTarget;
if (el.scrollWidth <= el.clientWidth) return;

const delta = event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY;
const maxScroll = el.scrollWidth - el.clientWidth;
const canScroll =
(delta < 0 && el.scrollLeft > 0) || (delta > 0 && el.scrollLeft < maxScroll);
if (!canScroll) return;

event.preventDefault();
el.scrollLeft += delta;
};
</script>

<template>
<div
v-if="pagePlugins.length > 0"
class="plugin-pages-switcher"
@mousedown="onMouseDown"
>
<div
class="plugin-pages-switcher__list"
:class="{ 'plugin-pages-switcher__list--expanded': expanded }"
@wheel="onWheel"
>
<button
v-for="plugin in pagePlugins"
:key="plugin.name"
type="button"
class="plugin-pages-switcher__item"
:class="{
'plugin-pages-switcher__item--active':
plugin.name === route.params.pluginName,
}"
@click="openPluginPage(plugin, $event)"
@auxclick="openPluginPage(plugin, $event)"
>
{{ pluginName(plugin) }}
</button>
</div>

<button
type="button"
class="plugin-pages-switcher__toggle"
:title="expanded ? tm('buttons.collapse') : tm('buttons.expand')"
:aria-label="expanded ? tm('buttons.collapse') : tm('buttons.expand')"
:aria-expanded="expanded"
@click="expanded = !expanded"
>
<v-icon
:icon="expanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
size="small"
/>
</button>
</div>
</template>

<style scoped>
/* Horizontal navigation row of every plugin that ships web pages. Collapsed
it scrolls horizontally (hidden scrollbar like the workspace tab strip);
expanded it wraps and shows every entry at once. */
.plugin-pages-switcher {
align-items: center;
display: flex;
flex-shrink: 0;
gap: 6px;
padding: 8px 0;
}

.plugin-pages-switcher__list {
align-items: center;
display: flex;
flex: 1;
gap: 6px;
min-width: 0;
overflow-x: auto;
scrollbar-width: none;
}

.plugin-pages-switcher__list::-webkit-scrollbar {
display: none;
}

.plugin-pages-switcher__list--expanded {
flex-wrap: wrap;
overflow: visible;
}

.plugin-pages-switcher__item {
align-items: center;
background: transparent;
border: 0;
border-radius: 8px;
color: rgba(var(--v-theme-on-surface), 0.58);
cursor: pointer;
display: inline-flex;
flex-shrink: 0;
font-size: 0.8125rem;
font-weight: 500;
height: 30px;
padding: 0 12px;
white-space: nowrap;
}

.plugin-pages-switcher__item:hover {
background: rgba(var(--v-theme-on-surface), 0.045);
color: rgba(var(--v-theme-on-surface), 0.78);
}

.plugin-pages-switcher__item--active {
background: rgba(var(--v-theme-on-surface), 0.065);
color: rgba(var(--v-theme-on-surface), 0.86);
}

.plugin-pages-switcher__toggle {
align-items: center;
background: transparent;
border: 0;
border-radius: 8px;
color: rgba(var(--v-theme-on-surface), 0.58);
cursor: pointer;
display: inline-flex;
flex-shrink: 0;
height: 30px;
justify-content: center;
width: 30px;
}

.plugin-pages-switcher__toggle:hover {
background: rgba(var(--v-theme-on-surface), 0.045);
color: rgba(var(--v-theme-on-surface), 0.78);
}
</style>
84 changes: 0 additions & 84 deletions dashboard/src/composables/usePluginSidebarItems.ts

This file was deleted.

6 changes: 3 additions & 3 deletions dashboard/src/i18n/locales/en-US/core/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"market": "Plugin Market",
"mcp": "MCP Servers",
"skills": "Skills",
"components": "Handlers"
"components": "Handlers",
"pluginPages": "Plugin Pages"
},
"conversation": "Conversations",
"sessionManagement": "Custom Rules",
Expand Down Expand Up @@ -45,6 +46,5 @@
"configTabs": {
"normal": "Normal Config",
"system": "System Config"
},
"pluginWebui": "Plugin Pages"
}
}
9 changes: 7 additions & 2 deletions dashboard/src/i18n/locales/en-US/features/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"market": "AstrBot Plugin Market"
},
"titles": {
"installedAstrBotPlugins": "Installed"
"installedAstrBotPlugins": "Installed",
"pluginPages": "Plugin Pages"
},
"failedPlugins": {
"title": "Failed to Load Plugins ({count})",
Expand Down Expand Up @@ -48,6 +49,8 @@
"openPages": "Pages",
"openPage": "Open",
"openWebui": "Open Plugin UI",
"expand": "Expand",
"collapse": "Collapse",
"close": "Close",
"save": "Save",
"saveAndClose": "Save and Close",
Expand Down Expand Up @@ -103,7 +106,9 @@
},
"empty": {
"noPlugins": "No Extensions",
"noPluginsDesc": "Try installing extensions or adjusting the search"
"noPluginsDesc": "Try installing extensions or adjusting the search",
"noPluginPages": "No Plugin Pages",
"noPluginPagesDesc": "Install plugins that ship web pages and they will show up here"
},
"detail": {
"contents": "Plugin Handlers",
Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/i18n/locales/ru-RU/core/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"market": "Магазин плагинов",
"mcp": "Серверы MCP",
"skills": "Навыки",
"components": "Управление поведением"
"components": "Управление поведением",
"pluginPages": "Страницы плагинов"
},
"config": "Конфигурация",
"chat": "Чат",
Expand Down Expand Up @@ -45,6 +46,5 @@
"configTabs": {
"normal": "Обычная конфигурация",
"system": "Системная конфигурация"
},
"pluginWebui": "Страницы плагинов"
}
}
13 changes: 11 additions & 2 deletions dashboard/src/i18n/locales/ru-RU/features/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"handlersOperation": "Управление поведением"
},
"titles": {
"installedAstrBotPlugins": "Установленные"
"installedAstrBotPlugins": "Установленные",
"pluginPages": "Страницы плагинов"
},
"failedPlugins": {
"title": "Ошибка загрузки ({count})",
Expand Down Expand Up @@ -48,6 +49,8 @@
"openPages": "Pages",
"openPage": "Открыть",
"openWebui": "Открыть UI плагина",
"expand": "Развернуть",
"collapse": "Свернуть",
"close": "Закрыть",
"save": "Сохранить",
"saveAndClose": "Сохранить и закрыть",
Expand Down Expand Up @@ -103,7 +106,9 @@
},
"empty": {
"noPlugins": "Плагины не найдены",
"noPluginsDesc": "Попробуйте установить новые плагины или изменить поиск."
"noPluginsDesc": "Попробуйте установить новые плагины или изменить поиск.",
"noPluginPages": "Страницы плагинов не найдены",
"noPluginPagesDesc": "Установите плагины с веб-страницами, и они появятся здесь."
},
"detail": {
"contents": "Обработчики плагина",
Expand Down Expand Up @@ -303,6 +308,10 @@
"invalidUrl": "Введите корректный URL",
"invalidRepositoryUrl": "Введите корректный URL Git-репозитория",
"enterJsonUrl": "Введите URL, возвращающий список плагинов в формате JSON",
"pluginNotFound": "Плагин не найден",
"pluginDisabled": "Этот плагин отключён. Включите его, прежде чем открывать страницу.",
"pluginPageNotFound": "Страница плагина не найдена",
"pluginPageLoadFailed": "Не удалось загрузить страницу плагина",
"missingMarketPluginId": "Эта запись источника не содержит идентификаторов плагина, поэтому его нельзя установить из магазина",
"sourceBindSuccess": "Источник плагина обновлен",
"validatingPlugin": "Проверка плагина...",
Expand Down
Loading
Loading