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:
@@ -2,7 +2,12 @@
|
||||
import { computed } from 'vue'
|
||||
import { rustItems } from '@/data/rust-items'
|
||||
import { rustContainers } from '@/data/rust-containers'
|
||||
import { Trash2, Plus, Settings2 } from 'lucide-vue-next'
|
||||
import Panel from '@/components/ds/data/Panel.vue'
|
||||
import Button from '@/components/ds/core/Button.vue'
|
||||
import IconButton from '@/components/ds/core/IconButton.vue'
|
||||
import Badge from '@/components/ds/core/Badge.vue'
|
||||
import Switch from '@/components/ds/forms/Switch.vue'
|
||||
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
|
||||
import type { PrefabLoot } from '@/types'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -76,157 +81,273 @@ const ungroupedItems = computed(() => {
|
||||
...(data as any),
|
||||
}))
|
||||
})
|
||||
|
||||
// Computed boolean for the Switch v-model
|
||||
const isEnabled = computed({
|
||||
get: () => containerData.value?.Enabled ?? true,
|
||||
set: () => toggleEnabled(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- Container Header -->
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-xl p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-lg font-semibold text-neutral-100">{{ containerName }}</h2>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="containerData?.Enabled ?? true"
|
||||
@change="toggleEnabled"
|
||||
class="rounded bg-neutral-800 border-neutral-600 text-oxide-500 focus:ring-oxide-500"
|
||||
/>
|
||||
<span class="text-sm text-neutral-400">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Settings2 class="w-4 h-4 text-neutral-500" />
|
||||
<span class="text-xs text-neutral-500 font-mono">{{ containerKey.split('/').pop() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lie-root">
|
||||
<!-- Container settings panel -->
|
||||
<Panel :title="containerName">
|
||||
<template #actions>
|
||||
<Badge tone="neutral" mono class="lie-prefab">
|
||||
{{ containerKey.split('/').pop() }}
|
||||
</Badge>
|
||||
<Switch v-model="isEnabled" label="Enabled" size="sm" />
|
||||
</template>
|
||||
|
||||
<!-- Item Settings -->
|
||||
<div class="grid grid-cols-4 gap-3" v-if="containerData">
|
||||
<div>
|
||||
<label class="block text-xs text-neutral-500 mb-1">Items Min</label>
|
||||
<!-- Item settings grid -->
|
||||
<div v-if="containerData" class="lie-settings">
|
||||
<div class="lie-setting">
|
||||
<label class="lie-setting__label">Items min</label>
|
||||
<input
|
||||
type="number"
|
||||
:value="containerData.ItemSettings?.ItemsMin ?? 1"
|
||||
@input="updateSettings('ItemsMin', Number(($event.target as HTMLInputElement).value))"
|
||||
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-sm text-neutral-200"
|
||||
class="cc-num-input"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-neutral-500 mb-1">Items Max</label>
|
||||
<div class="lie-setting">
|
||||
<label class="lie-setting__label">Items max</label>
|
||||
<input
|
||||
type="number"
|
||||
:value="containerData.ItemSettings?.ItemsMax ?? 6"
|
||||
@input="updateSettings('ItemsMax', Number(($event.target as HTMLInputElement).value))"
|
||||
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-sm text-neutral-200"
|
||||
class="cc-num-input"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-neutral-500 mb-1">Min Scrap</label>
|
||||
<div class="lie-setting">
|
||||
<label class="lie-setting__label">Min scrap</label>
|
||||
<input
|
||||
type="number"
|
||||
:value="containerData.ItemSettings?.MinScrap ?? 0"
|
||||
@input="updateSettings('MinScrap', Number(($event.target as HTMLInputElement).value))"
|
||||
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-sm text-neutral-200"
|
||||
class="cc-num-input"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-neutral-500 mb-1">Max Scrap</label>
|
||||
<div class="lie-setting">
|
||||
<label class="lie-setting__label">Max scrap</label>
|
||||
<input
|
||||
type="number"
|
||||
:value="containerData.ItemSettings?.MaxScrap ?? 0"
|
||||
@input="updateSettings('MaxScrap', Number(($event.target as HTMLInputElement).value))"
|
||||
class="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-sm text-neutral-200"
|
||||
class="cc-num-input"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="lie-unconfigured">
|
||||
Container not yet configured. Add an item to initialise its settings.
|
||||
</p>
|
||||
</Panel>
|
||||
|
||||
<!-- Ungrouped Items Table -->
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-xl p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-sm font-semibold text-neutral-300">Ungrouped Items</h3>
|
||||
<button
|
||||
@click="emit('add-item')"
|
||||
class="flex items-center gap-1 px-3 py-1.5 bg-oxide-500/10 text-oxide-400 rounded-lg hover:bg-oxide-500/20 text-sm"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" />
|
||||
Add Item
|
||||
</button>
|
||||
</div>
|
||||
<!-- Ungrouped items panel -->
|
||||
<Panel title="Ungrouped items" :flush-body="ungroupedItems.length > 0">
|
||||
<template #actions>
|
||||
<Button size="sm" variant="outline" icon="plus" @click="emit('add-item')">
|
||||
Add item
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<div v-if="ungroupedItems.length > 0" class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<div v-if="ungroupedItems.length > 0" class="lie-table-wrap">
|
||||
<table class="lie-table">
|
||||
<thead>
|
||||
<tr class="border-b border-neutral-800">
|
||||
<th class="text-left py-2 px-2 text-neutral-500 font-medium">Item</th>
|
||||
<th class="text-center py-2 px-2 text-neutral-500 font-medium w-20">Min</th>
|
||||
<th class="text-center py-2 px-2 text-neutral-500 font-medium w-20">Max</th>
|
||||
<th class="text-center py-2 px-2 text-neutral-500 font-medium w-24">Prob %</th>
|
||||
<th class="w-10"></th>
|
||||
<tr>
|
||||
<th class="lie-th">Item</th>
|
||||
<th class="lie-th lie-th--num">Min</th>
|
||||
<th class="lie-th lie-th--num">Max</th>
|
||||
<th class="lie-th lie-th--num">Prob %</th>
|
||||
<th class="lie-th lie-th--action"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in ungroupedItems"
|
||||
:key="item.shortname"
|
||||
class="border-b border-neutral-800/50 hover:bg-neutral-800/30"
|
||||
class="lie-tr"
|
||||
>
|
||||
<td class="py-2 px-2">
|
||||
<div>
|
||||
<span class="text-neutral-200">{{ item.name }}</span>
|
||||
<span class="text-neutral-600 text-xs ml-2">{{ item.shortname }}</span>
|
||||
</div>
|
||||
<td class="lie-td">
|
||||
<span class="lie-item-name">{{ item.name }}</span>
|
||||
<span class="lie-item-short">{{ item.shortname }}</span>
|
||||
</td>
|
||||
<td class="py-2 px-2">
|
||||
<td class="lie-td lie-td--num">
|
||||
<input
|
||||
type="number"
|
||||
:value="item.Min"
|
||||
@input="updateItemField(item.shortname, 'Min', 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"
|
||||
class="cc-num-input cc-num-input--center"
|
||||
min="0"
|
||||
/>
|
||||
</td>
|
||||
<td class="py-2 px-2">
|
||||
<td class="lie-td lie-td--num">
|
||||
<input
|
||||
type="number"
|
||||
:value="item.Max"
|
||||
@input="updateItemField(item.shortname, 'Max', 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"
|
||||
class="cc-num-input cc-num-input--center"
|
||||
min="0"
|
||||
/>
|
||||
</td>
|
||||
<td class="py-2 px-2">
|
||||
<td class="lie-td lie-td--num">
|
||||
<input
|
||||
type="number"
|
||||
:value="item.Probability ?? 100"
|
||||
@input="updateItemField(item.shortname, 'Probability', 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"
|
||||
class="cc-num-input cc-num-input--center"
|
||||
min="0"
|
||||
max="100"
|
||||
/>
|
||||
</td>
|
||||
<td class="py-2 px-2">
|
||||
<button
|
||||
<td class="lie-td lie-td--action">
|
||||
<IconButton
|
||||
icon="trash-2"
|
||||
variant="danger"
|
||||
size="sm"
|
||||
label="Remove item"
|
||||
@click="removeItem(item.shortname)"
|
||||
class="text-neutral-600 hover:text-red-400 transition-colors"
|
||||
>
|
||||
<Trash2 class="w-4 h-4" />
|
||||
</button>
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-6 text-neutral-500 text-sm">
|
||||
No items configured for this container.
|
||||
<button @click="emit('add-item')" class="text-oxide-400 hover:underline ml-1">Add one</button>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
v-else
|
||||
icon="package"
|
||||
title="No items configured"
|
||||
description="Add items to configure what this container can spawn."
|
||||
>
|
||||
<template #action>
|
||||
<Button size="sm" variant="outline" icon="plus" @click="emit('add-item')">
|
||||
Add item
|
||||
</Button>
|
||||
</template>
|
||||
</EmptyState>
|
||||
</Panel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lie-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* Badge for prefab key */
|
||||
.lie-prefab {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.lie-unconfigured {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Settings grid */
|
||||
.lie-settings {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.lie-setting {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
.lie-setting__label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.lie-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.lie-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.lie-th {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
color: var(--text-tertiary);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lie-th--num {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
}
|
||||
.lie-th--action {
|
||||
width: 40px;
|
||||
}
|
||||
.lie-tr {
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
transition: var(--transition-colors);
|
||||
}
|
||||
.lie-tr:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.lie-tr:hover {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
.lie-td {
|
||||
padding: 8px 12px;
|
||||
color: var(--text-primary);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.lie-td--num {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
}
|
||||
.lie-td--action {
|
||||
width: 40px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.lie-item-name {
|
||||
display: block;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.lie-item-short {
|
||||
display: block;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Shared number input */
|
||||
.cc-num-input {
|
||||
width: 100%;
|
||||
background: var(--surface-inset);
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--ring-default);
|
||||
padding: 5px 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
transition: var(--transition-colors);
|
||||
}
|
||||
.cc-num-input:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent), var(--glow-accent-sm);
|
||||
}
|
||||
.cc-num-input--center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user