ActionButton.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import { marshalLogsJsonToHtml } from './marshaller.js'
  2. import './ArgumentForm.js'
  3. class ActionButton extends window.HTMLElement {
  4. constructFromJson (json) {
  5. this.updateIterationTimestamp = 0
  6. this.constructDomFromTemplate()
  7. // Class attributes
  8. this.temporaryStatusMessage = null
  9. this.isWaiting = false
  10. this.actionCallUrl = window.restBaseUrl + 'StartAction'
  11. this.updateFromJson(json)
  12. // DOM Attributes
  13. this.setAttribute('role', 'none')
  14. this.btn.title = json.title
  15. this.btn.onclick = () => {
  16. if (json.arguments.length > 0) {
  17. const frm = document.createElement('argument-form')
  18. frm.setup(json, (args) => {
  19. this.startAction(args)
  20. })
  21. document.body.appendChild(frm)
  22. } else {
  23. this.startAction()
  24. }
  25. }
  26. this.updateFromJson(json)
  27. this.updateDom()
  28. this.setAttribute('id', 'actionButton_' + json.id)
  29. }
  30. updateFromJson (json) {
  31. // Fields that should not be updated
  32. //
  33. // title - as the callback URL relies on it
  34. // actionCallbackUrl - as it's based on the title
  35. // temporaryStatusMessage - as the button might be "waiting" on execution to finish while it's being updated.
  36. if (json.icon === '') {
  37. this.unicodeIcon = '&#x1f4a9'
  38. } else {
  39. this.unicodeIcon = unescape(json.icon)
  40. }
  41. }
  42. startAction (actionArgs) {
  43. this.btn.disabled = true
  44. this.isWaiting = true
  45. this.updateDom()
  46. this.btn.classList = [] // Removes old animation classes
  47. if (actionArgs === undefined) {
  48. actionArgs = []
  49. }
  50. const startActionArgs = {
  51. actionName: this.btn.title,
  52. arguments: actionArgs
  53. }
  54. window.fetch(this.actionCallUrl, {
  55. method: 'POST',
  56. headers: {
  57. 'Content-Type': 'application/json'
  58. },
  59. body: JSON.stringify(startActionArgs)
  60. }).then((res) => {
  61. if (res.ok) {
  62. return res.json()
  63. } else {
  64. throw new Error(res.statusText)
  65. }
  66. }
  67. ).then((json) => {
  68. marshalLogsJsonToHtml({ logs: [json.logEntry] })
  69. if (json.logEntry.timedOut) {
  70. this.onActionResult('action-timeout', 'Timed out')
  71. } else if (json.logEntry.exitCode === -1337) {
  72. this.onActionError('Error')
  73. } else if (json.logEntry.exitCode !== 0) {
  74. this.onActionResult('action-nonzero-exit', 'Exit code ' + json.logEntry.exitCode)
  75. } else {
  76. this.onActionResult('action-success', 'Success!')
  77. }
  78. }).catch(err => {
  79. this.onActionError(err)
  80. })
  81. }
  82. onActionResult (cssClass, temporaryStatusMessage) {
  83. this.btn.disabled = false
  84. this.temporaryStatusMessage = '[ ' + temporaryStatusMessage + ' ]'
  85. this.updateDom()
  86. this.btn.classList.add(cssClass)
  87. setTimeout(() => {
  88. this.btn.classList.remove(cssClass)
  89. }, 1000)
  90. }
  91. onActionError (err) {
  92. console.error('callback error', err)
  93. this.btn.disabled = false
  94. this.isWaiting = false
  95. this.updateDom()
  96. this.btn.classList.add('action-failed')
  97. setTimeout(() => {
  98. this.btn.classList.remove('action-failed')
  99. }, 1000)
  100. }
  101. constructDomFromTemplate () {
  102. const tpl = document.getElementById('tplActionButton')
  103. const content = tpl.content.cloneNode(true)
  104. /*
  105. * FIXME: Should probably be using a shadowdom here, but seem to
  106. * get an error when combined with custom elements.
  107. */
  108. this.appendChild(content)
  109. this.btn = this.querySelector('button')
  110. this.domTitle = this.btn.querySelector('.title')
  111. this.domIcon = this.btn.querySelector('.icon')
  112. }
  113. updateDom () {
  114. if (this.temporaryStatusMessage != null) {
  115. this.domTitle.innerText = this.temporaryStatusMessage
  116. this.domTitle.classList.add('temporary-status-message')
  117. this.isWaiting = false
  118. this.disabled = false
  119. setTimeout(() => {
  120. this.temporaryStatusMessage = null
  121. this.domTitle.classList.remove('temporary-status-message')
  122. this.updateDom()
  123. }, 2000)
  124. } else if (this.isWaiting) {
  125. this.domTitle.innerText = 'Waiting...'
  126. } else {
  127. this.domTitle.innerText = this.btn.title
  128. }
  129. this.domIcon.innerHTML = this.unicodeIcon
  130. }
  131. }
  132. window.customElements.define('action-button', ActionButton)