update
This commit is contained in:
94
frontend/src/components/OrdnerBrowser.vue
Normal file
94
frontend/src/components/OrdnerBrowser.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { fetchOrdner, projektHinzufuegen } from '../api.js'
|
||||
|
||||
const emit = defineEmits(['schliessen', 'hinzugefuegt'])
|
||||
|
||||
const pfad = ref('')
|
||||
const eingabe = ref('')
|
||||
const eltern = ref(null)
|
||||
const ordner = ref([])
|
||||
const fehler = ref('')
|
||||
|
||||
async function lade(ziel) {
|
||||
try {
|
||||
const d = await fetchOrdner(ziel)
|
||||
pfad.value = d.pfad
|
||||
eingabe.value = d.pfad
|
||||
eltern.value = d.eltern
|
||||
ordner.value = d.ordner
|
||||
fehler.value = ''
|
||||
} catch (e) {
|
||||
fehler.value = e.message
|
||||
}
|
||||
}
|
||||
|
||||
async function uebernehmen() {
|
||||
try {
|
||||
await projektHinzufuegen(pfad.value)
|
||||
emit('hinzugefuegt')
|
||||
} catch (e) {
|
||||
fehler.value = e.message
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => lade(''))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="schleier" @click.self="$emit('schliessen')" @keydown.esc="$emit('schliessen')">
|
||||
<div class="dialog">
|
||||
<div class="kopf">
|
||||
<h3>Projekt-Ordner wählen</h3>
|
||||
<button class="zu" @click="$emit('schliessen')">✕</button>
|
||||
</div>
|
||||
<input v-model="eingabe" spellcheck="false"
|
||||
@keydown.enter="lade(eingabe)" title="Pfad eintippen, Enter lädt" />
|
||||
<div class="liste">
|
||||
<a v-if="eltern" href="#" class="eintrag" @click.prevent="lade(eltern)">↰ ..</a>
|
||||
<a v-for="o in ordner" :key="o.pfad" href="#" class="eintrag"
|
||||
@click.prevent="lade(o.pfad)">
|
||||
<span class="ordner-icon">▸</span>{{ o.name }}
|
||||
<span v-if="o.hat_planer" class="badge gruen">.planer</span>
|
||||
</a>
|
||||
<p v-if="!ordner.length" class="leer">keine Unterordner</p>
|
||||
</div>
|
||||
<p v-if="fehler" class="fehler">{{ fehler }}</p>
|
||||
<div class="fuss">
|
||||
<code class="gewaehlt">{{ pfad }}</code>
|
||||
<button class="primaer" @click="uebernehmen">Als Projekt übernehmen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.schleier {
|
||||
position: fixed; inset: 0; background: rgb(0 0 0 / 0.4); z-index: 50;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.dialog {
|
||||
background: var(--panel); border: 1px solid var(--rand); border-radius: 12px;
|
||||
width: min(560px, 92vw); max-height: 80vh; padding: 1rem 1.2rem;
|
||||
display: flex; flex-direction: column; gap: 0.7rem;
|
||||
}
|
||||
.kopf { display: flex; align-items: center; }
|
||||
.kopf h3 { margin: 0; font-size: 1rem; flex: 1; }
|
||||
.zu { border: none; background: none; color: var(--dezent); padding: 0.2rem; }
|
||||
input {
|
||||
font: inherit; color: var(--text); background: var(--bg);
|
||||
border: 1px solid var(--rand); border-radius: 6px; padding: 0.4rem 0.6rem;
|
||||
}
|
||||
.liste { overflow-y: auto; min-height: 8rem; display: flex; flex-direction: column; }
|
||||
.eintrag {
|
||||
display: flex; align-items: center; gap: 0.45rem;
|
||||
color: var(--text); text-decoration: none; font-size: 0.92rem;
|
||||
padding: 0.3rem 0.5rem; border-radius: 6px;
|
||||
}
|
||||
.eintrag:hover { background: var(--bg); }
|
||||
.ordner-icon { color: var(--dezent); }
|
||||
.leer { color: var(--dezent); font-size: 0.85rem; margin: 0.3rem 0.5rem; }
|
||||
.fehler { color: var(--rot); font-size: 0.85rem; margin: 0; }
|
||||
.fuss { display: flex; align-items: center; gap: 0.8rem; border-top: 1px solid var(--rand); padding-top: 0.7rem; }
|
||||
.gewaehlt { flex: 1; font-size: 0.8rem; overflow-wrap: anywhere; color: var(--dezent); }
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { starteScan } from '../api.js'
|
||||
import { projektEntfernen, starteScan } from '../api.js'
|
||||
|
||||
const props = defineProps({ projekt: { type: Object, required: true } })
|
||||
const emit = defineEmits(['oeffne', 'aktualisiert'])
|
||||
@@ -16,6 +16,19 @@ async function scan(art) {
|
||||
}
|
||||
}
|
||||
|
||||
async function entfernen() {
|
||||
const ok = confirm(
|
||||
`„${props.projekt.name}" aus der Projektliste entfernen?\n.planer/ im Repo bleibt erhalten.`)
|
||||
if (!ok) return
|
||||
meldung.value = ''
|
||||
try {
|
||||
await projektEntfernen(props.projekt.name)
|
||||
emit('aktualisiert')
|
||||
} catch (e) {
|
||||
meldung.value = e.message
|
||||
}
|
||||
}
|
||||
|
||||
function zeit(ts) {
|
||||
if (!ts) return 'nie gescannt'
|
||||
return new Date(ts).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' })
|
||||
@@ -39,10 +52,14 @@ function zeit(ts) {
|
||||
{{ projekt.pfad }} · letzter Scan: {{ zeit(projekt.letzter_scan_ts) }}
|
||||
</p>
|
||||
<p class="detail drift" v-if="projekt.drift?.length">veraltet: {{ projekt.drift.join(', ') }}</p>
|
||||
<div class="aktionen" v-if="!projekt.fehler">
|
||||
<button v-if="!projekt.hat_planer" class="primaer" :disabled="projekt.scan_laeuft"
|
||||
@click="scan('scan')">Scannen</button>
|
||||
<button v-else :disabled="projekt.scan_laeuft" @click="scan('rescan')">Re-Scan</button>
|
||||
<div class="aktionen">
|
||||
<template v-if="!projekt.fehler">
|
||||
<button v-if="!projekt.hat_planer" class="primaer" :disabled="projekt.scan_laeuft"
|
||||
@click="scan('scan')">Scannen</button>
|
||||
<button v-else :disabled="projekt.scan_laeuft" @click="scan('rescan')">Re-Scan</button>
|
||||
</template>
|
||||
<button class="entfernen" :disabled="projekt.scan_laeuft" @click="entfernen"
|
||||
title=".planer/ im Repo bleibt erhalten">Entfernen</button>
|
||||
</div>
|
||||
<p v-if="meldung" class="meldung">{{ meldung }}</p>
|
||||
</div>
|
||||
@@ -61,6 +78,8 @@ function zeit(ts) {
|
||||
a.name:hover { color: var(--akzent); }
|
||||
.detail { color: var(--dezent); font-size: 0.85rem; margin: 0.3rem 0 0; overflow-wrap: anywhere; }
|
||||
.drift { color: var(--gelb); }
|
||||
.aktionen { margin-top: 0.7rem; }
|
||||
.aktionen { margin-top: 0.7rem; display: flex; gap: 0.5rem; }
|
||||
.entfernen { margin-left: auto; color: var(--dezent); }
|
||||
.entfernen:hover:not(:disabled) { color: var(--rot); border-color: var(--rot); }
|
||||
.meldung { color: var(--rot); font-size: 0.85rem; margin: 0.5rem 0 0; }
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fetchProjekte } from '../api.js'
|
||||
import { usePolling } from '../composables/usePolling.js'
|
||||
import ProjektKarte from './ProjektKarte.vue'
|
||||
|
||||
defineEmits(['oeffne'])
|
||||
defineEmits(['oeffne', 'geaendert'])
|
||||
|
||||
const projekte = ref([])
|
||||
const fehler = ref('')
|
||||
@@ -32,7 +32,8 @@ onMounted(async () => {
|
||||
</p>
|
||||
<div class="liste">
|
||||
<ProjektKarte v-for="p in projekte" :key="p.name" :projekt="p"
|
||||
@oeffne="$emit('oeffne', p.name)" @aktualisiert="laden(); poll.start()" />
|
||||
@oeffne="$emit('oeffne', p.name)"
|
||||
@aktualisiert="laden(); poll.start(); $emit('geaendert')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user