Commit 9b9351d0 by Jonathan Thomas

Refactored default image length to a single function. Only show Export button…

Refactored default image length to a single function. Only show Export button when clips are added. Integrated 'move up/down' actions. Fixed clip selection to only include the 1 clip. Refactored clip length (seconds) to Vuex. Set correct position and duration of Clip API objects. Preview now supports clip start/end and jumps position to the correct playback spot.
parent 21d1ad5c
<template>
<div class="row mb-3 gx-2 p-2 scrolling-container">
<h3>Clips <button v-if="clips" type="button" class="btn btn-danger export-btn">Export</button></h3>
<h3 v-if="thumbnailedClips.length > 0">Clips <button type="button" class="btn btn-danger export-btn">Export {{clips.length}} Clips</button></h3>
<div class="col-12">
<div class="row gy-2 gx-2 mb-2" v-for="(clip, index) in thumbnailedClips" :key="clip.id">
......@@ -13,8 +13,8 @@
<path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
</svg>
<ul class="dropdown-menu small-dropdown">
<li><a class="dropdown-item" href="#">Move Up</a></li>
<li><a class="dropdown-item" href="#">Move Down</a></li>
<li><a v-if="index > 0" class="dropdown-item" href="#" @click="moveClip('up', clip, index, index - 1)">Move Up</a></li>
<li><a v-if="index < clips.length - 1" class="dropdown-item" href="#" @click="moveClip('down', clip, index, index + 1)">Move Down</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" @click="deleteClipBtn(clip.id)">Delete</a></li>
</ul>
......@@ -34,7 +34,7 @@
</template>
<script>
import {mapActions, mapState, mapMutations} from "vuex"
import {mapActions, mapState, mapMutations, mapGetters} from "vuex"
export default {
name: "Clips.vue",
......@@ -46,27 +46,56 @@ export default {
deleteClipBtn(clip_id) {
this.deleteClip(clip_id)
},
moveClip(direction, clipObj, start_index, dest_index) {
let local_clips = [...this.thumbnailedClips]
let reordered_clips = this.reorder(local_clips, start_index, dest_index)
let pos = 0.0
for (let clip of reordered_clips) {
clip.position = pos
this.editClip(clip)
pos += (clip.end - clip.start)
}
},
reorder(array, sourceIndex, destinationIndex) {
const smallerIndex = Math.min(sourceIndex, destinationIndex)
const largerIndex = Math.max(sourceIndex, destinationIndex)
return [
...array.slice(0, smallerIndex),
...(sourceIndex < destinationIndex
? array.slice(smallerIndex + 1, largerIndex + 1)
: []),
array[sourceIndex],
...(sourceIndex > destinationIndex
? array.slice(smallerIndex, largerIndex)
: []),
...array.slice(largerIndex + 1),
]
},
toggleSelection(clipObj) {
if (clipObj.fileObj != this.preview.file) {
this.setPreviewFile(clipObj.fileObj)
if (clipObj != this.preview.clip) {
this.setPreviewClip(clipObj)
} else {
this.setPreviewFile(null)
this.setPreviewClip(null)
}
},
getSelectedClass(clipObj) {
if (this.preview.file && clipObj.fileObj.id == this.preview.file.id) {
if (this.preview.clip && clipObj.id == this.preview.clip.id) {
return 'selected'
} else {
return ''
}
},
...mapActions(['loadClips', 'createClip', 'deleteClip']),
...mapMutations((['setPreviewFile', 'setClips']))
...mapActions(['loadClips', 'createClip', 'deleteClip', 'editClip']),
...mapMutations((['setPreviewClip', 'setClips']))
},
computed: {
thumbnailedClips() {
return this.clips.filter(clip => clip.thumbnail)
return this.clips.filter(clip => clip.thumbnail).sort((a, b) => a.position - b.position)
},
...mapGetters(['totalClipDuration']),
...mapState(['clips', 'preview', 'scrollToClip'])
},
watch: {
......
......@@ -39,7 +39,7 @@
</svg>
</div>
<div ref="clip" class="clip" :class="clipSizeClass" :style="{left: preview.start * 100.0 + '%', width: `calc(${(preview.end - preview.start) * 100.0}%)`}">
{{ ((preview.end - preview.start) * preview.file.json.duration).toFixed(1) }} Seconds
{{ preview.length.toFixed(1) }} Seconds
</div>
</div>
</div>
......@@ -61,7 +61,7 @@
</template>
<script>
import {mapState, mapActions, mapMutations} from "vuex";
import {mapState, mapActions, mapGetters, mapMutations} from "vuex";
export default {
name: "Preview.vue",
......@@ -79,7 +79,7 @@ export default {
async createClipBtn() {
let data = {
"file": this.preview.file.url,
"position": 0.0,
"position": this.totalClipDuration,
"start": this.preview.start * this.preview.file.json.duration,
"end": this.preview.end * this.preview.file.json.duration,
"layer": 1,
......@@ -204,6 +204,7 @@ export default {
},
computed: {
...mapState(['preview']),
...mapGetters(['totalClipDuration']),
hasPreviewFile() {
if (this.preview.file) {
return true
......@@ -228,9 +229,12 @@ export default {
this.is_paused = true
this.$refs.video.pause()
this.$refs.video.load()
if (this.preview.clip) {
this.$refs.video.currentTime = this.preview.clip.start
} else {
this.$refs.video.currentTime = 0.0
}
}
this.setPreview({start: 0.0, end: 1.0})
this.setPreviewPosition(0.0)
},
},
updated() {
......
......@@ -14,4 +14,12 @@ instance.interceptors.request.use(function (config) {
return config;
});
export { instance };
\ No newline at end of file
function fixImageDuration(duration) {
if (duration == 3600) {
return 30
} else {
return duration
}
}
export { instance, fixImageDuration };
\ No newline at end of file
import { createStore } from 'vuex'
import { v4 as uuidv4 } from "uuid"
import { instance } from "./axios"
import { instance, fixImageDuration } from "./axios"
export default createStore({
......@@ -17,9 +17,11 @@ export default createStore({
clip: null,
start: 0.0,
end: 1.0,
position: 0.0
position: 0.0,
length: 0.0
},
scrollToClip: null,
default_image_length: 30
},
mutations: {
......@@ -101,20 +103,43 @@ export default createStore({
setPreview(state, payload) {
state.preview.start = payload.start
state.preview.end = payload.end
if (state.preview.file) {
state.preview.length = (state.preview.end - state.preview.start) *
fixImageDuration(state.preview.file.json.duration)
}
},
setPreviewPosition(state, position) {
state.preview.position = position
},
setPreviewFile(state, file) {
state.preview.file = file
state.preview.clip = null
state.preview.start = 0.0
state.preview.end = 1.0
if (state.preview.file) {
state.preview.length = (state.preview.end - state.preview.start) *
fixImageDuration(state.preview.file.json.duration)
}
},
setPreviewClip(state, clipObj) {
state.preview.clip = clipObj
state.preview.start = clipObj.start
state.preview.end = clipObj.end
state.preview.position = clipObj.start
if (clipObj && clipObj.fileObj) {
state.preview.start = clipObj.start / fixImageDuration(clipObj.fileObj.json.duration)
state.preview.end = clipObj.end / fixImageDuration(clipObj.fileObj.json.duration)
state.preview.position = clipObj.start / fixImageDuration(clipObj.fileObj.json.duration)
if (state.preview.clip.fileObj) {
state.preview.file = state.preview.clip.fileObj
}
} else {
state.preview.file = null
state.preview.start = 0.0
state.preview.end = 1.0
}
if (state.preview.file) {
state.preview.length = (state.preview.end - state.preview.start) *
fixImageDuration(state.preview.file.json.duration)
}
},
addUpload(state, upload) {
state.uploads.push(upload)
......@@ -132,7 +157,10 @@ export default createStore({
},
},
getters: {
isAuthenticated: state => !!state.user
isAuthenticated: state => !!state.user,
totalClipDuration: state => {
return state.clips.map(clip => clip.end - clip.start).reduce((prev, curr) => prev + curr, 0.0)
}
},
actions: {
async login({commit}, auth) {
......@@ -185,9 +213,7 @@ export default createStore({
// Generate thumbnail for each file object
for (let fileObj of files) {
// fix long duration on images
if (fileObj.json.duration == 3600) {
fileObj.json.duration = 60
}
fileObj.json.duration = fixImageDuration(fileObj.json.duration)
let payload = { obj: fileObj, frame: 1 }
dispatch('attachThumbnail', payload)
}
......@@ -230,14 +256,10 @@ export default createStore({
commit('addError', err.response.data)
}
},
async editClip({dispatch, commit}, payload) {
async editClip({dispatch, commit}, clipObj) {
try {
const response = await instance.post(`${payload.project_url}clips/`, payload.data)
let clipObj = response.data
const file_response = await instance.get(clipObj.file)
clipObj.fileObj = file_response.data
commit('addClip', clipObj)
await instance.patch(`${clipObj.url}`, clipObj)
commit('setClip', clipObj)
let fps = clipObj.json.reader.fps.num / clipObj.json.reader.fps.den
let thumbnail_payload = { obj: clipObj, frame: clipObj.start * fps, latest: true }
dispatch('attachThumbnail', thumbnail_payload)
......@@ -278,9 +300,7 @@ export default createStore({
let response = await instance.post(`${payload.project_url}files/`, payload.data, config)
let fileObj = response.data
// fix long duration on images
if (fileObj.json.duration == 3600) {
fileObj.json.duration = 60
}
fileObj.json.duration = fixImageDuration(fileObj.json.duration)
commit('removeUpload', uploadID)
let thumbnail_payload = { obj: fileObj, frame: 1 }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment