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> <template>
<div class="row mb-3 gx-2 p-2 scrolling-container"> <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="col-12">
<div class="row gy-2 gx-2 mb-2" v-for="(clip, index) in thumbnailedClips" :key="clip.id"> <div class="row gy-2 gx-2 mb-2" v-for="(clip, index) in thumbnailedClips" :key="clip.id">
...@@ -13,8 +13,8 @@ ...@@ -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"/> <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> </svg>
<ul class="dropdown-menu small-dropdown"> <ul class="dropdown-menu small-dropdown">
<li><a class="dropdown-item" href="#">Move Up</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 class="dropdown-item" href="#">Move Down</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><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" @click="deleteClipBtn(clip.id)">Delete</a></li> <li><a class="dropdown-item" href="#" @click="deleteClipBtn(clip.id)">Delete</a></li>
</ul> </ul>
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
</template> </template>
<script> <script>
import {mapActions, mapState, mapMutations} from "vuex" import {mapActions, mapState, mapMutations, mapGetters} from "vuex"
export default { export default {
name: "Clips.vue", name: "Clips.vue",
...@@ -46,27 +46,56 @@ export default { ...@@ -46,27 +46,56 @@ export default {
deleteClipBtn(clip_id) { deleteClipBtn(clip_id) {
this.deleteClip(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) { toggleSelection(clipObj) {
if (clipObj.fileObj != this.preview.file) { if (clipObj != this.preview.clip) {
this.setPreviewFile(clipObj.fileObj) this.setPreviewClip(clipObj)
} else { } else {
this.setPreviewFile(null) this.setPreviewClip(null)
} }
}, },
getSelectedClass(clipObj) { 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' return 'selected'
} else { } else {
return '' return ''
} }
}, },
...mapActions(['loadClips', 'createClip', 'deleteClip']), ...mapActions(['loadClips', 'createClip', 'deleteClip', 'editClip']),
...mapMutations((['setPreviewFile', 'setClips'])) ...mapMutations((['setPreviewClip', 'setClips']))
}, },
computed: { computed: {
thumbnailedClips() { 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']) ...mapState(['clips', 'preview', 'scrollToClip'])
}, },
watch: { watch: {
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
</svg> </svg>
</div> </div>
<div ref="clip" class="clip" :class="clipSizeClass" :style="{left: preview.start * 100.0 + '%', width: `calc(${(preview.end - preview.start) * 100.0}%)`}"> <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> </div>
</div> </div>
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
</template> </template>
<script> <script>
import {mapState, mapActions, mapMutations} from "vuex"; import {mapState, mapActions, mapGetters, mapMutations} from "vuex";
export default { export default {
name: "Preview.vue", name: "Preview.vue",
...@@ -79,7 +79,7 @@ export default { ...@@ -79,7 +79,7 @@ export default {
async createClipBtn() { async createClipBtn() {
let data = { let data = {
"file": this.preview.file.url, "file": this.preview.file.url,
"position": 0.0, "position": this.totalClipDuration,
"start": this.preview.start * this.preview.file.json.duration, "start": this.preview.start * this.preview.file.json.duration,
"end": this.preview.end * this.preview.file.json.duration, "end": this.preview.end * this.preview.file.json.duration,
"layer": 1, "layer": 1,
...@@ -204,6 +204,7 @@ export default { ...@@ -204,6 +204,7 @@ export default {
}, },
computed: { computed: {
...mapState(['preview']), ...mapState(['preview']),
...mapGetters(['totalClipDuration']),
hasPreviewFile() { hasPreviewFile() {
if (this.preview.file) { if (this.preview.file) {
return true return true
...@@ -228,9 +229,12 @@ export default { ...@@ -228,9 +229,12 @@ export default {
this.is_paused = true this.is_paused = true
this.$refs.video.pause() this.$refs.video.pause()
this.$refs.video.load() 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() { updated() {
......
...@@ -14,4 +14,12 @@ instance.interceptors.request.use(function (config) { ...@@ -14,4 +14,12 @@ instance.interceptors.request.use(function (config) {
return config; return config;
}); });
export { instance }; function fixImageDuration(duration) {
\ No newline at end of file if (duration == 3600) {
return 30
} else {
return duration
}
}
export { instance, fixImageDuration };
\ No newline at end of file
import { createStore } from 'vuex' import { createStore } from 'vuex'
import { v4 as uuidv4 } from "uuid" import { v4 as uuidv4 } from "uuid"
import { instance } from "./axios" import { instance, fixImageDuration } from "./axios"
export default createStore({ export default createStore({
...@@ -17,9 +17,11 @@ export default createStore({ ...@@ -17,9 +17,11 @@ export default createStore({
clip: null, clip: null,
start: 0.0, start: 0.0,
end: 1.0, end: 1.0,
position: 0.0 position: 0.0,
length: 0.0
}, },
scrollToClip: null, scrollToClip: null,
default_image_length: 30
}, },
mutations: { mutations: {
...@@ -101,20 +103,43 @@ export default createStore({ ...@@ -101,20 +103,43 @@ export default createStore({
setPreview(state, payload) { setPreview(state, payload) {
state.preview.start = payload.start state.preview.start = payload.start
state.preview.end = payload.end 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) { setPreviewPosition(state, position) {
state.preview.position = position state.preview.position = position
}, },
setPreviewFile(state, file) { setPreviewFile(state, file) {
state.preview.file = file state.preview.file = file
state.preview.clip = null
state.preview.start = 0.0 state.preview.start = 0.0
state.preview.end = 1.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) { setPreviewClip(state, clipObj) {
state.preview.clip = clipObj state.preview.clip = clipObj
state.preview.start = clipObj.start if (clipObj && clipObj.fileObj) {
state.preview.end = clipObj.end state.preview.start = clipObj.start / fixImageDuration(clipObj.fileObj.json.duration)
state.preview.position = clipObj.start 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) { addUpload(state, upload) {
state.uploads.push(upload) state.uploads.push(upload)
...@@ -132,7 +157,10 @@ export default createStore({ ...@@ -132,7 +157,10 @@ export default createStore({
}, },
}, },
getters: { 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: { actions: {
async login({commit}, auth) { async login({commit}, auth) {
...@@ -185,9 +213,7 @@ export default createStore({ ...@@ -185,9 +213,7 @@ export default createStore({
// Generate thumbnail for each file object // Generate thumbnail for each file object
for (let fileObj of files) { for (let fileObj of files) {
// fix long duration on images // fix long duration on images
if (fileObj.json.duration == 3600) { fileObj.json.duration = fixImageDuration(fileObj.json.duration)
fileObj.json.duration = 60
}
let payload = { obj: fileObj, frame: 1 } let payload = { obj: fileObj, frame: 1 }
dispatch('attachThumbnail', payload) dispatch('attachThumbnail', payload)
} }
...@@ -230,14 +256,10 @@ export default createStore({ ...@@ -230,14 +256,10 @@ export default createStore({
commit('addError', err.response.data) commit('addError', err.response.data)
} }
}, },
async editClip({dispatch, commit}, payload) { async editClip({dispatch, commit}, clipObj) {
try { try {
const response = await instance.post(`${payload.project_url}clips/`, payload.data) await instance.patch(`${clipObj.url}`, clipObj)
let clipObj = response.data commit('setClip', clipObj)
const file_response = await instance.get(clipObj.file)
clipObj.fileObj = file_response.data
commit('addClip', clipObj)
let fps = clipObj.json.reader.fps.num / clipObj.json.reader.fps.den let fps = clipObj.json.reader.fps.num / clipObj.json.reader.fps.den
let thumbnail_payload = { obj: clipObj, frame: clipObj.start * fps, latest: true } let thumbnail_payload = { obj: clipObj, frame: clipObj.start * fps, latest: true }
dispatch('attachThumbnail', thumbnail_payload) dispatch('attachThumbnail', thumbnail_payload)
...@@ -278,9 +300,7 @@ export default createStore({ ...@@ -278,9 +300,7 @@ export default createStore({
let response = await instance.post(`${payload.project_url}files/`, payload.data, config) let response = await instance.post(`${payload.project_url}files/`, payload.data, config)
let fileObj = response.data let fileObj = response.data
// fix long duration on images // fix long duration on images
if (fileObj.json.duration == 3600) { fileObj.json.duration = fixImageDuration(fileObj.json.duration)
fileObj.json.duration = 60
}
commit('removeUpload', uploadID) commit('removeUpload', uploadID)
let thumbnail_payload = { obj: fileObj, frame: 1 } 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