ScriptDialog.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /*
  2. * ScriptDialog.js
  3. *
  4. * @author realor
  5. */
  6. import { Dialog } from './Dialog.js'
  7. import { Controls } from './Controls.js'
  8. import { GeometryUtils } from '../utils/GeometryUtils.js'
  9. import { ObjectUtils } from '../utils/ObjectUtils.js'
  10. import { MessageDialog } from '../ui/MessageDialog.js'
  11. import { ConfirmDialog } from '../ui/ConfirmDialog.js'
  12. import { Toast } from '../ui/Toast.js'
  13. import { Solid } from '../core/Solid.js'
  14. import { SolidGeometry } from '../core/SolidGeometry.js'
  15. import { Cord } from '../core/Cord.js'
  16. import { CordGeometry } from '../core/CordGeometry.js'
  17. import { Profile } from '../core/Profile.js'
  18. import { ProfileGeometry } from '../core/ProfileGeometry.js'
  19. import { I18N } from '../i18n/I18N.js'
  20. import { Metadata, Result } from '../io/FileService.js'
  21. import * as THREE from '../lib/three.module.js'
  22. class ScriptDialog extends Dialog {
  23. static GLOBALS = {
  24. THREE,
  25. ObjectUtils,
  26. GeometryUtils,
  27. Solid,
  28. SolidGeometry,
  29. Cord,
  30. CordGeometry,
  31. Profile,
  32. ProfileGeometry,
  33. Dialog,
  34. MessageDialog,
  35. ConfirmDialog,
  36. Controls,
  37. Toast
  38. }
  39. constructor(application, saveAction) {
  40. super('title.script_editor')
  41. this.application = application
  42. this.setI18N(this.application.i18n)
  43. this.scriptName = ''
  44. this.scriptCode = ''
  45. this.saved = true
  46. this.setSize(760, 600)
  47. const editorHeight = 75
  48. const consoleHeight = 100 - editorHeight
  49. this.nameField = this.addTextField('name', 'tool.script.name', '', 'script_name')
  50. this.editorView = this.addCodeEditor('editor', 'label.formula.expression', '', { language: 'javascript', height: 'calc(' + editorHeight + '% - 38px)' })
  51. this.consoleElem = document.createElement('div')
  52. this.consoleElem.className = 'console'
  53. this.consoleElem.style.height = consoleHeight + '%'
  54. this.bodyElem.appendChild(this.consoleElem)
  55. this.runButton = this.addButton('run', 'button.run', () => {
  56. this.endEdition()
  57. this.run()
  58. })
  59. this.saveButton = this.addButton('save', 'button.save', () => {
  60. this.endEdition()
  61. saveAction(this.scriptName, this.scriptCode)
  62. })
  63. this.saveButton.style.display = saveAction ? '' : 'none'
  64. this.closeButton = this.addButton('cancel', 'button.close', () => {
  65. this.endEdition()
  66. this.hide()
  67. })
  68. this.nameField.addEventListener('input', () => {
  69. this.saveButton.disabled = this.nameField.value.trim().length === 0
  70. })
  71. // Make classes needed for scripting always global.
  72. for (let name in ScriptDialog.GLOBALS) {
  73. window[name] = ScriptDialog.GLOBALS[name]
  74. }
  75. }
  76. onShow() {
  77. this.nameField.value = this.scriptName
  78. const state = this.editorView.state
  79. const tx = state.update({ changes: { from: 0, to: state.doc.length, insert: this.scriptCode } })
  80. this.editorView.dispatch(tx)
  81. this.saveButton.disabled = this.nameField.value.trim().length === 0
  82. if (this.scriptName === '') {
  83. this.nameField.focus()
  84. } else {
  85. this.editorView.focus()
  86. }
  87. }
  88. clearConsole() {
  89. this.consoleElem.innerHTML = ''
  90. }
  91. run() {
  92. let error = null
  93. this.enterConsole()
  94. try {
  95. this.consoleElem.innerHTML = ''
  96. const fn = new Function(this.scriptCode)
  97. let t0 = Date.now()
  98. let result = fn()
  99. let t1 = Date.now()
  100. if (result instanceof Dialog) {
  101. result.show()
  102. } else {
  103. this.log('info', 'Execution completed in ' + (t1 - t0) + ' ms.')
  104. if (result !== undefined) this.log('info', 'Result: ' + result)
  105. Toast.create('message.script_executed')
  106. .setI18N(this.application.i18n)
  107. .show()
  108. }
  109. } catch (ex) {
  110. this.log('error', ex)
  111. error = ex
  112. } finally {
  113. this.exitConsole()
  114. }
  115. return error
  116. }
  117. endEdition() {
  118. this.scriptName = this.nameField.value
  119. let code = this.editorView.state.doc.toString()
  120. if (code !== this.scriptCode) {
  121. this.scriptCode = code
  122. this.saved = false
  123. }
  124. }
  125. log(className, ...args) {
  126. for (let arg of args) {
  127. let message = document.createElement('div')
  128. message.className = className
  129. message.innerHTML = String(arg)
  130. this.consoleElem.appendChild(message)
  131. }
  132. }
  133. enterConsole() {
  134. this.console = console
  135. window.console = {
  136. log: (...args) => this.log('info', ...args),
  137. info: (...args) => this.log('info', ...args),
  138. warn: (...args) => this.log('warn', ...args),
  139. error: (...args) => this.log('error', ...args)
  140. }
  141. }
  142. exitConsole() {
  143. window.console = this.console
  144. }
  145. }
  146. export { ScriptDialog }