Commit ed036f0b by Jonathan Thomas

Integrating loading existing project *.zip files into the "Create Project"…

Integrating loading existing project *.zip files into the "Create Project" modal. Also, handling some errors, and updating some text and labels.
parent 5dee4fdd
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
:href="projectDownloadUrl || '#'" :href="projectDownloadUrl || '#'"
target="_blank" target="_blank"
rel="noopener"> rel="noopener">
Download Project (*.osp) Download OpenShot Project (*.zip)
</a> </a>
</li> </li>
</ul> </ul>
......
...@@ -41,8 +41,8 @@ ...@@ -41,8 +41,8 @@
</div> </div>
<!-- Loading spinner --> <!-- Loading spinner -->
<div v-if="show_spinner" class="md-5 p-4 text-center"> <div v-if="show_spinner" class="md-5 p-4 text-center project-list-spinner">
<div class="spinner-border" role="status"> <div class="spinner-border project-spinner" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
...@@ -85,10 +85,51 @@ ...@@ -85,10 +85,51 @@
<input v-model="project_fps_den" type="text" class="form-control" placeholder="FPS Denominator"> <input v-model="project_fps_den" type="text" class="form-control" placeholder="FPS Denominator">
</div> </div>
<div v-if="!editing" class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input"
type="checkbox"
id="loadArchiveSwitch"
v-model="load_archive_enabled"
@change="archiveSwitchChanged">
<label class="form-check-label" for="loadArchiveSwitch">Load Existing Project</label>
</div>
<div v-if="load_archive_enabled" class="mt-2">
<input ref="projectArchiveInput"
type="file"
class="d-none"
accept=".zip,.osp"
@change="onArchiveSelected">
<div class="d-flex align-items-center flex-wrap gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" @click="triggerArchiveBrowse">
Browse Project
</button>
<span v-if="project_archive_file" class="text-muted archive-file-name">{{ project_archive_file.name }}</span>
<span v-else class="text-muted">No project selected</span>
<button v-if="project_archive_file"
type="button"
class="btn btn-link btn-sm text-decoration-none"
@click="clearArchiveSelection">
Clear
</button>
</div>
<div class="form-text">
Optional: load *.zip of an existing OpenShot project.
</div>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" ref="CloseProjectModal" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" ref="CloseProjectModal" data-bs-dismiss="modal">Cancel</button>
<button v-if="!editing" type="button" class="btn btn-primary" @click="createProjectClick">Create Project</button> <button v-if="!editing"
type="button"
class="btn btn-primary"
:disabled="creating_project"
@click="createProjectClick">
<span v-if="creating_project" class="spinner-border spinner-border-sm align-middle me-2 create-btn-spinner" role="status" aria-hidden="true"></span>
Create Project
</button>
<button v-if="editing" type="button" class="btn btn-primary" @click="updateProjectClick">Save Project</button> <button v-if="editing" type="button" class="btn btn-primary" @click="updateProjectClick">Save Project</button>
</div> </div>
</div> </div>
...@@ -133,7 +174,10 @@ export default { ...@@ -133,7 +174,10 @@ export default {
editing: false, editing: false,
show_spinner: false, show_spinner: false,
selected_profile: 3031, selected_profile: 3031,
profiles profiles,
project_archive_file: null,
creating_project: false,
load_archive_enabled: false
} }
}, },
methods: { methods: {
...@@ -152,6 +196,7 @@ export default { ...@@ -152,6 +196,7 @@ export default {
this.clearErrors() this.clearErrors()
this.selectedProject = project this.selectedProject = project
this.editing = true this.editing = true
this.disableArchiveSection()
// Set profile to match current project settings // Set profile to match current project settings
let profile_details = this.profiles.filter(p => p.width == project.width && let profile_details = this.profiles.filter(p => p.width == project.width &&
...@@ -178,11 +223,26 @@ export default { ...@@ -178,11 +223,26 @@ export default {
this.project_width = 1920 this.project_width = 1920
this.project_height = 1080 this.project_height = 1080
this.selected_profile = 3031 this.selected_profile = 3031
this.disableArchiveSection()
let myModal = new Modal(this.$refs.newProjectModal) let myModal = new Modal(this.$refs.newProjectModal)
myModal.show() myModal.show()
}, },
hideNewProjectModal() {
if (!this.$refs.newProjectModal) {
return
}
let myModal = Modal.getInstance(this.$refs.newProjectModal)
if (!myModal) {
myModal = new Modal(this.$refs.newProjectModal)
}
myModal.hide()
},
async createProjectClick() { async createProjectClick() {
if (this.creating_project) {
return
}
this.creating_project = true
let payload = { let payload = {
"name": this.project_name, "name": this.project_name,
"width": this.project_width, "width": this.project_width,
...@@ -195,10 +255,64 @@ export default { ...@@ -195,10 +255,64 @@ export default {
"json": {} "json": {}
} }
try {
let project = await this.createProject(payload) let project = await this.createProject(payload)
this.$refs.CloseProjectModal.click();
if (project && project.id) { if (project && project.id) {
await this.maybeLoadArchive(project.id)
this.hideNewProjectModal()
this.$router.push(`/projects/${project.id}`) this.$router.push(`/projects/${project.id}`)
} else if (!project) {
this.clearArchiveSelection()
}
} finally {
this.creating_project = false
}
},
triggerArchiveBrowse() {
if (this.$refs.projectArchiveInput) {
this.$refs.projectArchiveInput.click()
}
},
archiveSwitchChanged() {
if (!this.load_archive_enabled) {
this.clearArchiveSelection()
}
},
disableArchiveSection() {
this.load_archive_enabled = false
this.clearArchiveSelection()
},
onArchiveSelected(event) {
const files = event?.target?.files
if (!files || files.length === 0) {
this.project_archive_file = null
return
}
const file = files[0]
const fileName = file.name?.toLowerCase() || ''
if (!fileName.endsWith('.zip') && !fileName.endsWith('.osp')) {
this.clearArchiveSelection()
return
}
this.project_archive_file = file
},
clearArchiveSelection() {
this.project_archive_file = null
if (this.$refs.projectArchiveInput) {
this.$refs.projectArchiveInput.value = null
}
},
async maybeLoadArchive(projectId) {
if (!this.load_archive_enabled || !this.project_archive_file) {
return
}
try {
await this.loadProjectArchive({ projectId, file: this.project_archive_file })
} catch(err) {
console.error('Failed to load project archive', err)
} finally {
this.clearArchiveSelection()
this.load_archive_enabled = false
} }
}, },
async updateProjectClick() { async updateProjectClick() {
...@@ -222,7 +336,7 @@ export default { ...@@ -222,7 +336,7 @@ export default {
this.$refs.Close.click(); this.$refs.Close.click();
this.selectedProject = null this.selectedProject = null
}, },
...mapActions(['loadProjects', 'createProject', 'deleteProject', 'updateProject']), ...mapActions(['loadProjects', 'createProject', 'deleteProject', 'updateProject', 'loadProjectArchive']),
...mapMutations(['setProject', 'clearErrors']) ...mapMutations(['setProject', 'clearErrors'])
}, },
computed: { computed: {
...@@ -243,7 +357,7 @@ export default { ...@@ -243,7 +357,7 @@ export default {
button { button {
margin-left: 5px; margin-left: 5px;
} }
.spinner-border { .project-spinner {
width: 3em!important; width: 3em!important;
height: 3em!important; height: 3em!important;
} }
...@@ -296,4 +410,15 @@ export default { ...@@ -296,4 +410,15 @@ export default {
min-width: 5em; min-width: 5em;
opacity: 0.8; opacity: 0.8;
} }
.archive-file-name {
max-width: 180px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.create-btn-spinner {
width: 0.9rem;
height: 0.9rem;
border-width: 0.12em;
}
</style> </style>
...@@ -246,7 +246,7 @@ export default createStore({ ...@@ -246,7 +246,7 @@ export default createStore({
dispatch('attachThumbnail', payload) dispatch('attachThumbnail', payload)
} }
} catch(err) { } catch(err) {
commit('addError', err.response.data) commit('addError', err.response?.data || err.message)
} }
}, },
async getProject({commit}, project_id) { async getProject({commit}, project_id) {
...@@ -268,6 +268,21 @@ export default createStore({ ...@@ -268,6 +268,21 @@ export default createStore({
return null return null
} }
}, },
async loadProjectArchive({commit}, payload) {
if (!payload || !payload.projectId || !payload.file) {
return
}
try {
const formData = new FormData()
formData.append('archive', payload.file)
await instance.post(`/projects/${payload.projectId}/load/`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
} catch(err) {
commit('addError', err.response?.data || err.message)
throw err
}
},
async updateProject({commit}, payload) { async updateProject({commit}, payload) {
try { try {
await instance.patch(`${payload.url}`, payload) await instance.patch(`${payload.url}`, payload)
...@@ -457,6 +472,10 @@ export default createStore({ ...@@ -457,6 +472,10 @@ export default createStore({
// Project passed in // Project passed in
objectType = 'project' objectType = 'project'
} }
if (!Obj) {
console.warn('attachThumbnail: missing object data', payload)
return
}
let sessionKey = `${objectType}-${Obj.id}-frame${payload.frame}` let sessionKey = `${objectType}-${Obj.id}-frame${payload.frame}`
if (payload.clobber) { if (payload.clobber) {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<strong>Password:</strong> demo-password <strong>Password:</strong> demo-password
</p> </p>
<CloudInstancePromo <CloudInstancePromo
label="Ready for more?" label="Unlock the full experience"
title="Launch your own instance" title="Launch your own instance"
:subtitle-html="launchSubtitleHtml" :subtitle-html="launchSubtitleHtml"
/> />
......
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