update
This commit is contained in:
1 parent
7bf9667007
commit
c589e389df
20 files changed
+1814
-29
No files matched your search
@@ -0,0 +1,274 @@
|
||||
<script setup>
|
||||
// Der Zeitstrahl der Aufnahme rund um die Marke. Ein Balken zeigt das
|
||||
// Fenster des Clips; an seinen Enden zieht man Anfang und Ende, in der
|
||||
// Mitte schiebt man das ganze Fenster. Zeiten in Sekunden relativ zur
|
||||
// Marke: 0 ist der Moment, an dem C gedrückt wurde.
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
// Stücke der Aufnahme: [{ von, bis }] relativ zur Marke
|
||||
segmente: { type: Array, default: () => [] },
|
||||
maxVorher: { type: Number, default: 600 },
|
||||
minDauer: { type: Number, default: 3 },
|
||||
maxDauer: { type: Number, default: 180 },
|
||||
// Uhrzeit der Marke, für die Beschriftung
|
||||
marke: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const vorher = defineModel('vorher', { type: Number, default: 30 })
|
||||
const dauer = defineModel('dauer', { type: Number, default: 35 })
|
||||
const emit = defineEmits(['ende'])
|
||||
|
||||
// Der sichtbare Ausschnitt des Strahls, zoombar
|
||||
const ZOOMS = [120, 300, 900, 1800, 7200, 14400]
|
||||
const zoom = ref(1)
|
||||
const mitte = ref(0)
|
||||
const spanne = computed(() => ZOOMS[zoom.value])
|
||||
const links = computed(() => mitte.value - spanne.value / 2)
|
||||
const rechts = computed(() => mitte.value + spanne.value / 2)
|
||||
|
||||
const strahl = ref(null)
|
||||
|
||||
function prozent(t) {
|
||||
return ((t - links.value) / spanne.value) * 100
|
||||
}
|
||||
|
||||
const start = computed(() => -vorher.value)
|
||||
const ende = computed(() => -vorher.value + dauer.value)
|
||||
|
||||
// Wo die Aufnahme überhaupt Material hat
|
||||
const grenzen = computed(() => {
|
||||
if (!props.segmente.length) return { von: -props.maxVorher, bis: props.maxDauer }
|
||||
return {
|
||||
von: Math.min(...props.segmente.map((s) => s.von)),
|
||||
bis: Math.max(...props.segmente.map((s) => s.bis)),
|
||||
}
|
||||
})
|
||||
|
||||
function zoomen(schritt) {
|
||||
zoom.value = Math.max(0, Math.min(ZOOMS.length - 1, zoom.value + schritt))
|
||||
mitte.value = (start.value + ende.value) / 2
|
||||
}
|
||||
|
||||
// Beschriftung: runde Schritte, etwa sechs bis acht Striche
|
||||
const striche = computed(() => {
|
||||
const schritt = [10, 30, 60, 300, 900, 1800][zoom.value]
|
||||
const aus = []
|
||||
for (let t = Math.ceil(links.value / schritt) * schritt; t <= rechts.value; t += schritt) {
|
||||
aus.push({ t, text: uhrzeit(t) })
|
||||
}
|
||||
return aus
|
||||
})
|
||||
|
||||
function uhrzeit(t) {
|
||||
if (!props.marke) return `${t >= 0 ? '+' : '−'}${Math.abs(t)} s`
|
||||
const d = new Date(new Date(props.marke).getTime() + t * 1000)
|
||||
return d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||
}
|
||||
|
||||
// ── Ziehen ────────────────────────────────────────────────────────────
|
||||
let griff = null
|
||||
let ausgang = null
|
||||
|
||||
function anfassen(e, was) {
|
||||
griff = was
|
||||
ausgang = { x: e.clientX, vorher: vorher.value, dauer: dauer.value }
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
}
|
||||
|
||||
function ziehen(e) {
|
||||
if (!griff || !strahl.value) return
|
||||
const dt = ((e.clientX - ausgang.x) / strahl.value.clientWidth) * spanne.value
|
||||
const g = grenzen.value
|
||||
if (griff === 'links') {
|
||||
// Der Anfang wandert, das Ende bleibt stehen
|
||||
const altesEnde = -ausgang.vorher + ausgang.dauer
|
||||
let neuerStart = -ausgang.vorher + dt
|
||||
neuerStart = Math.max(g.von, -props.maxVorher, neuerStart)
|
||||
neuerStart = Math.min(
|
||||
altesEnde - props.minDauer,
|
||||
Math.max(altesEnde - props.maxDauer, neuerStart),
|
||||
)
|
||||
vorher.value = Math.round(-neuerStart)
|
||||
dauer.value = Math.round(altesEnde - neuerStart)
|
||||
} else if (griff === 'rechts') {
|
||||
let neuesEnde = -ausgang.vorher + ausgang.dauer + dt
|
||||
neuesEnde = Math.min(g.bis, neuesEnde)
|
||||
const d = Math.max(props.minDauer, Math.min(props.maxDauer, neuesEnde + ausgang.vorher))
|
||||
dauer.value = Math.round(d)
|
||||
} else {
|
||||
// Das ganze Fenster verschieben
|
||||
let neuerStart = -ausgang.vorher + dt
|
||||
neuerStart = Math.max(g.von, -props.maxVorher, Math.min(g.bis - ausgang.dauer, neuerStart))
|
||||
vorher.value = Math.round(-neuerStart)
|
||||
}
|
||||
}
|
||||
|
||||
function loslassen(e) {
|
||||
if (!griff) return
|
||||
griff = null
|
||||
e.currentTarget.releasePointerCapture?.(e.pointerId)
|
||||
emit('ende')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="trimmer">
|
||||
<div class="kopf">
|
||||
<span class="fenster">
|
||||
{{ uhrzeit(start) }} – {{ uhrzeit(ende) }} · <b>{{ dauer }} s</b>
|
||||
</span>
|
||||
<span class="zoom">
|
||||
<button type="button" :disabled="zoom === 0" title="Näher" @click="zoomen(-1)">+</button>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="zoom === ZOOMS.length - 1"
|
||||
title="Weiter"
|
||||
@click="zoomen(1)"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="strahl"
|
||||
class="strahl"
|
||||
@pointermove="ziehen"
|
||||
@pointerup="loslassen"
|
||||
@pointercancel="loslassen"
|
||||
>
|
||||
<!-- Wo Aufnahme liegt -->
|
||||
<span
|
||||
v-for="(s, i) in segmente"
|
||||
:key="i"
|
||||
class="aufnahme"
|
||||
:style="{ left: prozent(s.von) + '%', width: prozent(s.bis) - prozent(s.von) + '%' }"
|
||||
></span>
|
||||
|
||||
<!-- Die Marke: der Moment, an dem C gedrückt wurde -->
|
||||
<span class="marke" :style="{ left: prozent(0) + '%' }" title="Marke"></span>
|
||||
|
||||
<!-- Das Fenster des Clips mit seinen Griffen -->
|
||||
<span
|
||||
class="bereich"
|
||||
:style="{ left: prozent(start) + '%', width: prozent(ende) - prozent(start) + '%' }"
|
||||
@pointerdown="anfassen($event, 'mitte')"
|
||||
>
|
||||
<span class="griff links" @pointerdown.stop="anfassen($event, 'links')"></span>
|
||||
<span class="griff rechts" @pointerdown.stop="anfassen($event, 'rechts')"></span>
|
||||
</span>
|
||||
|
||||
<span v-for="s in striche" :key="s.t" class="strich" :style="{ left: prozent(s.t) + '%' }">
|
||||
<span class="text">{{ s.text }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.trimmer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.kopf {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.fenster b {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.zoom button {
|
||||
width: 1.7rem;
|
||||
height: 1.7rem;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.zoom button:disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.strahl {
|
||||
position: relative;
|
||||
height: 3.4rem;
|
||||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.aufnahme {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
height: 1.6rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.marke {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
margin-left: -1px;
|
||||
background: #f5c518;
|
||||
}
|
||||
|
||||
.bereich {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
height: 1.6rem;
|
||||
background: rgba(229, 9, 20, 0.35);
|
||||
border: 2px solid var(--accent);
|
||||
border-radius: 4px;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.griff {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
bottom: -2px;
|
||||
width: 0.7rem;
|
||||
background: var(--accent);
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.griff.links {
|
||||
left: -2px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.griff.rechts {
|
||||
right: -2px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.strich {
|
||||
position: absolute;
|
||||
top: 2.2rem;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: var(--line);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
top: 0.3rem;
|
||||
left: 0.25rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.68rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user