This repo contains code to mirror other repos. It also contains the code that is getting mirrored.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

476 line
12 KiB

  1. # -*- coding: utf-8 -*-
  2. from . import core as html5
  3. from . import utils
  4. class Button(html5.Button):
  5. def __init__(self, txt=None, callback=None, className=None, *args, **kwargs):
  6. super().__init__(*args, **kwargs)
  7. self["class"] = "btn"
  8. if className:
  9. self.addClass(className)
  10. self["type"] = "button"
  11. if txt is not None:
  12. self.setText(txt)
  13. self.callback = callback
  14. self.sinkEvent("onClick")
  15. def setText(self, txt):
  16. if txt is not None:
  17. self.element.innerHTML = txt
  18. self["title"] = txt
  19. else:
  20. self.element.innerHTML = ""
  21. self["title"] = ""
  22. def onClick(self, event):
  23. event.stopPropagation()
  24. event.preventDefault()
  25. if self.callback is not None:
  26. self.callback(self)
  27. class Input(html5.Input):
  28. def __init__(self, type="text", placeholder=None, callback=None, id=None, focusCallback=None, *args, **kwargs):
  29. """
  30. :param type: Input type. Default: "text
  31. :param placeholder: Placeholder text. Default: None
  32. :param callback: Function to be called onChanged: callback(id, value)
  33. :param id: Optional id of the input element. Will be passed to callback
  34. :return:
  35. """
  36. super().__init__(*args, **kwargs)
  37. self["class"] = "input"
  38. self["type"] = type
  39. if placeholder is not None:
  40. self["placeholder"] = placeholder
  41. self.callback = callback
  42. if id is not None:
  43. self["id"] = id
  44. self.sinkEvent("onChange")
  45. self.focusCallback = focusCallback
  46. if focusCallback:
  47. self.sinkEvent("onFocus")
  48. def onChange(self, event):
  49. event.stopPropagation()
  50. event.preventDefault()
  51. if self.callback is not None:
  52. self.callback(self, self["id"], self["value"])
  53. def onFocus(self, event):
  54. event.stopPropagation()
  55. event.preventDefault()
  56. if self.focusCallback is not None:
  57. self.focusCallback(self, self["id"], self["value"])
  58. def onDetach(self):
  59. super().onDetach()
  60. self.callback = None
  61. class Popup(html5.Div):
  62. def __init__(self, title=None, id=None, className=None, icon=None, enableShortcuts=True, closeable=True, *args, **kwargs):
  63. super().__init__("""
  64. <div class="box" [name]="popupBox">
  65. <div class="box-head" [name]="popupHead">
  66. <div class="item" [name]="popupHeadItem">
  67. <div class="item-image">
  68. <i class="i i--small" [name]="popupIcon"></i>
  69. </div>
  70. <div class="item-content">
  71. <div class="item-headline" [name]="popupHeadline"></div>
  72. </div>
  73. </div>
  74. </div>
  75. <div class="box-body box--content" [name]="popupBody"></div>
  76. <div class="box-foot box--content bar" [name]="popupFoot"></div>
  77. </div>
  78. """)
  79. self.appendChild = self.popupBody.appendChild
  80. self.fromHTML = lambda *args, **kwargs: self.popupBody.fromHTML(*args, **kwargs) if kwargs.get("bindTo") else self.popupBody.fromHTML(bindTo=self, *args, **kwargs)
  81. self["class"] = "popup popup--center is-active"
  82. if className:
  83. self.addClass(className)
  84. if closeable:
  85. closeBtn = Button("&times;", self.close, className="item-action")
  86. closeBtn.removeClass("btn")
  87. self.popupHeadItem.appendChild(closeBtn)
  88. if title:
  89. self.popupHeadline.appendChild(title)
  90. if icon:
  91. self.popupIcon.appendChild(icon[0])
  92. elif title:
  93. self.popupIcon.appendChild(title[0])
  94. else:
  95. self.popupIcon.appendChild("Vi") #fixme!!! this _LIBRARY_ is not only used in the Vi...
  96. # id can be used to pass information to callbacks
  97. self.id = id
  98. #FIXME: Implement a global overlay! One popupOverlay next to a list of popups.
  99. self.popupOverlay = html5.Div()
  100. self.popupOverlay["class"] = "popup-overlay is-active"
  101. self.enableShortcuts = enableShortcuts
  102. self.onDocumentKeyDownMethod = None
  103. self.popupOverlay.appendChild(self)
  104. html5.Body().appendChild(self.popupOverlay)
  105. #FIXME: Close/Cancel every popup with click on popupCloseBtn without removing the global overlay.
  106. def onAttach(self):
  107. super(Popup, self).onAttach()
  108. if self.enableShortcuts:
  109. self.onDocumentKeyDownMethod = self.onDocumentKeyDown # safe reference to method
  110. html5.document.addEventListener("keydown", self.onDocumentKeyDownMethod)
  111. def onDetach(self):
  112. super(Popup, self).onDetach()
  113. if self.enableShortcuts:
  114. html5.document.removeEventListener("keydown", self.onDocumentKeyDownMethod)
  115. def onDocumentKeyDown(self, event):
  116. if html5.isEscape(event):
  117. self.close()
  118. def close(self, *args, **kwargs):
  119. html5.Body().removeChild(self.popupOverlay)
  120. self.popupOverlay = None
  121. class InputDialog(Popup):
  122. def __init__(self, text, value="", successHandler=None, abortHandler=None,
  123. successLbl="OK", abortLbl="Cancel", placeholder="", *args, **kwargs):
  124. super().__init__(*args, **kwargs)
  125. self.addClass("popup--inputdialog")
  126. self.sinkEvent("onKeyDown", "onKeyUp")
  127. self.successHandler = successHandler
  128. self.abortHandler = abortHandler
  129. self.fromHTML(
  130. """
  131. <div class="input-group">
  132. <label class="label">
  133. {{text}}
  134. </label>
  135. <input class="input" [name]="inputElem" value="{{value}}" placeholder="{{placeholder}}" />
  136. </div>
  137. """,
  138. vars={
  139. "text": text,
  140. "value": value,
  141. "placeholder": placeholder
  142. }
  143. )
  144. # Cancel
  145. self.popupFoot.appendChild(Button(abortLbl, self.onCancel, className="btn--cancel btn--danger"))
  146. # Okay
  147. self.okayBtn = Button(successLbl, self.onOkay, className="btn--okay btn--primary")
  148. if not value:
  149. self.okayBtn.disable()
  150. self.popupFoot.appendChild(self.okayBtn)
  151. self.inputElem.focus()
  152. def onKeyDown(self, event):
  153. if html5.isReturn(event) and self.inputElem["value"]:
  154. event.stopPropagation()
  155. event.preventDefault()
  156. self.onOkay()
  157. def onKeyUp(self, event):
  158. if self.inputElem["value"]:
  159. self.okayBtn.enable()
  160. else:
  161. self.okayBtn.disable()
  162. def onDocumentKeyDown(self, event):
  163. if html5.isEscape(event):
  164. event.stopPropagation()
  165. event.preventDefault()
  166. self.onCancel()
  167. def onOkay(self, *args, **kwargs):
  168. if self.successHandler:
  169. self.successHandler(self, self.inputElem["value"])
  170. self.close()
  171. def onCancel(self, *args, **kwargs):
  172. if self.abortHandler:
  173. self.abortHandler(self, self.inputElem["value"])
  174. self.close()
  175. class Alert(Popup):
  176. """
  177. Just displaying an alerting message box with OK-button.
  178. """
  179. def __init__(self, msg, title=None, className=None, okCallback=None, okLabel="OK", icon="!", closeable=True, *args, **kwargs):
  180. super().__init__(title, className=None, icon=icon, closeable=closeable, *args, **kwargs)
  181. self.addClass("popup--alert")
  182. if className:
  183. self.addClass(className)
  184. self.okCallback = okCallback
  185. message = html5.Span()
  186. message.addClass("alert-msg")
  187. self.popupBody.appendChild(message)
  188. if isinstance(msg, str):
  189. msg = msg.replace("\n", "<br>")
  190. message.appendChild(msg, bindTo=False)
  191. self.sinkEvent("onKeyDown")
  192. if closeable:
  193. okBtn = Button(okLabel, callback=self.onOkBtnClick)
  194. okBtn.addClass("btn--okay btn--primary")
  195. self.popupFoot.appendChild(okBtn)
  196. okBtn.focus()
  197. def drop(self):
  198. self.okCallback = None
  199. self.close()
  200. def onOkBtnClick(self, sender=None):
  201. if self.okCallback:
  202. self.okCallback(self)
  203. self.drop()
  204. def onKeyDown(self, event):
  205. if html5.isReturn(event):
  206. event.stopPropagation()
  207. event.preventDefault()
  208. self.onOkBtnClick()
  209. class YesNoDialog(Popup):
  210. def __init__(self, question, title=None, yesCallback=None, noCallback=None,
  211. yesLabel="Yes", noLabel="No", icon="?",
  212. closeable=False, *args, **kwargs):
  213. super().__init__(title, closeable=closeable, icon=icon, *args, **kwargs)
  214. self.addClass("popup--yesnodialog")
  215. self.yesCallback = yesCallback
  216. self.noCallback = noCallback
  217. lbl = html5.Span()
  218. lbl["class"].append("question")
  219. self.popupBody.appendChild(lbl)
  220. if isinstance(question, html5.Widget):
  221. lbl.appendChild(question)
  222. else:
  223. utils.textToHtml(lbl, question)
  224. if len(noLabel):
  225. btnNo = Button(noLabel, className="btn--no", callback=self.onNoClicked)
  226. #btnNo["class"].append("btn--no")
  227. self.popupFoot.appendChild(btnNo)
  228. btnYes = Button(yesLabel, callback=self.onYesClicked)
  229. btnYes["class"].append("btn--yes")
  230. self.popupFoot.appendChild(btnYes)
  231. self.sinkEvent("onKeyDown")
  232. btnYes.focus()
  233. def onKeyDown(self, event):
  234. if html5.isReturn(event):
  235. event.stopPropagation()
  236. event.preventDefault()
  237. self.onYesClicked()
  238. def onDocumentKeyDown(self, event):
  239. if html5.isEscape(event):
  240. event.stopPropagation()
  241. event.preventDefault()
  242. self.onNoClicked()
  243. def drop(self):
  244. self.yesCallback = None
  245. self.noCallback = None
  246. self.close()
  247. def onYesClicked(self, *args, **kwargs):
  248. if self.yesCallback:
  249. self.yesCallback(self)
  250. self.drop()
  251. def onNoClicked(self, *args, **kwargs):
  252. if self.noCallback:
  253. self.noCallback(self)
  254. self.drop()
  255. class SelectDialog(Popup):
  256. def __init__(self, prompt, items=None, title=None, okBtn="OK", cancelBtn="Cancel", forceSelect=False,
  257. callback=None, *args, **kwargs):
  258. super().__init__(title, *args, **kwargs)
  259. self["class"].append("popup--selectdialog")
  260. self.callback = callback
  261. self.items = items
  262. assert isinstance(self.items, list)
  263. # Prompt
  264. if prompt:
  265. lbl = html5.Span()
  266. lbl["class"].append("prompt")
  267. if isinstance(prompt, html5.Widget):
  268. lbl.appendChild(prompt)
  269. else:
  270. utils.textToHtml(lbl, prompt)
  271. self.popupBody.appendChild(lbl)
  272. # Items
  273. if not forceSelect and len(items) <= 3:
  274. for idx, item in enumerate(items):
  275. if isinstance(item, dict):
  276. title = item.get("title")
  277. cssc = item.get("class")
  278. elif isinstance(item, tuple):
  279. title = item[1]
  280. cssc = None
  281. else:
  282. title = item
  283. btn = Button(title, callback=self.onAnyBtnClick)
  284. btn.idx = idx
  285. if cssc:
  286. btn.addClass(cssc)
  287. self.popupBody.appendChild(btn)
  288. else:
  289. self.select = html5.Select()
  290. self.popupBody.appendChild(self.select)
  291. for idx, item in enumerate(items):
  292. if isinstance(item, dict):
  293. title = item.get("title")
  294. elif isinstance(item, tuple):
  295. title = item[1]
  296. else:
  297. title = item
  298. opt = html5.Option(title)
  299. opt["value"] = str(idx)
  300. self.select.appendChild(opt)
  301. if okBtn:
  302. self.popupFoot.appendChild(Button(okBtn, callback=self.onOkClick))
  303. if cancelBtn:
  304. self.popupFoot.appendChild(Button(cancelBtn, callback=self.onCancelClick))
  305. def onAnyBtnClick(self, sender):
  306. item = self.items[sender.idx]
  307. if isinstance(item, dict) and item.get("callback") and callable(item["callback"]):
  308. item["callback"](item)
  309. if self.callback:
  310. self.callback(item)
  311. self.items = None
  312. self.close()
  313. def onCancelClick(self, sender=None):
  314. self.close()
  315. def onOkClick(self, sender=None):
  316. assert self.select["selectedIndex"] >= 0
  317. item = self.items[int(self.select.children(self.select["selectedIndex"])["value"])]
  318. if isinstance(item, dict) and item.get("callback") and callable(item["callback"]):
  319. item["callback"](item)
  320. if self.callback:
  321. self.callback(item)
  322. self.items = None
  323. self.select = None
  324. self.close()
  325. class TextareaDialog(Popup):
  326. def __init__(self, text, value="", successHandler=None, abortHandler=None, successLbl="OK", abortLbl="Cancel",
  327. *args, **kwargs):
  328. super().__init__(*args, **kwargs)
  329. self["class"].append("popup--textareadialog")
  330. self.successHandler = successHandler
  331. self.abortHandler = abortHandler
  332. span = html5.Span()
  333. span.element.innerHTML = text
  334. self.popupBody.appendChild(span)
  335. self.inputElem = html5.Textarea()
  336. self.inputElem["value"] = value
  337. self.popupBody.appendChild(self.inputElem)
  338. okayBtn = Button(successLbl, self.onOkay)
  339. okayBtn["class"].append("btn--okay")
  340. self.popupFoot.appendChild(okayBtn)
  341. cancelBtn = Button(abortLbl, self.onCancel)
  342. cancelBtn["class"].append("btn--cancel")
  343. self.popupFoot.appendChild(cancelBtn)
  344. self.sinkEvent("onKeyDown")
  345. self.inputElem.focus()
  346. def onDocumentKeyDown(self, event):
  347. if html5.isEscape(event):
  348. event.stopPropagation()
  349. event.preventDefault()
  350. self.onCancel()
  351. def onOkay(self, *args, **kwargs):
  352. if self.successHandler:
  353. self.successHandler(self, self.inputElem["value"])
  354. self.close()
  355. def onCancel(self, *args, **kwargs):
  356. if self.abortHandler:
  357. self.abortHandler(self, self.inputElem["value"])
  358. self.close()