From 72b994db7dd0dd99bb30ec14087c44f6ddb023e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 2 Aug 2022 10:43:08 +0200 Subject: [PATCH] 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. --- web/app/src/assets/base.css | 4 ++++ web/app/src/clipboard.js | 21 +++++++++++++++++ web/app/src/components/GetTheAddon.vue | 28 +++++++++++++++++++++++ web/app/src/components/jobs/JobsTable.vue | 6 +++++ web/app/src/stores/jobs.js | 6 +++++ web/app/src/views/JobsView.vue | 10 ++++++-- 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 web/app/src/clipboard.js create mode 100644 web/app/src/components/GetTheAddon.vue 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 @@ + + + + + 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,