Helios-Core.st 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. Smalltalk current createPackage: 'Helios-Core'!
  2. Object subclass: #HLTab
  3. instanceVariableNames: 'widget label'
  4. package: 'Helios-Core'!
  5. !HLTab methodsFor: 'accessing'!
  6. activate
  7. self manager activate: self
  8. !
  9. add
  10. self manager addTab: self
  11. !
  12. displayLabel
  13. ^ self label size > 20
  14. ifTrue: [ (self label first: 20), '...' ]
  15. ifFalse: [ self label ]
  16. !
  17. focus
  18. self widget canHaveFocus ifTrue: [
  19. self widget focus ]
  20. !
  21. label
  22. ^ label ifNil: [ '' ]
  23. !
  24. label: aString
  25. label := aString
  26. !
  27. manager
  28. ^ HLManager current
  29. !
  30. widget
  31. ^ widget
  32. !
  33. widget: aWidget
  34. widget := aWidget
  35. ! !
  36. !HLTab methodsFor: 'testing'!
  37. isActive
  38. ^ self manager activeTab = self
  39. ! !
  40. !HLTab class methodsFor: 'instance creation'!
  41. on: aWidget labelled: aString
  42. ^ self new
  43. widget: aWidget;
  44. label: aString;
  45. yourself
  46. ! !
  47. Widget subclass: #HLWidget
  48. instanceVariableNames: 'wrapper'
  49. package: 'Helios-Core'!
  50. !HLWidget methodsFor: 'accessing'!
  51. manager
  52. ^ HLManager current
  53. !
  54. wrapper
  55. ^ wrapper
  56. ! !
  57. !HLWidget methodsFor: 'actions'!
  58. alert: aString
  59. window alert: aString
  60. !
  61. confirm: aString
  62. ^ window confirm: aString
  63. ! !
  64. !HLWidget methodsFor: 'keybindings'!
  65. registerBindings
  66. self registerBindingsOn: self manager keyBinder bindings
  67. !
  68. registerBindingsOn: aBindingGroup
  69. ! !
  70. !HLWidget methodsFor: 'rendering'!
  71. renderContentOn: html
  72. !
  73. renderOn: html
  74. self registerBindings.
  75. wrapper := html div.
  76. [ :renderer | self renderContentOn: renderer ] appendToJQuery: wrapper asJQuery
  77. ! !
  78. !HLWidget methodsFor: 'testing'!
  79. canHaveFocus
  80. ^ false
  81. ! !
  82. !HLWidget methodsFor: 'updating'!
  83. refresh
  84. self wrapper ifNil: [ ^ self ].
  85. self wrapper asJQuery empty.
  86. [ :html | self renderContentOn: html ] appendToJQuery: self wrapper asJQuery
  87. ! !
  88. !HLWidget class methodsFor: 'accessing'!
  89. openAsTab
  90. self canBeOpenAsTab ifFalse: [ ^ self ].
  91. HLManager current addTab: (HLTab on: self new labelled: self tabLabel)
  92. !
  93. tabLabel
  94. ^ 'Tab'
  95. !
  96. tabPriority
  97. ^ 500
  98. ! !
  99. !HLWidget class methodsFor: 'testing'!
  100. canBeOpenAsTab
  101. ^ false
  102. ! !
  103. HLWidget subclass: #HLDebugger
  104. instanceVariableNames: ''
  105. package: 'Helios-Core'!
  106. HLWidget subclass: #HLFocusableWidget
  107. instanceVariableNames: 'hiddenInput'
  108. package: 'Helios-Core'!
  109. !HLFocusableWidget methodsFor: 'accessing'!
  110. focusClass
  111. ^ 'focused'
  112. ! !
  113. !HLFocusableWidget methodsFor: 'events'!
  114. blur
  115. hiddenInput asJQuery blur
  116. !
  117. focus
  118. hiddenInput asJQuery focus
  119. !
  120. hasFocus
  121. ^ self wrapper notNil and: [ self wrapper asJQuery hasClass: self focusClass ]
  122. ! !
  123. !HLFocusableWidget methodsFor: 'rendering'!
  124. renderContentOn: html
  125. !
  126. renderHiddenInputOn: html
  127. hiddenInput := html input
  128. style: 'position: absolute; left: -100000px;';
  129. onBlur: [ self wrapper asJQuery removeClass: self focusClass ];
  130. onFocus: [ self wrapper asJQuery addClass: self focusClass ]
  131. !
  132. renderOn: html
  133. self registerBindings.
  134. self renderHiddenInputOn: html.
  135. wrapper := html div
  136. class: 'hl_widget';
  137. onClick: [ hiddenInput asJQuery focus ];
  138. with: [
  139. self renderContentOn: html ]
  140. ! !
  141. !HLFocusableWidget methodsFor: 'testing'!
  142. canHaveFocus
  143. ^ true
  144. ! !
  145. HLFocusableWidget subclass: #HLListWidget
  146. instanceVariableNames: 'items selectedItem mapping'
  147. package: 'Helios-Core'!
  148. !HLListWidget methodsFor: 'accessing'!
  149. cssClassForItem: anObject
  150. ^ self selectedItem = anObject
  151. ifTrue: [ 'active' ]
  152. ifFalse: [ 'inactive' ]
  153. !
  154. iconForItem: anObject
  155. ^ ''
  156. !
  157. items
  158. ^ items ifNil: [ items := self defaultItems ]
  159. !
  160. items: aCollection
  161. items := aCollection
  162. !
  163. positionOf: aListItem
  164. <
  165. return aListItem.parent().children().get().indexOf(aListItem.get(0)) + 1
  166. >
  167. !
  168. selectedItem
  169. ^ selectedItem
  170. !
  171. selectedItem: anObject
  172. selectedItem := anObject
  173. ! !
  174. !HLListWidget methodsFor: 'actions'!
  175. activateFirstListItem
  176. self activateListItem: (window jQuery: ((wrapper asJQuery find: 'li') get: 0))
  177. !
  178. activateItem: anObject
  179. self activateListItem: (mapping
  180. at: anObject
  181. ifAbsent: [ ^ self ]) asJQuery
  182. !
  183. activateListItem: aListItem
  184. | parent position item |
  185. (aListItem get: 0) ifNil: [ ^self ].
  186. position := self positionOf: aListItem.
  187. parent := aListItem parent.
  188. parent children removeClass: 'active'.
  189. aListItem addClass: 'active'.
  190. "Move the scrollbar to show the active element"
  191. aListItem position top < 0 ifTrue: [
  192. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].
  193. aListItem position top + aListItem height > parent height ifTrue: [
  194. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ].
  195. "Activate the corresponding item"
  196. item := (self items at: (aListItem attr: 'list-data') asNumber).
  197. self selectedItem == item ifFalse: [
  198. self selectItem: item ]
  199. !
  200. focus
  201. super focus.
  202. self items isEmpty ifFalse: [
  203. self selectedItem ifNil: [ self activateFirstListItem ] ]
  204. !
  205. selectItem: anObject
  206. self selectedItem: anObject
  207. ! !
  208. !HLListWidget methodsFor: 'defaults'!
  209. defaultItems
  210. ^ #()
  211. ! !
  212. !HLListWidget methodsFor: 'events'!
  213. setupKeyBindings
  214. | next |
  215. hiddenInput asJQuery unbind: 'keydown'.
  216. hiddenInput asJQuery keydown: [ :e | | selected |
  217. selected := window jQuery: '.focused .nav-pills .active'.
  218. e which = 38 ifTrue: [
  219. self activateListItem: selected prev ].
  220. e which = 40 ifTrue: [
  221. next := selected next.
  222. (next get: 0) ifNil: [ next := window jQuery: '.focused .nav-pills li:first-child' ].
  223. self activateListItem: next ] ]
  224. ! !
  225. !HLListWidget methodsFor: 'initialization'!
  226. initialize
  227. super initialize.
  228. mapping := Dictionary new.
  229. ! !
  230. !HLListWidget methodsFor: 'private'!
  231. registerMappingFrom: anObject to: aTag
  232. mapping at: anObject put: aTag
  233. ! !
  234. !HLListWidget methodsFor: 'rendering'!
  235. renderButtonsOn: html
  236. !
  237. renderContentOn: html
  238. html ul
  239. class: 'nav nav-pills nav-stacked';
  240. with: [ self renderListOn: html ].
  241. html div class: 'pane_actions form-actions'; with: [
  242. self renderButtonsOn: html ].
  243. self setupKeyBindings
  244. !
  245. renderItem: anObject on: html
  246. | li |
  247. li := html li.
  248. self registerMappingFrom: anObject to: li.
  249. li
  250. class: (self cssClassForItem: anObject);
  251. at: 'list-data' put: (self items indexOf: anObject) asString;
  252. with: [
  253. html a
  254. with: [
  255. (html tag: 'i') class: (self iconForItem: anObject).
  256. self renderItemLabel: anObject on: html ];
  257. onClick: [
  258. self activateListItem: li asJQuery ] ]
  259. !
  260. renderItemLabel: anObject on: html
  261. html with: anObject asString
  262. !
  263. renderListOn: html
  264. mapping := Dictionary new.
  265. self items do: [ :each |
  266. self renderItem: each on: html ]
  267. ! !
  268. HLListWidget subclass: #HLNavigationListWidget
  269. instanceVariableNames: 'previous next'
  270. package: 'Helios-Core'!
  271. !HLNavigationListWidget methodsFor: 'accessing'!
  272. next
  273. ^ next
  274. !
  275. next: aWidget
  276. next := aWidget.
  277. aWidget previous = self ifFalse: [ aWidget previous: self ]
  278. !
  279. previous
  280. ^ previous
  281. !
  282. previous: aWidget
  283. previous := aWidget.
  284. aWidget next = self ifFalse: [ aWidget next: self ]
  285. ! !
  286. !HLNavigationListWidget methodsFor: 'actions'!
  287. nextFocus
  288. self next ifNotNil: [ self next focus ]
  289. !
  290. previousFocus
  291. self previous ifNotNil: [ self previous focus ]
  292. ! !
  293. !HLNavigationListWidget methodsFor: 'events'!
  294. setupKeyBindings
  295. super setupKeyBindings.
  296. hiddenInput asJQuery keydown: [ :e |
  297. e which = 39 ifTrue: [
  298. self nextFocus ].
  299. e which = 37 ifTrue: [
  300. self previousFocus ] ]
  301. ! !
  302. HLWidget subclass: #HLManager
  303. instanceVariableNames: 'tabs activeTab keyBinder environment history'
  304. package: 'Helios-Core'!
  305. !HLManager methodsFor: 'accessing'!
  306. activeTab
  307. ^ activeTab
  308. !
  309. environment
  310. "The default environment used by all Helios objects"
  311. ^ environment ifNil: [ environment := self defaultEnvironment ]
  312. !
  313. environment: anEnvironment
  314. environment := anEnvironment
  315. !
  316. history
  317. ^ history ifNil: [ history := OrderedCollection new ]
  318. !
  319. history: aCollection
  320. history := aCollection
  321. !
  322. keyBinder
  323. ^ keyBinder ifNil: [ keyBinder := HLKeyBinder new ]
  324. !
  325. tabs
  326. ^ tabs ifNil: [ tabs := OrderedCollection new ]
  327. ! !
  328. !HLManager methodsFor: 'actions'!
  329. activate: aTab
  330. self keyBinder flushBindings.
  331. activeTab := aTab.
  332. self
  333. refresh;
  334. addToHistory: aTab;
  335. show: aTab
  336. !
  337. addTab: aTab
  338. self tabs add: aTab.
  339. self activate: aTab
  340. !
  341. addToHistory: aTab
  342. self removeFromHistory: aTab.
  343. self history add: aTab
  344. !
  345. removeActiveTab
  346. self removeTab: self activeTab
  347. !
  348. removeFromHistory: aTab
  349. self history: (self history reject: [ :each | each == aTab ])
  350. !
  351. removeTab: aTab
  352. (self tabs includes: aTab) ifFalse: [ ^ self ].
  353. self removeFromHistory: aTab.
  354. self tabs remove: aTab.
  355. self keyBinder flushBindings.
  356. self refresh.
  357. self history ifNotEmpty: [
  358. self history last activate ]
  359. ! !
  360. !HLManager methodsFor: 'defaults'!
  361. defaultEnvironment
  362. ^ HLLocalEnvironment new
  363. ! !
  364. !HLManager methodsFor: 'initialization'!
  365. initialize
  366. super initialize.
  367. self keyBinder setupEvents
  368. ! !
  369. !HLManager methodsFor: 'rendering'!
  370. refresh
  371. (window jQuery: '.navbar') remove.
  372. (window jQuery: '#container') remove.
  373. self appendToJQuery: 'body' asJQuery
  374. !
  375. renderAddOn: html
  376. html li
  377. class: 'dropdown';
  378. with: [
  379. html a
  380. class: 'dropdown-toggle';
  381. at: 'data-toggle' put: 'dropdown';
  382. with: [
  383. html with: 'Open...'.
  384. (html tag: 'b') class: 'caret' ].
  385. html ul
  386. class: 'dropdown-menu';
  387. with: [
  388. ((HLWidget withAllSubclasses
  389. select: [ :each | each canBeOpenAsTab ])
  390. sorted: [ :a :b | a tabPriority < b tabPriority ])
  391. do: [ :each |
  392. html li with: [
  393. html a
  394. with: each tabLabel;
  395. onClick: [ each openAsTab ] ] ] ] ]
  396. !
  397. renderContentOn: html
  398. html div
  399. class: 'navbar navbar-fixed-top';
  400. with: [ html div
  401. class: 'navbar-inner';
  402. with: [ self renderTabsOn: html ] ].
  403. html div id: 'container'
  404. !
  405. renderTabsOn: html
  406. html ul
  407. class: 'nav';
  408. with: [
  409. self tabs do: [ :each |
  410. html li
  411. class: (each isActive ifTrue: [ 'active' ] ifFalse: [ 'inactive' ]);
  412. with: [
  413. html a
  414. with: [
  415. ((html tag: 'i') class: 'icon-remove')
  416. onClick: [ self removeTab: each ].
  417. html with: each displayLabel ];
  418. onClick: [ each activate ] ] ].
  419. self renderAddOn: html ]
  420. !
  421. show: aTab
  422. (window jQuery: '#container') empty.
  423. aTab widget appendToJQuery: '#container' asJQuery.
  424. aTab focus
  425. ! !
  426. HLManager class instanceVariableNames: 'current'!
  427. !HLManager class methodsFor: 'accessing'!
  428. current
  429. ^ current ifNil: [ current := self basicNew initialize ]
  430. ! !
  431. !HLManager class methodsFor: 'initialization'!
  432. initialize
  433. self current appendToJQuery: 'body' asJQuery
  434. ! !
  435. !HLManager class methodsFor: 'instance creation'!
  436. new
  437. "Use current instead"
  438. self shouldNotImplement
  439. ! !
  440. HLWidget subclass: #HLSUnit
  441. instanceVariableNames: ''
  442. package: 'Helios-Core'!
  443. !HLSUnit class methodsFor: 'accessing'!
  444. tabLabel
  445. ^ 'SUnit'
  446. !
  447. tabPriority
  448. ^ 1000
  449. ! !
  450. !HLSUnit class methodsFor: 'testing'!
  451. canBeOpenAsTab
  452. ^ true
  453. ! !
  454. HLWidget subclass: #HLTranscript
  455. instanceVariableNames: ''
  456. package: 'Helios-Core'!
  457. !HLTranscript class methodsFor: 'accessing'!
  458. tabLabel
  459. ^ 'Transcript'
  460. !
  461. tabPriority
  462. ^ 600
  463. ! !
  464. !HLTranscript class methodsFor: 'testing'!
  465. canBeOpenAsTab
  466. ^ true
  467. ! !
  468. !Package methodsFor: '*Helios-Core'!
  469. ajaxPutAt: aURL data: aString
  470. jQuery
  471. ajax: aURL
  472. options: #{ 'type' -> 'PUT'.
  473. 'data' -> aString.
  474. 'contentType' -> 'text/plain;charset=UTF-8'.
  475. 'error' -> [:xhr | window
  476. alert: 'Commiting ' , aURL , ' failed with reason: "' , (xhr responseText) , '"'] }
  477. !
  478. heliosCommit
  479. { Exporter -> (self commitPathJs, '/', self name, '.js').
  480. StrippedExporter -> (self commitPathJs, '/', self name, '.deploy.js').
  481. ChunkExporter -> (self commitPathSt, '/', self name, '.st')
  482. } do: [ :commitStrategy || fileContents |
  483. fileContents := (commitStrategy key new exportPackage: self name).
  484. self ajaxPutAt: commitStrategy value data: fileContents ]
  485. ! !