Controls.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /**
  2. * Controls.js
  3. *
  4. * @author realor
  5. */
  6. import '../lib/codemirror.js'
  7. import { I18N } from '../i18n/I18N.js'
  8. class Controls {
  9. static nextId = 0
  10. static addText(parent, text, className) {
  11. const textElem = document.createElement('span')
  12. I18N.set(textElem, 'innerHTML', text)
  13. if (className) textElem.className = className
  14. parent.appendChild(textElem)
  15. return textElem
  16. }
  17. static addTextWithArgs(parent, text, args = [], className) {
  18. const textElem = document.createElement('span')
  19. I18N.set(textElem, 'innerHTML', text, ...args)
  20. if (className) textElem.className = className
  21. parent.appendChild(textElem)
  22. return textElem
  23. }
  24. static addCode(parent, text, className) {
  25. const textElem = document.createElement('pre')
  26. textElem.innerHTML = text
  27. if (className) textElem.className = className
  28. parent.appendChild(textElem)
  29. return textElem
  30. }
  31. static addLink(parent, label, url, title, className, action) {
  32. const linkElem = document.createElement('a')
  33. parent.appendChild(linkElem)
  34. if (className) linkElem.className = className
  35. if (label) I18N.set(linkElem, 'innerHTML', label)
  36. if (url) linkElem.href = url
  37. if (title) {
  38. I18N.set(linkElem, 'title', title)
  39. I18N.set(linkElem, 'alt', title)
  40. }
  41. if (action) {
  42. linkElem.addEventListener('click', action, false)
  43. }
  44. return linkElem
  45. }
  46. static addTextField(parent, name, label, value, className) {
  47. return Controls.addInputField(parent, 'text', name, label, value, className)
  48. }
  49. static addNumberField(parent, name, label, value, className) {
  50. return Controls.addInputField(parent, 'number', name, label, value, className)
  51. }
  52. static addPasswordField(parent, name, label, value, className) {
  53. return Controls.addInputField(parent, 'password', name, label, value, className)
  54. }
  55. static addDateField(parent, name, label, value, className) {
  56. return Controls.addInputField(parent, 'date', name, label, value, className)
  57. }
  58. static addColorField(parent, name, label, value, className) {
  59. return Controls.addInputField(parent, 'color', name, label, value, className)
  60. }
  61. static addCheckBoxField(parent, name, label, checked, className) {
  62. const id = this.getNextId()
  63. const groupElem = Controls.addField(parent, id, label, className)
  64. const labelElem = groupElem.childNodes[0]
  65. const inputElem = document.createElement('input')
  66. inputElem.id = id
  67. inputElem.name = name
  68. inputElem.type = 'checkbox'
  69. inputElem.checked = Boolean(checked)
  70. groupElem.insertBefore(inputElem, labelElem)
  71. return inputElem
  72. }
  73. static addInputField(parent, type, name, label, value, className) {
  74. const id = this.getNextId()
  75. const groupElem = Controls.addField(parent, id, label, className)
  76. const inputElem = document.createElement('input')
  77. inputElem.id = id
  78. inputElem.name = name
  79. inputElem.type = type || 'text'
  80. if (value !== undefined && value !== null) inputElem.value = value
  81. groupElem.appendChild(inputElem)
  82. return inputElem
  83. }
  84. static addTextAreaField(parent, name, label, value, className) {
  85. const id = this.getNextId()
  86. const groupElem = Controls.addField(parent, id, label, className)
  87. const textAreaElem = document.createElement('textarea')
  88. textAreaElem.id = id
  89. textAreaElem.name = name
  90. if (value) textAreaElem.value = value
  91. groupElem.appendChild(textAreaElem)
  92. return textAreaElem
  93. }
  94. static addSelectField(parent, name, label, options, value, className) {
  95. const id = this.getNextId()
  96. const groupElem = Controls.addField(parent, id, label, className)
  97. const selectElem = document.createElement('select')
  98. selectElem.id = id
  99. selectElem.name = name
  100. groupElem.appendChild(selectElem)
  101. if (options) {
  102. Controls.setSelectOptions(selectElem, options)
  103. }
  104. if (value) {
  105. Controls.setSelectValue(selectElem, value)
  106. }
  107. return selectElem
  108. }
  109. static setSelectOptions(selectElem, options, value = selectElem.value, create) {
  110. selectElem.innerHTML = ''
  111. if (options) {
  112. for (let i = 0; i < options.length; i++) {
  113. let option = options[i]
  114. let optionElem = document.createElement('option')
  115. if (option instanceof Array) {
  116. optionElem.value = option[0]
  117. I18N.set(optionElem, 'innerHTML', option[1])
  118. } else {
  119. optionElem.value = option
  120. I18N.set(optionElem, 'innerHTML', option)
  121. }
  122. selectElem.appendChild(optionElem)
  123. }
  124. if (value) {
  125. Controls.setSelectValue(selectElem, value, create)
  126. }
  127. }
  128. }
  129. static setSelectValue(selectElem, value = selectElem.value, create) {
  130. let found = false
  131. for (let option of selectElem.options) {
  132. if (option.value === value) {
  133. found = true
  134. break
  135. }
  136. }
  137. if (found) {
  138. selectElem.value = value
  139. } else if (value && create) {
  140. let optionElem = document.createElement('option')
  141. optionElem.value = value
  142. optionElem.innerHTML = value
  143. selectElem.appendChild(optionElem)
  144. selectElem.value = value
  145. } else if (selectElem.options.length > 0) {
  146. // select first
  147. selectElem.value = selectElem.options[0].value
  148. }
  149. }
  150. static addRadioButtons(parent, name, label, options, value, className, clickListener) {
  151. const groupElem = document.createElement('fieldset')
  152. parent.appendChild(groupElem)
  153. if (className) groupElem.className = className
  154. if (label) {
  155. const legendElem = document.createElement('legend')
  156. groupElem.appendChild(legendElem)
  157. I18N.set(legendElem, 'innerHTML', label)
  158. }
  159. const id = this.getNextId()
  160. const hiddenElem = document.createElement('input')
  161. hiddenElem.type = 'hidden'
  162. hiddenElem.id = id
  163. groupElem.appendChild(hiddenElem)
  164. for (let i = 0; i < options.length; i++) {
  165. let option = options[i]
  166. let radioElem = document.createElement('input')
  167. radioElem.id = id + '_' + i
  168. radioElem.type = 'radio'
  169. radioElem.name = name
  170. radioElem.value = option instanceof Array ? option[0] : option
  171. if (value === radioElem.value) {
  172. radioElem.checked = true
  173. hiddenElem.value = radioElem.value
  174. }
  175. radioElem.addEventListener(
  176. 'click',
  177. function(event) {
  178. let elem = event.target || event.srcElement
  179. hiddenElem.value = elem.value
  180. if (clickListener) clickListener(event)
  181. },
  182. false
  183. )
  184. let labelElem = document.createElement('label')
  185. let spanElem = document.createElement('span')
  186. I18N.set(spanElem, 'innerHTML', option instanceof Array ? option[1] : option)
  187. labelElem.htmlFor = radioElem.id
  188. labelElem.appendChild(radioElem)
  189. labelElem.appendChild(spanElem)
  190. groupElem.appendChild(labelElem)
  191. }
  192. return hiddenElem
  193. }
  194. static addButton(parent, name, label, action, className) {
  195. const buttonElem = document.createElement('button')
  196. buttonElem.name = name
  197. I18N.set(buttonElem, 'innerHTML', label)
  198. if (className) buttonElem.className = className
  199. buttonElem.addEventListener('click', event => action(event), false)
  200. parent.appendChild(buttonElem)
  201. return buttonElem
  202. }
  203. static addImageButton(parent, name, label, action, className) {
  204. const buttonElem = document.createElement('button')
  205. buttonElem.name = name
  206. I18N.set(buttonElem, 'title', label)
  207. I18N.set(buttonElem, 'alt', label)
  208. if (className) buttonElem.className = className
  209. buttonElem.addEventListener('click', event => action(event), false)
  210. parent.appendChild(buttonElem)
  211. return buttonElem
  212. }
  213. static addCodeEditor(parent, name, label, value = '', options = {}) {
  214. const id = this.getNextId()
  215. const groupElem = Controls.addField(parent, id, label, 'code_editor')
  216. if (options.height) {
  217. groupElem.style.height = options.height
  218. }
  219. const editorElem = document.createElement('div')
  220. editorElem.id = id
  221. editorElem.className = 'cm-editor-holder'
  222. groupElem.appendChild(editorElem)
  223. const { basicSetup } = CM['@codemirror/basic-setup']
  224. const { keymap, highlightSpecialChars, highlightActiveLine, drawSelection, EditorView } = CM['@codemirror/view']
  225. const { lineNumbers, highlightActiveLineGutter } = CM['@codemirror/gutter']
  226. const { history, historyKeymap } = CM['@codemirror/history']
  227. const { defaultKeymap } = CM['@codemirror/commands']
  228. const { bracketMatching } = CM['@codemirror/matchbrackets']
  229. const { foldGutter, foldKeymap } = CM['@codemirror/fold']
  230. const { javascript, javascriptLanguage } = CM['@codemirror/lang-javascript']
  231. const { json, jsonLanguage } = CM['@codemirror/lang-json']
  232. const { defaultHighlightStyle } = CM['@codemirror/highlight']
  233. const { searchKeymap, highlightSelectionMatches } = CM['@codemirror/search']
  234. const { indentOnInput } = CM['@codemirror/language']
  235. const { EditorState } = CM['@codemirror/state']
  236. let theme = EditorView.theme({
  237. '&.cm-focused .cm-cursor': {
  238. borderLeftColor: '#000',
  239. borderLeftWidth: '2px'
  240. },
  241. '&.cm-focused .cm-matchingBracket': {
  242. backgroundColor: 'yellow',
  243. color: 'black'
  244. },
  245. '& .ͼa': {
  246. color: '#444',
  247. fontWeight: 'bold'
  248. },
  249. '& .ͼl': {
  250. color: '#808080'
  251. },
  252. '& .ͼf': {
  253. color: '#8080e0'
  254. },
  255. '& .ͼd': {
  256. color: '#2020ff'
  257. },
  258. '& .ͼb': {
  259. color: '#008000'
  260. },
  261. '& .cm-wrap': {
  262. height: '100%'
  263. },
  264. '& .cm-scroller': {
  265. overflow: 'auto'
  266. }
  267. })
  268. this.editorView = new EditorView({
  269. parent: editorElem
  270. })
  271. const extensions = [
  272. lineNumbers(),
  273. highlightActiveLineGutter(),
  274. highlightSpecialChars(),
  275. history(),
  276. foldGutter(),
  277. drawSelection(),
  278. EditorState.allowMultipleSelections.of(true),
  279. indentOnInput(),
  280. defaultHighlightStyle.fallback,
  281. bracketMatching(),
  282. highlightActiveLine(),
  283. highlightSelectionMatches(),
  284. keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap]),
  285. options.language === 'javascript' ? javascript() : json(),
  286. theme
  287. ]
  288. let editorState = EditorState.create({
  289. doc: value || '',
  290. extensions: extensions
  291. })
  292. this.editorView.setState(editorState)
  293. return this.editorView
  294. }
  295. static addField(parent, id, label, className) {
  296. const groupElem = document.createElement('div')
  297. if (className) groupElem.className = className
  298. parent.appendChild(groupElem)
  299. const labelElem = document.createElement('label')
  300. labelElem.htmlFor = id
  301. I18N.set(labelElem, 'innerHTML', label)
  302. groupElem.appendChild(labelElem)
  303. return groupElem
  304. }
  305. static addTable(parent, name, columns, className) {
  306. const tableElem = document.createElement('table')
  307. parent.appendChild(tableElem)
  308. tableElem.id = name
  309. if (className) tableElem.className = className
  310. const headElem = document.createElement('thead')
  311. tableElem.appendChild(headElem)
  312. const bodyElem = document.createElement('tbody')
  313. tableElem.appendChild(bodyElem)
  314. const footElem = document.createElement('tfoot')
  315. tableElem.appendChild(footElem)
  316. if (columns) {
  317. const headRowElem = document.createElement('tr')
  318. headElem.appendChild(headRowElem)
  319. for (let i = 0; i < columns.length; i++) {
  320. const headColElem = document.createElement('th')
  321. headRowElem.appendChild(headColElem)
  322. I18N.set(headColElem, 'innerHTML', columns[i])
  323. headColElem.className = 'col_' + i
  324. }
  325. }
  326. return tableElem
  327. }
  328. static addTableRow(tableElem) {
  329. const columns = tableElem.tHead.children[0].children.length
  330. const bodyElem = tableElem.tBodies[0]
  331. const rowElem = document.createElement('tr')
  332. bodyElem.appendChild(rowElem)
  333. for (let i = 0; i < columns; i++) {
  334. let colElem = document.createElement('td')
  335. rowElem.appendChild(colElem)
  336. }
  337. return rowElem
  338. }
  339. static getNextId() {
  340. return 'f' + this.nextId++
  341. }
  342. }
  343. export { Controls }