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 @@
:href="projectDownloadUrl || '#'"
target="_blank"
rel="noopener">
Download Project (*.osp)
Download OpenShot Project (*.zip)
</a>
</li>
</ul>
......
......@@ -41,8 +41,8 @@
</div>
<!-- Loading spinner -->
<div v-if="show_spinner" class="md-5 p-4 text-center">
<div class="spinner-border" role="status">
<div v-if="show_spinner" class="md-5 p-4 text-center project-list-spinner">
<div class="spinner-border project-spinner" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
......@@ -85,10 +85,51 @@
<input v-model="project_fps_den" type="text" class="form-control" placeholder="FPS Denominator">
</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 class="modal-footer">
<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>
</div>
</div>
......@@ -133,7 +174,10 @@ export default {
editing: false,
show_spinner: false,
selected_profile: 3031,
profiles
profiles,
project_archive_file: null,
creating_project: false,
load_archive_enabled: false
}
},
methods: {
......@@ -152,6 +196,7 @@ export default {
this.clearErrors()
this.selectedProject = project
this.editing = true
this.disableArchiveSection()
// Set profile to match current project settings
let profile_details = this.profiles.filter(p => p.width == project.width &&
......@@ -178,11 +223,26 @@ export default {
this.project_width = 1920
this.project_height = 1080
this.selected_profile = 3031
this.disableArchiveSection()
let myModal = new Modal(this.$refs.newProjectModal)
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() {
if (this.creating_project) {
return
}
this.creating_project = true
let payload = {
"name": this.project_name,
"width": this.project_width,
......@@ -195,10 +255,64 @@ export default {
"json": {}
}
let project = await this.createProject(payload)
this.$refs.CloseProjectModal.click();
if (project && project.id) {
this.$router.push(`/projects/${project.id}`)
try {
let project = await this.createProject(payload)
if (project && project.id) {
await this.maybeLoadArchive(project.id)
this.hideNewProjectModal()
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() {
......@@ -222,7 +336,7 @@ export default {
this.$refs.Close.click();
this.selectedProject = null
},
...mapActions(['loadProjects', 'createProject', 'deleteProject', 'updateProject']),
...mapActions(['loadProjects', 'createProject', 'deleteProject', 'updateProject', 'loadProjectArchive']),
...mapMutations(['setProject', 'clearErrors'])
},
computed: {
......@@ -243,7 +357,7 @@ export default {
button {
margin-left: 5px;
}
.spinner-border {
.project-spinner {
width: 3em!important;
height: 3em!important;
}
......@@ -296,4 +410,15 @@ export default {
min-width: 5em;
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>
......@@ -246,7 +246,7 @@ export default createStore({
dispatch('attachThumbnail', payload)
}
} catch(err) {
commit('addError', err.response.data)
commit('addError', err.response?.data || err.message)
}
},
async getProject({commit}, project_id) {
......@@ -268,6 +268,21 @@ export default createStore({
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) {
try {
await instance.patch(`${payload.url}`, payload)
......@@ -457,6 +472,10 @@ export default createStore({
// Project passed in
objectType = 'project'
}
if (!Obj) {
console.warn('attachThumbnail: missing object data', payload)
return
}
let sessionKey = `${objectType}-${Obj.id}-frame${payload.frame}`
if (payload.clobber) {
......
......@@ -9,7 +9,7 @@
<strong>Password:</strong> demo-password
</p>
<CloudInstancePromo
label="Ready for more?"
label="Unlock the full experience"
title="Launch your own instance"
: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