Commit bf3292b6 by Jonathan Thomas

Separated thumbnail generation from load actions. Improved scroll-to-bottom clip…

Separated thumbnail generation from load actions. Improved scroll-to-bottom clip logic. Improved timeline/clip style (added seconds).
parent ba033dbe
......@@ -60,18 +60,14 @@ export default {
...mapMutations((['setPreviewFile']))
},
computed: {
...mapState(['clips', 'previewFile'])
...mapState(['clips', 'previewFile', 'latestClip'])
},
watch: {
clips(newVal, oldVal) {
let diff = newVal.length - oldVal.length
if (diff == 1) {
// 1 clip was added (scroll to bottom)
latestClip() {
this.$nextTick(() => {
this.$refs.bottom.scrollIntoView({behavior: 'smooth', block: 'center'})
})
}
}
},
async mounted() {
await this.loadClips(this.project.id)
......
......@@ -7,7 +7,17 @@
<label for="floatingInput">Search</label>
</div>
</div>
<div v-for="file in searchedFiles" :key="file.id" class="col-4">
<div v-for="file in searchedFiles" :key="file.id" class="col-4" style="position: relative;">
<div class="btn-group dropstart context-menu">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="dropdown-toggle bi bi-three-dots-vertical" data-bs-toggle="dropdown" aria-expanded="false" viewBox="0 0 16 16">
<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="#" @click="deleteFileBtn(file)">Delete</a></li>
</ul>
</div>
<img @click="toggleSelection(file)" :class="getSelectedClass(file)" class="file-thumbnail img-fluid img-thumbnail" :title="file.name" :src="file.thumbnail"/>
<figcaption class="figure-caption">{{ getFileName(file) }}</figcaption>
</div>
......@@ -61,6 +71,9 @@ export default {
// Clear file input
this.$refs.fileUpload.value = null
},
async deleteFileBtn(file) {
await this.deleteFile(file.id)
},
toggleSelection(fileObject) {
if (fileObject != this.previewFile) {
this.setPreviewFile(fileObject)
......@@ -83,7 +96,7 @@ export default {
return `${base.substr(0, 18)}...`
}
},
...mapActions(['loadFiles', 'createFile']),
...mapActions(['loadFiles', 'createFile', 'deleteFile']),
...mapMutations((['setPreviewFile']))
},
computed: {
......@@ -118,4 +131,16 @@ export default {
.file-thumbnail {
cursor: pointer;
}
.context-menu {
color: #ffffff;
position: absolute;
top: 0%;
right: 0%;
padding: 10px;
cursor: pointer;
}
.small-dropdown {
min-width: 5em;
opacity: 80%;
}
</style>
\ No newline at end of file
......@@ -38,7 +38,9 @@
<path d="M7 2a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM7 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
</svg>
</div>
<div class="clip" :style="{left: markers.start * 100.0 + '%', width: (markers.end - markers.start) * 100.0 + '%'}"></div>
<div class="clip" :style="{left: markers.start * 100.0 + '%', width: `calc(${(markers.end - markers.start) * 100.0}%)`}">
{{ ((markers.end - markers.start) * previewFile.json.duration).toFixed(1) }} Sec
</div>
</div>
</div>
<div class="col-sm-1 d-flex flex-column p-1">
......@@ -145,18 +147,18 @@ export default {
drag(e) {
if (this.dragging_marker == 'start') {
this.markers.start = Math.min(this.getCursorPosition(e), this.markers.end)
if (this.$refs.video) {
if (this.$refs.video && isFinite(this.markers.start * this.$refs.video.duration)) {
this.$refs.video.currentTime = this.markers.start * this.$refs.video.duration
}
} else if (this.dragging_marker == 'end') {
this.markers.end = Math.max(this.getCursorPosition(e, 12), this.markers.start)
if (this.$refs.video) {
if (this.$refs.video && isFinite(this.markers.start * this.$refs.video.duration)) {
this.$refs.video.currentTime = this.markers.end * this.$refs.video.duration
}
} else if (this.dragging_marker == 'playhead') {
let percent_x = this.getCursorPosition(e)
this.$refs.playhead.style.left = `${percent_x * 100}%`
if (this.$refs.video) {
if (this.$refs.video && isFinite(this.markers.start * this.$refs.video.duration)) {
this.$refs.video.currentTime = percent_x * this.$refs.video.duration
}
}
......@@ -261,8 +263,11 @@ video {
background-color: #0d6efd;
height:48px;
position: absolute;
opacity: 50%;
z-index: 0;
color: #ffffff;
font-size: 0.7em;
text-align: center;
padding-top: 15px;
}
.marker_left {
border-left: rgba(255, 255, 255, 0.5) solid 1px;
......
......@@ -11,6 +11,7 @@ export default createStore({
user: null,
errors: [],
previewFile: null,
latestClip: null,
uploads: []
},
......@@ -43,12 +44,37 @@ export default createStore({
setFiles(state, files) {
state.files = files
},
addFile(state, fileObj) {
state.files.push(fileObj)
},
updateFileThumbnail(state, payload) {
state.files = [
...state.files = state.files.filter(file => file.id != payload.obj.id),
payload.obj
]
},
deleteFile(state, file_id) {
state.files = state.files.filter(file => file.id != file_id)
},
addClip(state, clip) {
state.clips.push(clip)
},
setClips(state, clips) {
state.clips = clips
},
deleteClip(state, clip_id) {
state.clips = state.clips.filter(clip => clip.id != clip_id)
},
updateClipThumbnail(state, payload) {
state.clips = [
...state.clips = state.clips.filter(clip => clip.id != payload.obj.id),
payload.obj
]
if (payload.latest)
{
state.latestClip = payload.obj
}
},
setPreviewFile(state, file) {
state.previewFile = file
},
......@@ -113,80 +139,55 @@ export default createStore({
commit('addError', err.response.data)
}
},
async loadFiles({commit}, project_id) {
async loadFiles({commit, dispatch}, project_id) {
try {
const response = await instance.get(`projects/${project_id}/files/`)
let files = response.data.results
// Generate thumbnail for each file object
for (let fileObj of files) {
let data = {
"frame_number": 1,
"width": 480,
"height": 270,
"image_format": "JPEG",
"image_quality": 100
}
try {
const response = await instance.post(`${fileObj.url}thumbnail/`, data, { responseType: 'arraybuffer' })
let blob = new Blob(
[response.data],
{ type: response.headers['content-type'] }
)
// Append thumbnail attribute to each file object
let thumbnailUrl = URL.createObjectURL(blob)
fileObj.thumbnail = thumbnailUrl
} catch(err) {
commit('addError', err.response.data)
// fix long duration on images
if (fileObj.json.duration == 3600) {
fileObj.json.duration = 60 * 3
}
let payload = { obj: fileObj, frame: 1 }
dispatch('attachThumbnail', payload)
}
commit('setFiles', files)
} catch(err) {
commit('addError', err.response.data)
}
},
async loadClips({commit}, project_id) {
async loadClips({commit, dispatch}, project_id) {
try {
const response = await instance.get(`projects/${project_id}/clips/`)
let clips = response.data.results
// Attach file obj to clip
for (let clipObj of clips) {
// Get related file object
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 data = {
"frame_number": clipObj.start * fps,
"width": 480,
"height": 270,
"image_format": "JPEG",
"image_quality": 100
}
try {
const response = await instance.post(`${clipObj.file}thumbnail/`, data, { responseType: 'arraybuffer' })
let blob = new Blob(
[response.data],
{ type: response.headers['content-type'] }
)
// Append thumbnail attribute to each clip object
let thumbnailUrl = URL.createObjectURL(blob)
clipObj.thumbnail = thumbnailUrl
} catch(err) {
commit('addError', err.response.data)
}
let thumbnail_payload = { obj: clipObj, frame: clipObj.start * fps, latest: false }
dispatch('attachThumbnail', thumbnail_payload)
}
commit('setClips', clips)
} catch(err) {
commit('addError', err.response.data)
}
},
async createClip({dispatch, commit}, payload) {
try {
await instance.post(`${payload.project_url}clips/`, payload.data)
dispatch('loadClips', payload.project_id)
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)
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)
} catch(err) {
commit('addError', err.response.data)
}
......@@ -199,7 +200,15 @@ export default createStore({
commit('addError', err.response.data)
}
},
async createFile({dispatch, commit}, payload) {
async deleteFile({commit}, file_id) {
try {
await instance.delete(`files/${file_id}/`)
commit('deleteFile', file_id)
} catch(err) {
commit('addError', err.response.data)
}
},
async createFile({commit, dispatch}, payload) {
let uploadID = uuidv4()
let uploadObject = { 'id': uploadID, progress: 0 }
commit('addUpload', uploadObject)
......@@ -213,13 +222,59 @@ export default createStore({
}
try {
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
// fix long duration on images
if (fileObj.json.duration == 3600) {
fileObj.json.duration = 60 * 3
}
commit('removeUpload', uploadID)
dispatch('loadFiles', payload.project_id)
let thumbnail_payload = { obj: fileObj, frame: 1 }
dispatch('attachThumbnail', thumbnail_payload)
} catch(err) {
commit('removeUpload', uploadID)
commit('addError', err.response.data)
}
},
async attachThumbnail({commit}, payload) {
let fileObj = null
let objectType = null
if (payload.obj.file) {
// Clip passed in
fileObj = payload.obj.fileObj
objectType = 'clip'
} else {
// File passed in
fileObj = payload.obj
objectType = 'file'
}
let data = {
"frame_number": payload.frame,
"width": 480,
"height": 270,
"image_format": "JPEG",
"image_quality": 100
}
try {
const response = await instance.post(`${fileObj.url}thumbnail/`, data, { responseType: 'arraybuffer' })
let blob = new Blob(
[response.data],
{ type: response.headers['content-type'] }
)
// Append thumbnail attribute to each file object
let thumbnailUrl = URL.createObjectURL(blob)
payload.obj.thumbnail = thumbnailUrl
if (objectType == 'file') {
commit('updateFileThumbnail', payload)
} else {
commit('updateClipThumbnail', payload)
}
} catch(err) {
commit('addError', err.response.data)
}
}
},
modules: {
......
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