widgets.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. const shortid = require('shortid')
  2. // Connectors
  3. const cwd = require('./cwd')
  4. const prompts = require('./prompts')
  5. // Utils
  6. const { log } = require('../util/logger')
  7. function getDefaultWidgets () {
  8. return [
  9. {
  10. id: shortid(),
  11. definitionId: 'org.vue.widgets.welcome',
  12. x: 0,
  13. y: 0,
  14. width: 3,
  15. height: 4,
  16. configured: true,
  17. config: null
  18. },
  19. {
  20. id: shortid(),
  21. definitionId: 'org.vue.widgets.kill-port',
  22. x: 3,
  23. y: 0,
  24. width: 2,
  25. height: 1,
  26. configured: true,
  27. config: null
  28. }
  29. ]
  30. }
  31. let widgetDefs = new Map()
  32. let widgetCount = new Map()
  33. let widgets = []
  34. let currentWidget
  35. let loadPromise, loadResolve
  36. function reset (context) {
  37. widgetDefs = new Map()
  38. widgetCount = new Map()
  39. widgets = []
  40. loadPromise = new Promise((resolve) => {
  41. loadResolve = () => {
  42. loadPromise = null
  43. resolve()
  44. log('Load Promise resolved')
  45. }
  46. })
  47. }
  48. async function registerDefinition ({ definition, project }, context) {
  49. definition.hasConfigPrompts = !!definition.onConfigOpen
  50. // Default icon
  51. if (!definition.icon) {
  52. const plugins = require('./plugins')
  53. const plugin = plugins.findOne({ id: definition.pluginId, file: cwd.get() }, context)
  54. const logo = await plugins.getLogo(plugin, context)
  55. if (logo) {
  56. definition.icon = `${logo}?project=${project.id}`
  57. }
  58. }
  59. widgetDefs.set(definition.id, definition)
  60. }
  61. function listDefinitions (context) {
  62. return widgetDefs.values()
  63. }
  64. function findDefinition ({ definitionId }, context) {
  65. const def = widgetDefs.get(definitionId)
  66. if (!def) {
  67. throw new Error(`Widget definition ${definitionId} not found`)
  68. }
  69. return def
  70. }
  71. async function list (context) {
  72. log('loadPromise', loadPromise)
  73. if (loadPromise) {
  74. await loadPromise
  75. }
  76. log('widgets', widgets)
  77. return widgets
  78. }
  79. function load (context) {
  80. const projects = require('./projects')
  81. const id = projects.getCurrent(context).id
  82. const project = context.db.get('projects').find({ id }).value()
  83. widgets = project.widgets
  84. if (!widgets) {
  85. widgets = getDefaultWidgets()
  86. }
  87. widgets.forEach(widget => {
  88. updateCount(widget.definitionId, 1)
  89. })
  90. log('Widgets loaded', widgets.length)
  91. loadResolve()
  92. }
  93. function save (context) {
  94. const projects = require('./projects')
  95. const id = projects.getCurrent(context).id
  96. context.db.get('projects').find({ id }).assign({
  97. widgets
  98. }).write()
  99. }
  100. function canAddMore (definition, context) {
  101. if (definition.maxCount) {
  102. return getCount(definition.id) < definition.maxCount
  103. }
  104. return true
  105. }
  106. function add ({ definitionId }, context) {
  107. const definition = findDefinition({ definitionId }, context)
  108. const { x, y, width, height } = findValidPosition(definition)
  109. const widget = {
  110. id: shortid(),
  111. definitionId,
  112. x,
  113. y,
  114. width,
  115. height,
  116. config: null,
  117. configured: !definition.needsUserConfig
  118. }
  119. // Default config
  120. if (definition.defaultConfig) {
  121. widget.config = definition.defaultConfig({
  122. definition
  123. })
  124. }
  125. updateCount(definitionId, 1)
  126. widgets.push(widget)
  127. save(context)
  128. if (definition.onAdded) {
  129. definition.onAdded({ widget, definition })
  130. }
  131. return widget
  132. }
  133. function getCount (definitionId) {
  134. if (widgetCount.has(definitionId)) {
  135. return widgetCount.get(definitionId)
  136. } else {
  137. return 0
  138. }
  139. }
  140. function updateCount (definitionId, mod) {
  141. widgetCount.set(definitionId, getCount(definitionId) + mod)
  142. }
  143. function findValidPosition (definition, currentWidget = null) {
  144. // Find next available space
  145. const width = (currentWidget && currentWidget.width) || definition.defaultWidth || definition.minWidth
  146. const height = (currentWidget && currentWidget.height) || definition.defaultHeight || definition.minHeight
  147. // Mark occupied positions on the grid
  148. const grid = new Map()
  149. for (const widget of widgets) {
  150. if (widget !== currentWidget) {
  151. for (let x = widget.x; x < widget.x + widget.width; x++) {
  152. for (let y = widget.y; y < widget.y + widget.height; y++) {
  153. grid.set(`${x}:${y}`, true)
  154. }
  155. }
  156. }
  157. }
  158. // Go through the possible positions
  159. let x = 0
  160. let y = 0
  161. while (true) {
  162. // Virtual "line brak"
  163. if (x !== 0 && x + width >= 7) {
  164. x = 0
  165. y++
  166. }
  167. const { result, testX } = hasEnoughSpace(grid, x, y, width, height)
  168. if (!result) {
  169. x = testX + 1
  170. continue
  171. }
  172. // Found! :)
  173. break
  174. }
  175. return {
  176. x,
  177. y,
  178. width,
  179. height
  180. }
  181. }
  182. function hasEnoughSpace (grid, x, y, width, height) {
  183. // Test if enough horizontal available space
  184. for (let testX = x; testX < x + width; testX++) {
  185. // Test if enough vertical available space
  186. for (let testY = y; testY < y + height; testY++) {
  187. if (grid.get(`${testX}:${testY}`)) {
  188. return { result: false, testX }
  189. }
  190. }
  191. }
  192. return { result: true }
  193. }
  194. function findById ({ id }, context) {
  195. return widgets.find(w => w.id === id)
  196. }
  197. function remove ({ id }, context) {
  198. const index = widgets.findIndex(w => w.id === id)
  199. if (index !== -1) {
  200. const widget = widgets[index]
  201. updateCount(widget.definitionId, -1)
  202. widgets.splice(index, 1)
  203. save(context)
  204. const definition = findDefinition(widget, context)
  205. if (definition.onAdded) {
  206. definition.onAdded({ widget, definition })
  207. }
  208. return widget
  209. }
  210. }
  211. function move (input, context) {
  212. const widget = findById(input, context)
  213. if (widget) {
  214. widget.x = input.x
  215. widget.y = input.y
  216. const definition = findDefinition(widget, context)
  217. widget.width = input.width
  218. widget.height = input.height
  219. if (widget.width < definition.minWidth) widget.width = definition.minWidth
  220. if (widget.width > definition.maxWidth) widget.width = definition.maxWidth
  221. if (widget.height < definition.minHeight) widget.height = definition.minHeight
  222. if (widget.height > definition.maxHeight) widget.height = definition.maxHeight
  223. for (const otherWidget of widgets) {
  224. if (otherWidget !== widget) {
  225. if (areOverlapping(otherWidget, widget)) {
  226. const otherDefinition = findDefinition(otherWidget, context)
  227. Object.assign(otherWidget, findValidPosition(otherDefinition, otherWidget))
  228. }
  229. }
  230. }
  231. save(context)
  232. }
  233. return widgets
  234. }
  235. function areOverlapping (widgetA, widgetB) {
  236. return (
  237. widgetA.x + widgetA.width - 1 >= widgetB.x &&
  238. widgetA.x <= widgetB.x + widgetB.width - 1 &&
  239. widgetA.y + widgetA.height - 1 >= widgetB.y &&
  240. widgetA.y <= widgetB.y + widgetB.height - 1
  241. )
  242. }
  243. async function openConfig ({ id }, context) {
  244. const widget = findById({ id }, context)
  245. const definition = findDefinition(widget, context)
  246. if (definition.onConfigOpen) {
  247. const result = await definition.onConfigOpen({
  248. widget,
  249. definition,
  250. context
  251. })
  252. await prompts.reset(widget.config || {})
  253. result.prompts.forEach(prompts.add)
  254. await prompts.start()
  255. currentWidget = widget
  256. }
  257. return widget
  258. }
  259. function getConfigPrompts ({ id }, context) {
  260. return currentWidget && currentWidget.id === id ? prompts.list() : []
  261. }
  262. function saveConfig ({ id }, context) {
  263. const widget = findById({ id }, context)
  264. widget.config = prompts.getAnswers()
  265. widget.configured = true
  266. save(context)
  267. currentWidget = null
  268. return widget
  269. }
  270. function resetConfig ({ id }, context) {
  271. // const widget = findById({ id }, context)
  272. // TODO
  273. save(context)
  274. }
  275. module.exports = {
  276. reset,
  277. registerDefinition,
  278. listDefinitions,
  279. findDefinition,
  280. list,
  281. load,
  282. save,
  283. canAddMore,
  284. getCount,
  285. add,
  286. remove,
  287. move,
  288. openConfig,
  289. getConfigPrompts,
  290. saveConfig,
  291. resetConfig
  292. }