diff --git a/web/app/src/assets/base.css b/web/app/src/assets/base.css
index b2ca0a42..04be3ee6 100644
--- a/web/app/src/assets/base.css
+++ b/web/app/src/assets/base.css
@@ -644,3 +644,7 @@ span.state-transition-arrow.lazy {
max-height: 100%;
max-width: 100%;
}
+
+.click-to-copy {
+ cursor: pointer;
+}
diff --git a/web/app/src/clipboard.js b/web/app/src/clipboard.js
new file mode 100644
index 00000000..7b524656
--- /dev/null
+++ b/web/app/src/clipboard.js
@@ -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);
+}
diff --git a/web/app/src/components/GetTheAddon.vue b/web/app/src/components/GetTheAddon.vue
new file mode 100644
index 00000000..7c4ae6e6
--- /dev/null
+++ b/web/app/src/components/GetTheAddon.vue
@@ -0,0 +1,28 @@
+
+
+
+
Get the Blender add-on and submit a job.
+
+
Use the URL below in the add-on preferences. Click on it to copy.
+
{{ api() }}
+
+
+
+
+
+
+
diff --git a/web/app/src/components/jobs/JobsTable.vue b/web/app/src/components/jobs/JobsTable.vue
index e9b1713f..2c2299e9 100644
--- a/web/app/src/components/jobs/JobsTable.vue
+++ b/web/app/src/components/jobs/JobsTable.vue
@@ -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();
diff --git a/web/app/src/stores/jobs.js b/web/app/src/stores/jobs.js
index 6dc650dc..bd3c4ba4 100644
--- a/web/app/src/stores/jobs.js
+++ b/web/app/src/stores/jobs.js
@@ -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() {
diff --git a/web/app/src/views/JobsView.vue b/web/app/src/views/JobsView.vue
index dde2e913..3e76ecb7 100644
--- a/web/app/src/views/JobsView.vue
+++ b/web/app/src/views/JobsView.vue
@@ -3,8 +3,12 @@
-
-
+
+
+
+
+
@@ -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,