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
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user