Web: show a "get the addon" call to action if there are no jobs

If there are no jobs in the database yet, show a "get the addon" call to
action. This includes the current API URL, which can be copied by clicking
on it.

There is no feedback yet that the copy took place, though.
This commit is contained in:
Sybren A. Stüvel 2022-08-02 10:43:08 +02:00
parent 2185d8b5ee
commit 72b994db7d
6 changed files with 73 additions and 2 deletions

View File

@ -644,3 +644,7 @@ span.state-transition-arrow.lazy {
max-height: 100%;
max-width: 100%;
}
.click-to-copy {
cursor: pointer;
}

21
web/app/src/clipboard.js Normal file
View File

@ -0,0 +1,21 @@
/**
* Copy the inner text of an element to the clipboard.
*
* @param {Event } clickEvent the click event that triggered this function call.
*/
export function copyElementText(clickEvent) {
const sourceElement = clickEvent.target;
const inputElement = document.createElement("input");
document.body.appendChild(inputElement);
inputElement.setAttribute("value", sourceElement.innerText);
inputElement.select();
// Note that the `navigator.clipboard` interface is only available when using
// a secure (HTTPS) connection, which Flamenco Manager will likely not have.
// This is why this code falls back to the deprecated `document.execCommand()`
// call.
// Source: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
document.execCommand("copy");
document.body.removeChild(inputElement);
}

View File

@ -0,0 +1,28 @@
<template>
<div class="details-no-item-selected">
<div class="get-the-addon">
<p>Get the Blender add-on and submit a job.</p>
<p><button class="btn btn-primary">Get the add-on!</button></p>
<p>Use the URL below in the add-on preferences. Click on it to copy.</p>
<p class="click-to-copy" title="Click to copy this URL" @click="copyElementText">{{ api() }}</p>
</div>
</div>
</template>
<script setup>
import { api } from '@/urls';
import { copyElementText } from '@/clipboard.js';
</script>
<style scoped>
.get-the-addon {
color: var(--color-text-muted);
display: block;
text-align: center;
}
button {
margin: var(--spacer);
font-size: var(--font-size-lg);
}
</style>

View File

@ -18,6 +18,7 @@ import * as datetime from "@/datetime";
import * as API from '@/manager-api'
import { indicator } from '@/statusindicator';
import { apiClient } from '@/stores/api-query-count';
import { useJobs } from '@/stores/jobs';
import JobActionsBar from '@/components/jobs/JobActionsBar.vue'
import StatusFilterBar from '@/components/StatusFilterBar.vue'
@ -33,6 +34,8 @@ export default {
return {
shownStatuses: [],
availableStatuses: [], // Will be filled after data is loaded from the backend.
jobs: useJobs(),
};
},
mounted() {
@ -127,6 +130,7 @@ export default {
fetchAllJobs() {
const jobsApi = new API.JobsApi(apiClient);
const jobsQuery = {};
this.jobs.isJobless = false;
jobsApi.queryJobs(jobsQuery).then(this.onJobsFetched, function (error) {
// TODO: error handling.
console.error(error);
@ -135,6 +139,8 @@ export default {
onJobsFetched(data) {
// "Down-cast" to JobUpdate to only get those fields, just for debugging things:
// data.jobs = data.jobs.map((j) => API.JobUpdate.constructFromObject(j));
const hasJobs = data && data.jobs && data.jobs.length > 0;
this.jobs.isJobless = !hasJobs;
this.tabulator.setData(data.jobs);
this._refreshAvailableStatuses();

View File

@ -17,6 +17,12 @@ export const useJobs = defineStore('jobs', {
* @type {string}
*/
activeJobID: "",
/**
* Set to true when it is known that there are no jobs at all in the system.
* This is written by the JobsTable.vue component.
*/
isJobless: false,
}),
getters: {
canDelete() {

View File

@ -3,8 +3,12 @@
<jobs-table ref="jobsTable" :activeJobID="jobID" @tableRowClicked="onTableJobClicked" />
</div>
<div class="col col-2 job-details-column" id="col-job-details">
<job-details ref="jobDetails" :jobData="jobs.activeJob" @reshuffled="_recalcTasksTableHeight" />
<tasks-table v-if="hasJobData" ref="tasksTable" :jobID="jobID" :taskID="taskID" @tableRowClicked="onTableTaskClicked" />
<get-the-addon v-if="jobs.isJobless" />
<template v-else>
<job-details ref="jobDetails" :jobData="jobs.activeJob" @reshuffled="_recalcTasksTableHeight" />
<tasks-table v-if="hasJobData" ref="tasksTable" :jobID="jobID" :taskID="taskID"
@tableRowClicked="onTableTaskClicked" />
</template>
</div>
<div class="col col-3">
<task-details v-if="hasJobData" :taskData="tasks.activeTask" @showTaskLogTail="showTaskLogTail" />
@ -37,6 +41,7 @@ import { useTaskLog } from '@/stores/tasklog'
import { apiClient } from '@/stores/api-query-count';
import FooterPopup from '@/components/footer/FooterPopup.vue'
import GetTheAddon from '@/components/GetTheAddon.vue'
import JobDetails from '@/components/jobs/JobDetails.vue'
import JobsTable from '@/components/jobs/JobsTable.vue'
import NotificationBar from '@/components/footer/NotificationBar.vue'
@ -49,6 +54,7 @@ export default {
props: ["jobID", "taskID"], // provided by Vue Router.
components: {
FooterPopup,
GetTheAddon,
JobDetails,
JobsTable,
NotificationBar,