themes.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. var Themes = () => {
  2. var defaults = {
  3. // storage
  4. themes: [],
  5. // UI
  6. timeout: null,
  7. theme: {},
  8. // static
  9. }
  10. var state = Object.assign({}, defaults)
  11. chrome.runtime.sendMessage({message: 'options.themes'}, (res) => {
  12. Object.assign(state, res)
  13. m.redraw()
  14. })
  15. var events = {
  16. name: (e) => {
  17. state.theme.name = e.target.value
  18. },
  19. url: (e) => {
  20. state.theme.url = e.target.value
  21. },
  22. add: () => {
  23. if (!state.theme.name || !state.theme.url) {
  24. return
  25. }
  26. var all = chrome.runtime.getManifest().web_accessible_resources
  27. .filter((file) => file.indexOf('/themes/') === 0)
  28. .map((file) => file.replace(/\/themes\/(.*)\.css/, '$1'))
  29. .concat(state.themes.map(({name}) => name))
  30. if (all.includes(state.theme.name)) {
  31. return
  32. }
  33. state.themes.push({
  34. name: state.theme.name,
  35. url: state.theme.url,
  36. })
  37. chrome.runtime.sendMessage({
  38. message: 'themes',
  39. themes: state.themes.map(({name, url}) => ({name, url}))
  40. })
  41. state.theme.name = ''
  42. state.theme.url = ''
  43. m.redraw()
  44. },
  45. update: {
  46. name: (theme) => (e) => {
  47. theme.name = e.target.value
  48. clearTimeout(state.timeout)
  49. state.timeout = setTimeout(() => {
  50. chrome.runtime.sendMessage({
  51. message: 'themes',
  52. themes: state.themes.map(({name, url}) => ({name, url}))
  53. })
  54. m.redraw()
  55. }, 750)
  56. },
  57. url: (theme) => (e) => {
  58. theme.url = e.target.value
  59. clearTimeout(state.timeout)
  60. state.timeout = setTimeout(() => {
  61. chrome.runtime.sendMessage({
  62. message: 'themes',
  63. themes: state.themes.map(({name, url}) => ({name, url}))
  64. })
  65. }, 750)
  66. }
  67. },
  68. remove: (theme) => () => {
  69. var index = state.themes.findIndex(({name}) => name === theme.name)
  70. state.themes.splice(index, 1)
  71. chrome.runtime.sendMessage({
  72. message: 'themes',
  73. themes: state.themes.map(({name, url}) => ({name, url}))
  74. })
  75. m.redraw()
  76. }
  77. }
  78. var oncreate = {
  79. ripple: (vnode) => {
  80. mdc.ripple.MDCRipple.attachTo(vnode.dom)
  81. },
  82. textfield: (vnode) => {
  83. mdc.textfield.MDCTextField.attachTo(vnode.dom)
  84. }
  85. }
  86. var onupdate = {
  87. cache: (theme) => (vnode) => {
  88. if (vnode.dom.classList.contains('is-checked') !== state.themes[theme.name].cache) {
  89. vnode.dom.classList.toggle('is-checked')
  90. }
  91. }
  92. }
  93. var render = () =>
  94. m('.bs-callout m-themes',
  95. // add theme
  96. m('.m-add-theme',
  97. m('h4.mdc-typography--headline5', 'Custom Themes'),
  98. // name
  99. m('.mdc-text-field m-textfield m-name', {
  100. oncreate: oncreate.textfield,
  101. },
  102. m('input.mdc-text-field__input', {
  103. type: 'text',
  104. value: state.theme.name,
  105. onchange: events.name,
  106. placeholder: 'Name'
  107. }),
  108. m('.mdc-line-ripple')
  109. ),
  110. // url
  111. m('.mdc-text-field m-textfield m-url', {
  112. oncreate: oncreate.textfield,
  113. },
  114. m('input.mdc-text-field__input', {
  115. type: 'text',
  116. value: state.theme.url,
  117. onchange: events.url,
  118. placeholder: 'URL - file:///home.. | http://localhost..'
  119. }),
  120. m('.mdc-line-ripple')
  121. ),
  122. m('button.mdc-button mdc-button--raised m-button', {
  123. oncreate: oncreate.ripple,
  124. onclick: events.add
  125. },
  126. 'Add'
  127. )
  128. ),
  129. // themes list
  130. (state.themes.length || null) &&
  131. m('ul.m-list', state.themes.map((theme) =>
  132. m('li.mdc-elevation--z2', {
  133. class: theme.expanded ? 'm-expanded' : null,
  134. },
  135. m('.m-summary', {
  136. onclick: (e) => theme.expanded = !theme.expanded
  137. },
  138. m('.m-title', theme.name),
  139. m('i.material-icons', {
  140. class: theme.expanded ? 'icon-arrow-up' : 'icon-arrow-down'
  141. })
  142. ),
  143. m('.m-content',
  144. // name
  145. m('.m-option m-theme',
  146. m('.m-name', m('span', 'Name')),
  147. m('.m-control',
  148. m('.mdc-text-field m-textfield', {
  149. oncreate: oncreate.textfield
  150. },
  151. m('input.mdc-text-field__input', {
  152. type: 'text',
  153. onkeyup: events.update.name(theme),
  154. value: theme.name,
  155. }),
  156. m('.mdc-line-ripple')
  157. )
  158. )
  159. ),
  160. // url
  161. m('.m-option m-theme',
  162. m('.m-name', m('span', 'URL')),
  163. m('.m-control',
  164. m('.mdc-text-field m-textfield', {
  165. oncreate: oncreate.textfield
  166. },
  167. m('input.mdc-text-field__input', {
  168. type: 'text',
  169. onkeyup: events.update.url(theme),
  170. value: theme.url,
  171. }),
  172. m('.mdc-line-ripple')
  173. )
  174. )
  175. ),
  176. // update/remove
  177. m('.m-footer',
  178. m('span',
  179. m('button.mdc-button mdc-button--raised m-button', {
  180. oncreate: oncreate.ripple,
  181. onclick: events.remove(theme)
  182. },
  183. 'Remove'
  184. )
  185. )
  186. )
  187. )
  188. )
  189. ))
  190. )
  191. return {state, render}
  192. }