Commit 22ca61b8 by Jonathan Thomas

Fixed thumbnail widths on all card views. Added col-md sizes on thumbnails.…

Fixed thumbnail widths on all card views. Added col-md sizes on thumbnails. Refactored AddProject, LoadProjects, and RemoveProject (faster async calls). Added Project thumbnails. Changed Projects screen to use thumbnails, context menus. Added profile/presets option to new project modal.
parent 1f2c97c4
......@@ -189,6 +189,7 @@ export default {
}
.clip-thumbnail {
cursor: pointer;
width: 100%;
}
.clip-btn {
float: right;
......
......@@ -19,7 +19,7 @@
<!-- Scrolling container for files -->
<div class="row mb-3 gx-2 p-2 scrolling-container">
<div v-for="file in searchedFiles" :key="file.id" class="col-sm-12 col-lg-4" style="position: relative;">
<div v-for="file in searchedFiles" :key="file.id" class="col-sm-12 col-md-6 col-lg-4" style="position: relative;">
<!-- Context Menu -->
<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">
......@@ -156,6 +156,7 @@ export default {
}
.file-thumbnail {
cursor: pointer;
width: 100%;
}
.context-menu {
color: #ffffff;
......
......@@ -17,20 +17,36 @@
</div>
</div>
<!-- Project cards -->
<div class="row gy-3 gx-3">
<div class="col-sm-3" v-for="project in projects" :key="project.id" >
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ project.name }}</h5>
<p class="card-text">{{ project.width }}x{{ project.height }} @ {{ project.fps_num / project.fps_den }} FPS</p>
<button type="button" class="btn btn-primary" @click="editProject(project)">Edit</button>
<button type="button" class="btn btn-danger" @click="showDeletePrompt(project)" ref="Delete">Delete</button>
</div>
<!-- Project thumbnails -->
<div class="row gy-2 gx-2 mb-2">
<div v-for="project in thumbnailedProjects" :key="project.id" class="col-sm-12 col-md-6 col-lg-3" style="position: relative;">
<!-- Project name badge -->
<span class="project-badge badge">{{ project.name }}</span>
<!-- Context menu for project -->
<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="editProject(project)">Edit</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" @click="showDeletePrompt(project)">Delete</a></li>
</ul>
</div>
<!-- Project profile label -->
<div class="profile-label">
{{ project.width }}x{{ project.height}}@{{ (project.fps_num / project.fps_den).toFixed(0) }}fps
</div>
<!-- Project thumbnail image -->
<img @click="editProject(project)" class="img-fluid img-thumbnail project-thumbnail" :title="project.name" :src="project.thumbnail"/>
</div>
</div>
<!-- New Project Modal -->
<div class="modal fade" ref="newProjectModal" tabindex="-1" aria-labelledby="newModalLabel" aria-hidden="true">
<div class="modal-dialog">
......@@ -46,6 +62,14 @@
<input v-model="project_name" type="text" class="form-control">
</div>
<div class="mb-3">
<select v-model="selected_profile" @change="profileChanged" class="form-select" aria-label="Choose a profile...">
<option value="" selected>Choose a profile...</option>
<option v-for="preset in presets" :key="preset.id" :value="preset.id">
{{ preset.width }}x{{ preset.height }} @ {{ preset.fps.toFixed(0) }}fps</option>
</select>
</div>
<label class="form-label">Size:</label>
<div class="input-group mb-3">
<input v-model="project_width" type="text" class="form-control" placeholder="Width">
......@@ -70,15 +94,15 @@
</div>
<!-- Delete Project Modal -->
<div class="modal fade" ref="deleteModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal fade" ref="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Project <span ref="prompt_project_name"></span>?</h5>
<h5 class="modal-title" id="deleteModalLabel">Delete Project?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete project #<span ref="prompt_project_id"></span>?
Are you sure you want to delete project <span ref="prompt_project_name" style="font-weight: bold;"></span>?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" ref="Close" data-bs-dismiss="modal">Cancel</button>
......@@ -90,7 +114,7 @@
</template>
<script>
import { mapState, mapActions, mapMutations } from 'vuex'
import { mapActions, mapMutations, mapGetters } from 'vuex'
import { Modal } from "bootstrap"
export default {
......@@ -103,10 +127,78 @@ export default {
project_fps_den: 1,
project_width: 1920,
project_height: 1080,
show_spinner: false
show_spinner: false,
selected_profile: 3031,
presets: [
{"id": 6061, "width": 3840, "height": 2160, "fps_num": 60, "fps_den": 1, "fps": 60.0},
{"id": 67001, "width": 3840, "height": 2160, "fps_num": 60000, "fps_den": 1001, "fps": 59.94005994005994},
{"id": 6051, "width": 3840, "height": 2160, "fps_num": 50, "fps_den": 1, "fps": 50.0},
{"id": 6031, "width": 3840, "height": 2160, "fps_num": 30, "fps_den": 1, "fps": 30.0},
{"id": 37001, "width": 3840, "height": 2160, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 6026, "width": 3840, "height": 2160, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 6025, "width": 3840, "height": 2160, "fps_num": 24, "fps_den": 1, "fps": 24.0},
{"id": 31001, "width": 3840, "height": 2160, "fps_num": 24000, "fps_den": 1001, "fps": 23.976023976023978},
{"id": 4061, "width": 2560, "height": 1440, "fps_num": 60, "fps_den": 1, "fps": 60.0},
{"id": 65001, "width": 2560, "height": 1440, "fps_num": 60000, "fps_den": 1001, "fps": 59.94005994005994},
{"id": 4051, "width": 2560, "height": 1440, "fps_num": 50, "fps_den": 1, "fps": 50.0},
{"id": 4031, "width": 2560, "height": 1440, "fps_num": 30, "fps_den": 1, "fps": 30.0},
{"id": 35001, "width": 2560, "height": 1440, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 4026, "width": 2560, "height": 1440, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 4025, "width": 2560, "height": 1440, "fps_num": 24, "fps_den": 1, "fps": 24.0},
{"id": 29001, "width": 2560, "height": 1440, "fps_num": 24000, "fps_den": 1001, "fps": 23.976023976023978},
{"id": 3061, "width": 1920, "height": 1080, "fps_num": 60, "fps_den": 1, "fps": 60.0},
{"id": 64001, "width": 1920, "height": 1080, "fps_num": 60000, "fps_den": 1001, "fps": 59.94005994005994},
{"id": 3051, "width": 1920, "height": 1080, "fps_num": 50, "fps_den": 1, "fps": 50.0},
{"id": 3031, "width": 1920, "height": 1080, "fps_num": 30, "fps_den": 1, "fps": 30.0},
{"id": 34001, "width": 1920, "height": 1080, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 3026, "width": 1920, "height": 1080, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 3025, "width": 1920, "height": 1080, "fps_num": 24, "fps_den": 1, "fps": 24.0},
{"id": 28001, "width": 1920, "height": 1080, "fps_num": 24000, "fps_den": 1001, "fps": 23.976023976023978},
{"id": 33521, "width": 1440, "height": 1080, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 2546, "width": 1440, "height": 1080, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 2061, "width": 1280, "height": 720, "fps_num": 60, "fps_den": 1, "fps": 60.0},
{"id": 63001, "width": 1280, "height": 720, "fps_num": 60000, "fps_den": 1001, "fps": 59.94005994005994},
{"id": 2051, "width": 1280, "height": 720, "fps_num": 50, "fps_den": 1, "fps": 50.0},
{"id": 2031, "width": 1280, "height": 720, "fps_num": 30, "fps_den": 1, "fps": 30.0},
{"id": 33001, "width": 1280, "height": 720, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 2026, "width": 1280, "height": 720, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 2025, "width": 1280, "height": 720, "fps_num": 24, "fps_den": 1, "fps": 24.0},
{"id": 27001, "width": 1280, "height": 720, "fps_num": 24000, "fps_den": 1001, "fps": 23.976023976023978},
{"id": 1626, "width": 1024, "height": 576, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 32335, "width": 854, "height": 480, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 1370, "width": 768, "height": 576, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 32201, "width": 720, "height": 480, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 32207, "width": 720, "height": 486, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 1322, "width": 720, "height": 576, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 26207, "width": 720, "height": 486, "fps_num": 24000, "fps_den": 1001, "fps": 23.976023976023978},
{"id": 32121, "width": 640, "height": 480, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 826, "width": 512, "height": 288, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 31961, "width": 480, "height": 480, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 1082, "width": 480, "height": 576, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 31667, "width": 426, "height": 240, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 698, "width": 384, "height": 288, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 31641, "width": 352, "height": 288, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 31833, "width": 352, "height": 480, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 31593, "width": 352, "height": 240, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 666, "width": 352, "height": 288, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 954, "width": 352, "height": 576, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 656, "width": 352, "height": 288, "fps_num": 15, "fps_den": 1, "fps": 15.0},
{"id": 31561, "width": 320, "height": 240, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 576, "width": 320, "height": 240, "fps_num": 15, "fps_den": 1, "fps": 15.0},
{"id": 31321, "width": 176, "height": 144, "fps_num": 30000, "fps_den": 1001, "fps": 29.97002997002997},
{"id": 346, "width": 176, "height": 144, "fps_num": 25, "fps_den": 1, "fps": 25.0},
{"id": 336, "width": 176, "height": 144, "fps_num": 15, "fps_den": 1, "fps": 15.0}
]
}
},
methods: {
profileChanged() {
let profile_details = this.presets.filter(p => p.id == this.selected_profile)[0]
this.project_width = profile_details.width
this.project_height = profile_details.height
this.project_fps_num = profile_details.fps_num
this.project_fps_den = profile_details.fps_den
},
editProject(project) {
this.clearErrors()
this.$router.push(`/projects/${project.id}`)
......@@ -133,7 +225,6 @@ export default {
},
showDeletePrompt(project) {
this.deletedProject = project;
this.$refs.prompt_project_id.innerText = this.deletedProject.id
this.$refs.prompt_project_name.innerText = this.deletedProject.name
let myModal = new Modal(this.$refs.deleteModal)
myModal.show()
......@@ -147,7 +238,7 @@ export default {
...mapMutations(['setProject', 'clearErrors'])
},
computed: {
...mapState(['projects'])
...mapGetters(['thumbnailedProjects']),
},
async mounted() {
this.show_spinner = true
......@@ -165,4 +256,44 @@ export default {
width: 3em!important;
height: 3em!important;
}
.context-menu {
color: #ffffff;
position: absolute;
top: 0%;
right: 0%;
padding: 10px;
cursor: pointer;
}
.img-parent {
position: relative;
}
.project-thumbnail {
cursor: pointer;
width: 100%;
}
.profile-label {
color: #ffffff;
position: absolute;
bottom: 5%;
z-index: 999;
right: 5%;
font-size: .8em;
background-color: #000000;
border-radius: 4px;
padding: 3px;
opacity: 75%;
}
.project-badge {
position: absolute;
bottom: 5%;
left: 5%;
padding: 8px;
opacity: 75%;
background-color: #000000;
border-radius: 4px;
}
.small-dropdown {
min-width: 5em;
opacity: 80%;
}
</style>
\ No newline at end of file
......@@ -52,12 +52,24 @@ export default createStore({
setUser(state, user) {
state.user = user
},
addProject(state, projectObj) {
state.projects.push(projectObj)
},
setProjects(state, projects) {
state.projects = projects
},
setProject(state, project) {
state.project = project
},
updateProjectThumbnail(state, payload) {
state.projects = [
...state.projects = state.projects.filter(p => p.id != payload.obj.id),
payload.obj
]
},
deleteProject(state, project_id) {
state.projects = state.projects.filter(p => p.id != project_id)
},
setFiles(state, files) {
state.files = files
},
......@@ -184,6 +196,9 @@ export default createStore({
thumbnailedClips: state => {
return state.clips.filter(clip => clip.thumbnail).sort((a, b) => a.position - b.position)
},
thumbnailedProjects: state => {
return state.projects.filter(project => project.thumbnail).sort((a, b) => b.id - a.id)
},
activeExports: state => {
return state.exports.filter(e => e.status == "in-progress" || e.status == "pending").sort((a, b) => b.id - a.id)
}
......@@ -200,10 +215,16 @@ export default createStore({
commit('addError', err.response.data)
}
},
async loadProjects({commit}) {
async loadProjects({commit, dispatch}) {
try {
const response = await instance.get('/projects/')
commit('setProjects', response.data.results)
// Sort all projects, and limit to X most recent
let sorted_projects = response.data.results.sort().reverse().slice(0, 12)
commit('setProjects', sorted_projects)
for (let p of response.data.results) {
let payload = { obj: p, frame: 1 }
dispatch('attachThumbnail', payload)
}
} catch(err) {
commit('addError', err.response.data)
}
......@@ -215,18 +236,20 @@ export default createStore({
commit('addError', err.response.data)
}
},
async createProject({dispatch, commit}, payload) {
async createProject({commit, dispatch}, payload) {
try {
await instance.post('/projects/', payload)
dispatch('loadProjects')
const response = await instance.post('/projects/', payload)
commit('addProject', response)
let thumbnail_payload = { obj: response.data, frame: 1 }
dispatch('attachThumbnail', thumbnail_payload)
} catch(err) {
commit('addError', err.response.data)
}
},
async deleteProject({commit, dispatch}, project_id) {
async deleteProject({commit}, project_id) {
try {
await instance.delete(`projects/${project_id}/`)
dispatch('loadProjects')
commit('deleteProject', project_id)
} catch(err) {
commit('addError', err.response.data)
}
......@@ -358,18 +381,20 @@ export default createStore({
}
},
async attachThumbnail({commit}, payload) {
let fileObj = null
let Obj = payload.obj
let objectType = null
if (payload.obj.file) {
// Clip passed in
fileObj = payload.obj.fileObj
Obj = payload.obj.fileObj
objectType = 'clip'
} else {
} else if (payload.obj.media) {
// File passed in
fileObj = payload.obj
objectType = 'file'
} else {
// Project passed in
objectType = 'project'
}
let sessionKey = `${objectType}-${fileObj.id}-frame${payload.frame}`
let sessionKey = `${objectType}-${Obj.id}-frame${payload.frame}`
let data = {
"frame_number": payload.frame,
......@@ -390,7 +415,7 @@ export default createStore({
}
}
if (!thumbnailUrl) {
const response = await instance.post(`${fileObj.url}thumbnail/`, data, { responseType: 'arraybuffer' })
const response = await instance.post(`${Obj.url}thumbnail/`, data, { responseType: 'arraybuffer' })
let blob = new Blob(
[response.data],
{ type: response.headers['content-type'] }
......@@ -403,8 +428,10 @@ export default createStore({
payload.obj.thumbnail = thumbnailUrl
if (objectType == 'file') {
commit('updateFileThumbnail', payload)
} else {
} else if (objectType == 'clip') {
commit('updateClipThumbnail', payload)
} else {
commit('updateProjectThumbnail', payload)
}
} catch(err) {
......
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