132 lines
5.2 KiB
Vue
132 lines
5.2 KiB
Vue
<script setup>
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { Package, Plus, Trash2, X, Terminal, Sparkles } from 'lucide-vue-next'
|
|
import { getPackCmds, setPackCmds } from '../packCmds'
|
|
import { call } from '../api'
|
|
|
|
const props = defineProps({
|
|
open: Boolean,
|
|
scope: { type: String, required: true }, // lp | proj
|
|
targetId: { type: [Number, String], required: true },
|
|
title: { type: String, default: '' },
|
|
dir: { type: String, default: '' }
|
|
})
|
|
const emit = defineEmits(['close', 'saved'])
|
|
const { t } = useI18n()
|
|
const rows = ref([])
|
|
const suggest = ref({ kind: '', label: '', items: [] })
|
|
const suggestLoading = ref(false)
|
|
|
|
watch(() => [props.open, props.scope, props.targetId, props.dir], async () => {
|
|
if (!props.open) return
|
|
const list = getPackCmds(props.scope, props.targetId)
|
|
rows.value = list.length
|
|
? list.map(x => ({ name: x.name || '', cmd: x.cmd || '' }))
|
|
: [{ name: '', cmd: '' }]
|
|
suggest.value = { kind: '', label: '', items: [] }
|
|
if (!props.dir) return
|
|
suggestLoading.value = true
|
|
try {
|
|
suggest.value = await call('SuggestPackCommands', props.dir) || { kind: '', label: '', items: [] }
|
|
} catch {
|
|
suggest.value = { kind: '', label: '', items: [] }
|
|
}
|
|
suggestLoading.value = false
|
|
}, { immediate: true })
|
|
|
|
const canSave = computed(() => rows.value.some(r => String(r.cmd || '').trim()))
|
|
const validCount = computed(() => rows.value.filter(r => String(r.cmd || '').trim()).length)
|
|
const suggestItems = computed(() => suggest.value?.items || [])
|
|
|
|
function addRow() {
|
|
rows.value.push({ name: '', cmd: '' })
|
|
}
|
|
function removeRow(i) {
|
|
rows.value.splice(i, 1)
|
|
if (!rows.value.length) rows.value.push({ name: '', cmd: '' })
|
|
}
|
|
function applySuggest(item) {
|
|
if (!item?.cmd) return
|
|
const empty = rows.value.findIndex(r => !String(r.cmd || '').trim())
|
|
const row = { name: item.name || '', cmd: item.cmd }
|
|
if (empty >= 0) rows.value[empty] = row
|
|
else rows.value.push(row)
|
|
}
|
|
function applyAllSuggest() {
|
|
for (const it of suggestItems.value) applySuggest(it)
|
|
}
|
|
function save() {
|
|
setPackCmds(props.scope, props.targetId, rows.value)
|
|
emit('saved')
|
|
emit('close')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<div v-if="open" class="overlay" @click.self="emit('close')">
|
|
<section class="modal pack-cmds-modal" @click.stop>
|
|
<header class="pack-cmds-head">
|
|
<div class="pack-cmds-brand">
|
|
<span class="pack-cmds-ico"><Package /></span>
|
|
<div>
|
|
<h2>{{ title || t('packCmdsTitle') }}</h2>
|
|
<p>{{ t('packCmdsHint') }}</p>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="nm-close" :title="t('close')" @click="emit('close')"><X /></button>
|
|
</header>
|
|
|
|
<div class="pack-cmds-body">
|
|
<div v-if="dir" class="pack-suggest">
|
|
<div class="pack-suggest-top">
|
|
<b><Sparkles />{{ t('packSuggestTitle') }}</b>
|
|
<em v-if="suggestLoading">{{ t('packSuggestLoading') }}</em>
|
|
<em v-else-if="suggest.label" class="pack-suggest-kind">{{ suggest.label }}</em>
|
|
<button v-if="suggestItems.length" type="button" class="pack-suggest-all" @click="applyAllSuggest">{{ t('packSuggestAll') }}</button>
|
|
</div>
|
|
<div v-if="suggestItems.length" class="pack-suggest-chips">
|
|
<button v-for="(it, i) in suggestItems" :key="i" type="button" :title="it.cmd" @click="applySuggest(it)">
|
|
<span>{{ it.name }}</span>
|
|
<code>{{ it.cmd }}</code>
|
|
</button>
|
|
</div>
|
|
<p v-else-if="!suggestLoading" class="pack-suggest-empty">{{ t('packSuggestEmpty') }}</p>
|
|
</div>
|
|
|
|
<div class="pack-cmds-meta">
|
|
<b>{{ t('packCmdsList') }}</b>
|
|
<em>{{ t('packCmdsCount', { n: validCount }) }}</em>
|
|
</div>
|
|
<div class="pack-cmds-list">
|
|
<article v-for="(r, i) in rows" :key="i" class="pack-cmd-card">
|
|
<div class="pack-cmd-card-top">
|
|
<span class="pack-cmd-idx">{{ i + 1 }}</span>
|
|
<button type="button" class="pack-cmd-del" :title="t('delete')" @click="removeRow(i)"><Trash2 /></button>
|
|
</div>
|
|
<label class="pack-cmd-labeled">
|
|
<span>{{ t('packCmdNameLabel') }}</span>
|
|
<input v-model.trim="r.name" :placeholder="t('packCmdNamePh')" />
|
|
</label>
|
|
<label class="pack-cmd-labeled">
|
|
<span>{{ t('packCmdCmdLabel') }}</span>
|
|
<span class="pack-cmd-field">
|
|
<Terminal />
|
|
<input v-model.trim="r.cmd" class="pack-cmd-term" spellcheck="false" :placeholder="t('packCmdPh')" />
|
|
</span>
|
|
</label>
|
|
</article>
|
|
</div>
|
|
<button type="button" class="pack-cmd-add" @click="addRow"><Plus />{{ t('packCmdAdd') }}</button>
|
|
</div>
|
|
|
|
<footer class="pack-cmds-foot">
|
|
<button type="button" class="btn secondary" @click="emit('close')">{{ t('cancel') }}</button>
|
|
<button type="button" class="btn primary" :disabled="!canSave" @click="save">{{ t('save') }}</button>
|
|
</footer>
|
|
</section>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|