feat(redesign): re-skin plugin-config editors + Loot Builder to DS (Phase D batch 3)
All checks were successful
Test Asgard Runner / test (push) Successful in 2s

10 plugin-config views (LootBuilder, RaidableBases, Teleport, Kits, Gather, AutoDoors, FurnaceSplitter, BetterChat, TimedExecute, PluginConfigs landing) + 5 child components (loot sidebar/item-editor/group-editor/item-picker, teleport PermissionGroupEditor) re-skinned onto DS components + tokens. All config logic preserved (path-traversal get/set, apply-to-server, import-from-server, CRUD, multiplier logic, per-store status derivation). Presentation-only. Build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-06-11 02:46:16 -04:00
parent b42a2d7ea7
commit 376ed9a98d
15 changed files with 4990 additions and 4006 deletions

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import Button from '@/components/ds/core/Button.vue'
import Icon from '@/components/ds/core/Icon.vue'
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
const props = defineProps<{
configData: Record<string, any>
@@ -47,7 +49,6 @@ function ensurePaths(data: Record<string, any>) {
function addGroup() {
const name = newGroupName.value.trim()
if (!name) return
// Check if already exists
if (groups.value.some(g => g.name === name)) return
const updated = { ...props.configData }
@@ -95,96 +96,95 @@ function updateField(groupName: string, field: string, value: number) {
</script>
<template>
<div class="space-y-4">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-neutral-300 uppercase tracking-wider">VIP Permission Groups</h3>
<div class="pge">
<div class="pge__head">
<div class="pge__section-label">VIP permission groups</div>
</div>
<!-- Add Group -->
<div class="flex gap-2">
<!-- Add group row -->
<div class="pge__add-row">
<input
v-model="newGroupName"
placeholder="New group name (e.g. vip, vip+, mvp)..."
class="flex-1 bg-neutral-800 border border-neutral-700 rounded-lg px-3 py-2 text-sm text-neutral-200"
type="text"
class="pge__name-input"
placeholder="New group name (e.g. vip, vip+, mvp)…"
@keydown.enter="addGroup"
/>
<button
@click="addGroup"
<Button
size="sm"
icon="plus"
:disabled="!newGroupName.trim()"
class="flex items-center gap-1 px-3 py-2 bg-oxide-500 text-white rounded-lg hover:bg-oxide-600 disabled:opacity-50 text-sm"
>
<Plus class="w-4 h-4" />
Add Group
</button>
@click="addGroup"
>Add group</Button>
</div>
<!-- Empty State -->
<div v-if="groups.length === 0" class="bg-neutral-900 border border-neutral-800 rounded-xl p-8 text-center text-neutral-500 text-sm">
No VIP groups defined. Add groups to configure per-permission teleport limits, cooldowns, and countdowns.
</div>
<!-- Empty state -->
<EmptyState
v-if="groups.length === 0"
icon="users"
title="No VIP groups defined"
description="Add groups to configure per-permission teleport limits, cooldowns, and countdowns."
/>
<!-- Groups Table -->
<div v-else class="bg-neutral-900 border border-neutral-800 rounded-xl overflow-hidden">
<table class="w-full text-sm">
<!-- Groups table -->
<div v-else class="pge__table-wrap">
<table class="pge__table">
<thead>
<tr class="border-b border-neutral-800">
<th class="text-left py-3 px-4 text-neutral-500 font-medium">Group Name</th>
<th class="text-center py-3 px-4 text-neutral-500 font-medium w-28">Homes Limit</th>
<th class="text-center py-3 px-4 text-neutral-500 font-medium w-28">Cooldown (s)</th>
<th class="text-center py-3 px-4 text-neutral-500 font-medium w-28">Countdown (s)</th>
<th class="text-center py-3 px-4 text-neutral-500 font-medium w-28">Daily Limit</th>
<th class="w-12"></th>
<tr>
<th class="pge__th pge__th--left">Group name</th>
<th class="pge__th">Homes limit</th>
<th class="pge__th">Cooldown (s)</th>
<th class="pge__th">Countdown (s)</th>
<th class="pge__th">Daily limit</th>
<th class="pge__th pge__th--action" />
</tr>
</thead>
<tbody>
<tr
v-for="group in groups"
:key="group.name"
class="border-b border-neutral-800/50"
class="pge__tr"
>
<td class="py-3 px-4 text-neutral-200 font-medium">{{ group.name }}</td>
<td class="py-3 px-4">
<td class="pge__td pge__td--name">{{ group.name }}</td>
<td class="pge__td pge__td--num">
<input
type="number"
class="pge__num-input"
:value="group.homesLimit"
min="0"
@input="updateField(group.name, 'homesLimit', Number(($event.target as HTMLInputElement).value))"
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-center text-neutral-200"
min="0"
/>
</td>
<td class="py-3 px-4">
<td class="pge__td pge__td--num">
<input
type="number"
class="pge__num-input"
:value="group.cooldown"
min="0"
@input="updateField(group.name, 'cooldown', Number(($event.target as HTMLInputElement).value))"
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-center text-neutral-200"
min="0"
/>
</td>
<td class="py-3 px-4">
<td class="pge__td pge__td--num">
<input
type="number"
class="pge__num-input"
:value="group.countdown"
@input="updateField(group.name, 'countdown', Number(($event.target as HTMLInputElement).value))"
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-center text-neutral-200"
min="0"
@input="updateField(group.name, 'countdown', Number(($event.target as HTMLInputElement).value))"
/>
</td>
<td class="py-3 px-4">
<td class="pge__td pge__td--num">
<input
type="number"
class="pge__num-input"
:value="group.dailyLimit"
@input="updateField(group.name, 'dailyLimit', Number(($event.target as HTMLInputElement).value))"
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-center text-neutral-200"
min="0"
@input="updateField(group.name, 'dailyLimit', Number(($event.target as HTMLInputElement).value))"
/>
</td>
<td class="py-3 px-4">
<button
@click="removeGroup(group.name)"
class="text-neutral-600 hover:text-red-400 transition-colors p-1"
>
<Trash2 class="w-4 h-4" />
<td class="pge__td pge__td--action">
<button class="pge__del" type="button" @click="removeGroup(group.name)">
<Icon name="trash-2" :size="15" />
</button>
</td>
</tr>
@@ -193,3 +193,63 @@ function updateField(groupName: string, field: string, value: number) {
</div>
</div>
</template>
<style scoped>
/* ---------- Shell ---------- */
.pge { display: flex; flex-direction: column; gap: 14px; }
/* ---------- Head ---------- */
.pge__head { display: flex; align-items: center; justify-content: space-between; }
.pge__section-label {
font-size: var(--text-xs); font-weight: 600; color: var(--text-tertiary);
text-transform: uppercase; letter-spacing: var(--tracking-wider);
}
/* ---------- Add row ---------- */
.pge__add-row { display: flex; gap: 8px; align-items: center; }
.pge__name-input {
flex: 1; height: var(--control-h-sm); padding: 0 10px;
background: var(--surface-inset); border: 0; border-radius: var(--radius-sm);
box-shadow: var(--ring-default); font-family: var(--font-sans);
font-size: var(--text-sm); color: var(--text-primary);
}
.pge__name-input:focus { outline: none; box-shadow: inset 0 0 0 1px var(--accent), var(--glow-accent-sm); }
.pge__name-input::placeholder { color: var(--text-muted); }
/* ---------- Table ---------- */
.pge__table-wrap { border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--ring-default); }
.pge__table { width: 100%; border-collapse: collapse; }
.pge__th {
padding: 9px 12px; font-size: var(--text-xs); font-weight: 600;
color: var(--text-tertiary); text-align: center;
background: var(--surface-raised); border-bottom: 1px solid var(--border-subtle);
}
.pge__th--left { text-align: left; }
.pge__th--action { width: 44px; }
.pge__tr { border-bottom: 1px solid var(--border-subtle); }
.pge__tr:last-child { border-bottom: 0; }
.pge__tr:hover { background: var(--surface-hover); }
.pge__td { padding: 8px 12px; font-size: var(--text-sm); color: var(--text-primary); }
.pge__td--name { font-weight: 500; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.pge__td--num { text-align: center; }
.pge__td--action { text-align: center; }
/* ---------- Number input (table cell) ---------- */
.pge__num-input {
width: 80px; height: 28px; padding: 0 8px; text-align: center;
background: var(--surface-inset); border: 0; border-radius: var(--radius-sm);
box-shadow: var(--ring-default); font-family: var(--font-mono);
font-size: var(--text-sm); color: var(--text-primary);
font-variant-numeric: tabular-nums;
}
.pge__num-input:focus { outline: none; box-shadow: inset 0 0 0 1px var(--accent), var(--glow-accent-sm); }
/* ---------- Delete button ---------- */
.pge__del {
display: inline-flex; align-items: center; justify-content: center;
width: 28px; height: 28px; border-radius: var(--radius-sm); border: none;
background: transparent; color: var(--text-muted); cursor: pointer;
transition: var(--transition-colors);
}
.pge__del:hover { color: var(--danger); background: var(--status-offline-soft); }
</style>