Commit 3d1229fb by Jonathan Thomas

Updated clips to have context menu, larger. Thumbnails only appear once loaded.…

Updated clips to have context menu, larger. Thumbnails only appear once loaded. Fixed some buggy code when switching between projects (showing the wrong clips). Added time calculation to timeline and clips.
parent bf3292b6
......@@ -3,24 +3,26 @@
<h3>Clips</h3>
<div class="col-12">
<div class="row gy-2 gx-2 mb-2" v-for="clip in clips" :key="clip.id">
<div class="col-7 text-center img-parent">
<img @click="toggleSelection(clip)" :class="getSelectedClass(clip)" class="img-fluid img-thumbnail clip-thumbnail" :title="clip.name" :src="clip.thumbnail"/>
</div>
<div class="col-5">
<div class="row">
<div class="row gy-2 gx-2 mb-2" v-for="clip in thumbnailedClips" :key="clip.id">
<div class="col-12 text-center img-parent">
<div class="col-12">
<div class="d-grid gap-1 p-2">
<button type="button" class="btn btn-outline-primary">Move Up</button>
<button type="button" class="btn btn-outline-primary">Move Down</button>
<button type="button" class="btn btn-outline-primary" @click="deleteClipBtn(clip.id)">Delete</button>
</div>
</div>
<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="#">Move Up</a></li>
<li><a class="dropdown-item" href="#">Move Down</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" @click="deleteClipBtn(clip.id)">Delete</a></li>
</ul>
</div>
<div class="clip-label">
{{ (clip.end - clip.start).toFixed(1) }} Seconds
</div>
<img @click="toggleSelection(clip)" :class="getSelectedClass(clip)" class="img-fluid img-thumbnail clip-thumbnail" :title="clip.name" :src="clip.thumbnail"/>
</div>
</div>
......@@ -57,20 +59,27 @@ export default {
}
},
...mapActions(['loadClips', 'createClip', 'deleteClip']),
...mapMutations((['setPreviewFile']))
...mapMutations((['setPreviewFile', 'setClips']))
},
computed: {
thumbnailedClips() {
return this.clips.filter(clip => clip.thumbnail)
},
...mapState(['clips', 'previewFile', 'latestClip'])
},
watch: {
latestClip() {
this.$nextTick(() => {
this.$refs.bottom.scrollIntoView({behavior: 'smooth', block: 'center'})
this.$refs.bottom.scrollIntoView({behavior: 'smooth', block: 'end'})
})
}
},
async mounted() {
this.setClips([])
await this.loadClips(this.project.id)
},
unmounted() {
this.setClips([])
}
}
</script>
......@@ -92,4 +101,24 @@ export default {
.selected {
border: #0d6efd 4px solid;
}
.context-menu {
color: #ffffff;
position: absolute;
top: 0%;
right: 0%;
padding: 10px;
cursor: pointer;
}
.small-dropdown {
min-width: 5em;
opacity: 80%;
}
.clip-label {
color: #ffffff;
position: absolute;
top: 88%;
z-index: 999;
right: 5%;
font-size: .8em;
}
</style>
\ No newline at end of file
......@@ -97,18 +97,20 @@ export default {
}
},
...mapActions(['loadFiles', 'createFile', 'deleteFile']),
...mapMutations((['setPreviewFile']))
...mapMutations((['setPreviewFile', 'setFiles']))
},
computed: {
searchedFiles() {
return this.files.filter(file => path.basename(file.media).toLowerCase().includes(this.search.toLowerCase()))
return this.files.filter(file => path.basename(file.media).toLowerCase().includes(this.search.toLowerCase()) && file.thumbnail)
},
...mapState(['files', 'previewFile', 'uploads'])
},
mounted() {
this.setFiles([])
this.loadFiles(this.project.id)
},
unmounted() {
this.setFiles([])
this.setPreviewFile(null)
}
}
......
......@@ -138,10 +138,12 @@ export default {
}
},
seekToMarker(marker) {
if (marker == 'start') {
this.$refs.video.currentTime = this.markers.start * this.$refs.video.duration
} else if (marker == 'end') {
this.$refs.video.currentTime = this.markers.end * this.$refs.video.duration
if (this.$refs.video) {
if (marker == 'start') {
this.$refs.video.currentTime = this.markers.start * this.$refs.video.duration
} else if (marker == 'end') {
this.$refs.video.currentTime = this.markers.end * this.$refs.video.duration
}
}
},
drag(e) {
......
......@@ -82,7 +82,7 @@
</template>
<script>
import { mapState, mapActions } from 'vuex'
import { mapState, mapActions, mapMutations } from 'vuex'
import { Modal } from "bootstrap"
export default {
......@@ -133,7 +133,8 @@ export default {
this.$refs.Close.click();
this.deletedProject = null
},
...mapActions(['loadProjects', 'createProject', 'deleteProject'])
...mapActions(['loadProjects', 'createProject', 'deleteProject']),
...mapMutations(['setProject'])
},
computed: {
...mapState(['projects'])
......
......@@ -8,11 +8,12 @@ export default createStore({
projects: [],
files: [],
clips: [],
user: null,
errors: [],
uploads: [],
project: null,
user: null,
previewFile: null,
latestClip: null,
uploads: []
},
mutations: {
......@@ -41,6 +42,9 @@ export default createStore({
setProjects(state, projects) {
state.projects = projects
},
setProject(state, project) {
state.project = project
},
setFiles(state, files) {
state.files = files
},
......@@ -48,10 +52,12 @@ export default createStore({
state.files.push(fileObj)
},
updateFileThumbnail(state, payload) {
state.files = [
if (payload.obj.projects.includes(state.project.url)) {
state.files = [
...state.files = state.files.filter(file => file.id != payload.obj.id),
payload.obj
]
payload.obj
]
}
},
deleteFile(state, file_id) {
state.files = state.files.filter(file => file.id != file_id)
......@@ -61,18 +67,23 @@ export default createStore({
},
setClips(state, clips) {
state.clips = clips
if (!clips) {
// clear latest clip if no clips
state.latestClip = null
}
},
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
if (state.project.url == payload.obj.project) {
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) {
......@@ -148,7 +159,7 @@ export default createStore({
for (let fileObj of files) {
// fix long duration on images
if (fileObj.json.duration == 3600) {
fileObj.json.duration = 60 * 3
fileObj.json.duration = 60
}
let payload = { obj: fileObj, frame: 1 }
dispatch('attachThumbnail', payload)
......@@ -226,7 +237,7 @@ export default createStore({
let fileObj = response.data
// fix long duration on images
if (fileObj.json.duration == 3600) {
fileObj.json.duration = 60 * 3
fileObj.json.duration = 60
}
commit('removeUpload', uploadID)
......
......@@ -11,7 +11,7 @@
</template>
<script>
import { mapActions, mapMutations } from "vuex";
import { mapActions, mapMutations, mapState } from "vuex";
import Files from "../components/Files";
import Clips from "../components/Clips";
import Preview from "../components/Preview";
......@@ -21,17 +21,17 @@ export default {
data() {
return {
hasData: false,
project: {}
}
},
methods: {
...mapActions(['getProject']),
...mapMutations(['addError'])
...mapMutations(['addError', 'setProject'])
},
computed: {
id() {
return this.$route.params.id
}
},
...mapState(['project'])
},
components: {
Files, Clips, Preview
......@@ -39,14 +39,16 @@ export default {
async mounted() {
try {
let results = await this.getProject(this.$route.params.id)
this.project = results.data
this.setProject(results.data)
this.hasData = true
} catch(err) {
// Handle 404
this.addError(err.response.data)
await this.$router.push(`/`)
}
},
unmounted() {
this.setProject(null)
}
}
</script>
......
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