mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-23 00:21:28 +00:00
feat(admin): add route reordering with up/down buttons
This commit is contained in:
parent
ac8b4a77b8
commit
7ee1e5899f
4 changed files with 54 additions and 3 deletions
|
|
@ -387,6 +387,17 @@ main {
|
||||||
background: var(--accent-dim);
|
background: var(--accent-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-btn:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:disabled:hover {
|
||||||
|
color: var(--muted);
|
||||||
|
border-color: var(--border);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-btn.danger:hover {
|
.icon-btn.danger:hover {
|
||||||
color: var(--bad);
|
color: var(--bad);
|
||||||
border-color: var(--bad);
|
border-color: var(--bad);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,22 @@
|
||||||
<span v-if="route.fallback" class="badge fallback">{{ t("route.fallback") }}</span>
|
<span v-if="route.fallback" class="badge fallback">{{ t("route.fallback") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
|
<button
|
||||||
|
class="icon-btn"
|
||||||
|
:disabled="atFirst"
|
||||||
|
:title="t('route.moveUp')"
|
||||||
|
@click="$emit('move', route, -1)"
|
||||||
|
>
|
||||||
|
↑
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="icon-btn"
|
||||||
|
:disabled="atLast"
|
||||||
|
:title="t('route.moveDown')"
|
||||||
|
@click="$emit('move', route, 1)"
|
||||||
|
>
|
||||||
|
↓
|
||||||
|
</button>
|
||||||
<button class="icon-btn" :title="t('routeEditor.editTitle')" @click="$emit('edit', route)">
|
<button class="icon-btn" :title="t('routeEditor.editTitle')" @click="$emit('edit', route)">
|
||||||
✎
|
✎
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -67,11 +83,12 @@ import { fmtMatch } from "~/types";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{ route: Route }>();
|
const props = defineProps<{ route: Route; atFirst?: boolean; atLast?: boolean }>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "toggle", route: Route): void;
|
(e: "toggle", route: Route): void;
|
||||||
(e: "edit", route: Route): void;
|
(e: "edit", route: Route): void;
|
||||||
(e: "delete", route: Route): void;
|
(e: "delete", route: Route): void;
|
||||||
|
(e: "move", route: Route, dir: -1 | 1): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function onToggle(event: Event): void {
|
function onToggle(event: Event): void {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ const en: Dict = {
|
||||||
"route.chat": "CHAT",
|
"route.chat": "CHAT",
|
||||||
"route.topic": "TOPIC",
|
"route.topic": "TOPIC",
|
||||||
"route.fallback": "fallback",
|
"route.fallback": "fallback",
|
||||||
|
"route.moveUp": "Move up",
|
||||||
|
"route.moveDown": "Move down",
|
||||||
"filter.event": "Event",
|
"filter.event": "Event",
|
||||||
"filter.repo": "Repo",
|
"filter.repo": "Repo",
|
||||||
"filter.actor": "Actor",
|
"filter.actor": "Actor",
|
||||||
|
|
@ -70,7 +72,7 @@ const en: Dict = {
|
||||||
"routeEditor.langHint": "en or zh; custom via KV i18n:<lang>",
|
"routeEditor.langHint": "en or zh; custom via KV i18n:<lang>",
|
||||||
"routeEditor.enabled": "Route enabled",
|
"routeEditor.enabled": "Route enabled",
|
||||||
"routeEditor.fallback": "Fallback route",
|
"routeEditor.fallback": "Fallback route",
|
||||||
"routeEditor.fallbackHint": "Only fires when no other route matched; its filters are ignored",
|
"routeEditor.fallbackHint": "Only fires when no other route matched; filters still apply",
|
||||||
"routeEditor.filters": "Filters",
|
"routeEditor.filters": "Filters",
|
||||||
"routeEditor.filtersNote": "(all must match · AND)",
|
"routeEditor.filtersNote": "(all must match · AND)",
|
||||||
"routeEditor.matchPlaceholder": "match value",
|
"routeEditor.matchPlaceholder": "match value",
|
||||||
|
|
@ -192,6 +194,8 @@ const zh: Dict = {
|
||||||
"route.chat": "群组",
|
"route.chat": "群组",
|
||||||
"route.topic": "话题",
|
"route.topic": "话题",
|
||||||
"route.fallback": "兜底",
|
"route.fallback": "兜底",
|
||||||
|
"route.moveUp": "上移",
|
||||||
|
"route.moveDown": "下移",
|
||||||
"filter.event": "事件",
|
"filter.event": "事件",
|
||||||
"filter.repo": "仓库",
|
"filter.repo": "仓库",
|
||||||
"filter.actor": "操作者",
|
"filter.actor": "操作者",
|
||||||
|
|
@ -225,7 +229,7 @@ const zh: Dict = {
|
||||||
"routeEditor.langHint": "en 或 zh;可通过 KV i18n:<lang> 自定义",
|
"routeEditor.langHint": "en 或 zh;可通过 KV i18n:<lang> 自定义",
|
||||||
"routeEditor.enabled": "启用路由",
|
"routeEditor.enabled": "启用路由",
|
||||||
"routeEditor.fallback": "兜底路由",
|
"routeEditor.fallback": "兜底路由",
|
||||||
"routeEditor.fallbackHint": "仅当没有其它路由匹配该事件时才发送,其过滤器会被忽略",
|
"routeEditor.fallbackHint": "仅当没有其它路由匹配该事件时才发送,其过滤器仍会生效",
|
||||||
"routeEditor.filters": "过滤器",
|
"routeEditor.filters": "过滤器",
|
||||||
"routeEditor.filtersNote": "(全部匹配 · AND)",
|
"routeEditor.filtersNote": "(全部匹配 · AND)",
|
||||||
"routeEditor.matchPlaceholder": "匹配值",
|
"routeEditor.matchPlaceholder": "匹配值",
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,13 @@
|
||||||
v-for="(r, i) in groupRoutes"
|
v-for="(r, i) in groupRoutes"
|
||||||
:key="r.id"
|
:key="r.id"
|
||||||
:route="r"
|
:route="r"
|
||||||
|
:at-first="i === 0"
|
||||||
|
:at-last="i === groupRoutes.length - 1"
|
||||||
:style="{ animationDelay: i * 45 + 'ms' }"
|
:style="{ animationDelay: i * 45 + 'ms' }"
|
||||||
@toggle="onToggle"
|
@toggle="onToggle"
|
||||||
@edit="openEdit"
|
@edit="openEdit"
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
|
@move="onMove"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -271,6 +274,22 @@ async function onSave(route: Route): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onMove(route: Route, dir: -1 | 1): Promise<void> {
|
||||||
|
const group = selectedGroup.value;
|
||||||
|
if (!group) return;
|
||||||
|
const i = groupRoutes.value.findIndex((r) => r.id === route.id);
|
||||||
|
const j = i + dir;
|
||||||
|
if (i < 0 || j < 0 || j >= groupRoutes.value.length) return;
|
||||||
|
const next = [...groupRoutes.value];
|
||||||
|
[next[i], next[j]] = [next[j]!, next[i]!];
|
||||||
|
try {
|
||||||
|
await saveGroupRoutes(group.id, next);
|
||||||
|
} catch (err) {
|
||||||
|
push(t("toast.saveFailed", { msg: err instanceof Error ? err.message : String(err) }), "bad");
|
||||||
|
loadGroupRoutes(group.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onToggle(route: Route): Promise<void> {
|
async function onToggle(route: Route): Promise<void> {
|
||||||
const group = selectedGroup.value;
|
const group = selectedGroup.value;
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue