icons.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /**
  2. * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: AGPL-3.0-or-later
  4. */
  5. /* eslint-disable quote-props */
  6. /* eslint-disable n/no-unpublished-import */
  7. import path from 'path'
  8. import fs from 'fs'
  9. import sass from 'sass'
  10. const colors = {
  11. dark: '000',
  12. white: 'fff',
  13. // gold but for backwards compatibility called yellow
  14. yellow: 'a08b00',
  15. red: 'e9322d',
  16. orange: 'eca700',
  17. green: '46ba61',
  18. grey: '969696',
  19. }
  20. const variables = {}
  21. const icons = {
  22. 'add': path.join(__dirname, '../img', 'actions', 'add.svg'),
  23. 'address': path.join(__dirname, '../img', 'actions', 'address.svg'),
  24. 'alert-outline': path.join(__dirname, '../img', 'actions', 'alert-outline.svg'),
  25. 'audio-off': path.join(__dirname, '../img', 'actions', 'audio-off.svg'),
  26. 'audio': path.join(__dirname, '../img', 'actions', 'audio.svg'),
  27. 'calendar': path.join(__dirname, '../img', 'places', 'calendar.svg'),
  28. 'caret': path.join(__dirname, '../img', 'actions', 'caret.svg'),
  29. 'category-app-bundles': path.join(__dirname, '../img', 'categories', 'bundles.svg'),
  30. 'category-auth': path.join(__dirname, '../img', 'categories', 'auth.svg'),
  31. 'category-customization': path.join(__dirname, '../img', 'categories', 'customization.svg'),
  32. 'category-dashboard': path.join(__dirname, '../img', 'categories', 'dashboard.svg'),
  33. 'category-files': path.join(__dirname, '../img', 'categories', 'files.svg'),
  34. 'category-games': path.join(__dirname, '../img', 'categories', 'games.svg'),
  35. 'category-integration': path.join(__dirname, '../img', 'categories', 'integration.svg'),
  36. 'category-monitoring': path.join(__dirname, '../img', 'categories', 'monitoring.svg'),
  37. 'category-multimedia': path.join(__dirname, '../img', 'categories', 'multimedia.svg'),
  38. 'category-office': path.join(__dirname, '../img', 'categories', 'office.svg'),
  39. 'category-organization': path.join(__dirname, '../img', 'categories', 'organization.svg'),
  40. 'category-social': path.join(__dirname, '../img', 'categories', 'social.svg'),
  41. 'category-workflow': path.join(__dirname, '../img', 'categories', 'workflow.svg'),
  42. 'change': path.join(__dirname, '../img', 'actions', 'change.svg'),
  43. 'checkmark': path.join(__dirname, '../img', 'actions', 'checkmark.svg'),
  44. 'circles': path.join(__dirname, '../img', 'apps', 'circles.svg'),
  45. 'clippy': path.join(__dirname, '../img', 'actions', 'clippy.svg'),
  46. 'close': path.join(__dirname, '../img', 'actions', 'close.svg'),
  47. 'comment': path.join(__dirname, '../img', 'actions', 'comment.svg'),
  48. 'confirm-fade': path.join(__dirname, '../img', 'actions', 'confirm-fade.svg'),
  49. 'confirm': path.join(__dirname, '../img', 'actions', 'confirm.svg'),
  50. 'contacts': path.join(__dirname, '../img', 'places', 'contacts.svg'),
  51. 'delete': path.join(__dirname, '../img', 'actions', 'delete.svg'),
  52. 'desktop': path.join(__dirname, '../img', 'clients', 'desktop.svg'),
  53. 'details': path.join(__dirname, '../img', 'actions', 'details.svg'),
  54. 'disabled-user': path.join(__dirname, '../img', 'actions', 'disabled-user.svg'),
  55. 'disabled-users': path.join(__dirname, '../img', 'actions', 'disabled-users.svg'),
  56. 'download': path.join(__dirname, '../img', 'actions', 'download.svg'),
  57. 'edit': path.join(__dirname, '../img', 'actions', 'edit.svg'),
  58. 'encryption': path.join(__dirname, '../../', 'apps/files_external/img', 'app.svg'),
  59. 'error': path.join(__dirname, '../img', 'actions', 'error.svg'),
  60. 'external': path.join(__dirname, '../img', 'actions', 'external.svg'),
  61. 'favorite': path.join(__dirname, '../img', 'actions', 'star-dark.svg'),
  62. 'files': path.join(__dirname, '../img', 'places', 'files.svg'),
  63. 'filter': path.join(__dirname, '../img', 'actions', 'filter.svg'),
  64. 'folder': path.join(__dirname, '../img', 'filetypes', 'folder.svg'),
  65. 'fullscreen': path.join(__dirname, '../img', 'actions', 'fullscreen.svg'),
  66. 'group': path.join(__dirname, '../img', 'actions', 'group.svg'),
  67. 'history': path.join(__dirname, '../img', 'actions', 'history.svg'),
  68. 'home': path.join(__dirname, '../img', 'places', 'home.svg'),
  69. 'info': path.join(__dirname, '../img', 'actions', 'info.svg'),
  70. 'link': path.join(__dirname, '../img', 'places', 'link.svg'),
  71. 'logout': path.join(__dirname, '../img', 'actions', 'logout.svg'),
  72. 'mail': path.join(__dirname, '../img', 'actions', 'mail.svg'),
  73. 'menu-sidebar': path.join(__dirname, '../img', 'actions', 'menu-sidebar.svg'),
  74. 'menu': path.join(__dirname, '../img', 'actions', 'menu.svg'),
  75. 'more': path.join(__dirname, '../img', 'actions', 'more.svg'),
  76. 'music': path.join(__dirname, '../img', 'places', 'music.svg'),
  77. 'password': path.join(__dirname, '../img', 'actions', 'password.svg'),
  78. 'pause': path.join(__dirname, '../img', 'actions', 'pause.svg'),
  79. 'phone': path.join(__dirname, '../img', 'clients', 'phone.svg'),
  80. 'picture': path.join(__dirname, '../img', 'places', 'picture.svg'),
  81. 'play-add': path.join(__dirname, '../img', 'actions', 'play-add.svg'),
  82. 'play-next': path.join(__dirname, '../img', 'actions', 'play-next.svg'),
  83. 'play-previous': path.join(__dirname, '../img', 'actions', 'play-previous.svg'),
  84. 'play': path.join(__dirname, '../img', 'actions', 'play.svg'),
  85. 'projects': path.join(__dirname, '../img', 'actions', 'projects.svg'),
  86. 'public': path.join(__dirname, '../img', 'actions', 'public.svg'),
  87. 'quota': path.join(__dirname, '../img', 'actions', 'quota.svg'),
  88. 'recent': path.join(__dirname, '../img', 'actions', 'recent.svg'),
  89. 'rename': path.join(__dirname, '../img', 'actions', 'rename.svg'),
  90. 'screen-off': path.join(__dirname, '../img', 'actions', 'screen-off.svg'),
  91. 'screen': path.join(__dirname, '../img', 'actions', 'screen.svg'),
  92. 'search': path.join(__dirname, '../img', 'actions', 'search.svg'),
  93. 'settings': path.join(__dirname, '../img', 'actions', 'settings-dark.svg'),
  94. 'share': path.join(__dirname, '../img', 'actions', 'share.svg'),
  95. 'shared': path.join(__dirname, '../img', 'actions', 'share.svg'),
  96. 'sound-off': path.join(__dirname, '../img', 'actions', 'sound-off.svg'),
  97. 'sound': path.join(__dirname, '../img', 'actions', 'sound.svg'),
  98. 'star': path.join(__dirname, '../img', 'actions', 'star.svg'),
  99. 'starred': path.join(__dirname, '../img', 'actions', 'star-dark.svg'),
  100. 'star-rounded': path.join(__dirname, '../img', 'actions', 'star-rounded.svg'),
  101. 'tablet': path.join(__dirname, '../img', 'clients', 'tablet.svg'),
  102. 'tag': path.join(__dirname, '../img', 'actions', 'tag.svg'),
  103. 'talk': path.join(__dirname, '../img', 'apps', 'spreed.svg'),
  104. 'teams': path.join(__dirname, '../img', 'apps', 'circles.svg'),
  105. 'template-add': path.join(__dirname, '../img', 'actions', 'template-add.svg'),
  106. 'timezone': path.join(__dirname, '../img', 'actions', 'timezone.svg'),
  107. 'toggle-background': path.join(__dirname, '../img', 'actions', 'toggle-background.svg'),
  108. 'toggle-filelist': path.join(__dirname, '../img', 'actions', 'toggle-filelist.svg'),
  109. 'toggle-pictures': path.join(__dirname, '../img', 'actions', 'toggle-pictures.svg'),
  110. 'toggle': path.join(__dirname, '../img', 'actions', 'toggle.svg'),
  111. 'triangle-e': path.join(__dirname, '../img', 'actions', 'triangle-e.svg'),
  112. 'triangle-n': path.join(__dirname, '../img', 'actions', 'triangle-n.svg'),
  113. 'triangle-s': path.join(__dirname, '../img', 'actions', 'triangle-s.svg'),
  114. 'unshare': path.join(__dirname, '../img', 'actions', 'unshare.svg'),
  115. 'upload': path.join(__dirname, '../img', 'actions', 'upload.svg'),
  116. 'user-admin': path.join(__dirname, '../img', 'actions', 'user-admin.svg'),
  117. 'user': path.join(__dirname, '../img', 'actions', 'user.svg'),
  118. 'video-off': path.join(__dirname, '../img', 'actions', 'video-off.svg'),
  119. 'video-switch': path.join(__dirname, '../img', 'actions', 'video-switch.svg'),
  120. 'video': path.join(__dirname, '../img', 'actions', 'video.svg'),
  121. 'view-close': path.join(__dirname, '../img', 'actions', 'view-close.svg'),
  122. 'view-download': path.join(__dirname, '../img', 'actions', 'view-download.svg'),
  123. 'view-next': path.join(__dirname, '../img', 'actions', 'arrow-right.svg'),
  124. 'view-pause': path.join(__dirname, '../img', 'actions', 'view-pause.svg'),
  125. 'view-play': path.join(__dirname, '../img', 'actions', 'view-play.svg'),
  126. 'view-previous': path.join(__dirname, '../img', 'actions', 'arrow-left.svg'),
  127. }
  128. const iconsColor = {
  129. 'add-folder-description': {
  130. path: path.join(__dirname, '../img', 'actions', 'add-folder-description.svg'),
  131. color: 'grey',
  132. },
  133. 'settings': {
  134. path: path.join(__dirname, '../img', 'actions', 'settings.svg'),
  135. color: 'black',
  136. },
  137. 'error-color': {
  138. path: path.join(__dirname, '../img', 'actions', 'error.svg'),
  139. color: 'red',
  140. },
  141. 'checkmark-color': {
  142. path: path.join(__dirname, '../img', 'actions', 'checkmark.svg'),
  143. color: 'green',
  144. },
  145. 'starred': {
  146. path: path.join(__dirname, '../img', 'actions', 'star-dark.svg'),
  147. color: 'yellow',
  148. },
  149. 'star': {
  150. path: path.join(__dirname, '../img', 'actions', 'star-dark.svg'),
  151. color: 'grey',
  152. },
  153. 'delete-color': {
  154. path: path.join(__dirname, '../img', 'actions', 'delete.svg'),
  155. color: 'red',
  156. },
  157. 'file': {
  158. path: path.join(__dirname, '../img', 'filetypes', 'text.svg'),
  159. color: 'grey',
  160. },
  161. 'filetype-file': {
  162. path: path.join(__dirname, '../img', 'filetypes', 'file.svg'),
  163. color: 'grey',
  164. },
  165. 'filetype-folder': {
  166. path: path.join(__dirname, '../img', 'filetypes', 'folder.svg'),
  167. // TODO: replace primary ?
  168. color: 'primary',
  169. },
  170. 'filetype-folder-drag-accept': {
  171. path: path.join(__dirname, '../img', 'filetypes', 'folder-drag-accept.svg'),
  172. // TODO: replace primary ?
  173. color: 'primary',
  174. },
  175. 'filetype-text': {
  176. path: path.join(__dirname, '../img', 'filetypes', 'text.svg'),
  177. color: 'grey',
  178. },
  179. 'file-text': {
  180. path: path.join(__dirname, '../img', 'filetypes', 'text.svg'),
  181. color: 'black',
  182. },
  183. }
  184. // use this to define aliases to existing icons
  185. // key is the css selector, value is the variable
  186. const iconsAliases = {
  187. 'icon-caret': 'icon-caret-white',
  188. // starring action
  189. 'icon-star:hover': 'icon-starred',
  190. 'icon-star:focus': 'icon-starred',
  191. // Un-starring action
  192. 'icon-starred:hover': 'icon-star-grey',
  193. 'icon-starred:focus': 'icon-star-grey',
  194. // Delete normal
  195. 'icon-delete.no-permission:hover': 'icon-delete-dark',
  196. 'icon-delete.no-permission:focus': 'icon-delete-dark',
  197. 'icon-delete.no-hover:hover': 'icon-delete-dark',
  198. 'icon-delete.no-hover:focus': 'icon-delete-dark',
  199. 'icon-delete:hover': 'icon-delete-color-red',
  200. 'icon-delete:focus': 'icon-delete-color-red',
  201. // Delete white
  202. 'icon-delete-white.no-permission:hover': 'icon-delete-white',
  203. 'icon-delete-white.no-permission:focus': 'icon-delete-white',
  204. 'icon-delete-white.no-hover:hover': 'icon-delete-white',
  205. 'icon-delete-white.no-hover:focus': 'icon-delete-white',
  206. 'icon-delete-white:hover': 'icon-delete-color-red',
  207. 'icon-delete-white:focus': 'icon-delete-color-red',
  208. // Default to white
  209. 'icon-view-close': 'icon-view-close-white',
  210. 'icon-view-download': 'icon-view-download-white',
  211. 'icon-view-pause': 'icon-view-pause-white',
  212. 'icon-view-play': 'icon-view-play-white',
  213. // Default app place to white
  214. 'icon-calendar': 'icon-calendar-white',
  215. 'icon-contacts': 'icon-contacts-white',
  216. 'icon-files': 'icon-files-white',
  217. // Re-using existing icons
  218. 'icon-category-installed': 'icon-user-dark',
  219. 'icon-category-enabled': 'icon-checkmark-dark',
  220. 'icon-category-disabled': 'icon-close-dark',
  221. 'icon-category-updates': 'icon-download-dark',
  222. 'icon-category-security': 'icon-password-dark',
  223. 'icon-category-search': 'icon-search-dark',
  224. 'icon-category-tools': 'icon-settings-dark',
  225. 'nav-icon-systemtagsfilter': 'icon-tag-dark',
  226. }
  227. const colorSvg = function(svg = '', color = '000') {
  228. if (!color.match(/^[0-9a-f]{3,6}$/i)) {
  229. // Prevent not-sane colors from being written into the SVG
  230. console.warn(color, 'does not match the required format')
  231. color = '000'
  232. }
  233. // add fill (fill is not present on black elements)
  234. const fillRe = /<((circle|rect|path)((?!fill=)[a-z0-9 =".\-#():;,])+)\/>/gmi
  235. svg = svg.replace(fillRe, '<$1 fill="#' + color + '"/>')
  236. // replace any fill or stroke colors
  237. svg = svg.replace(/stroke="#([a-z0-9]{3,6})"/gmi, 'stroke="#' + color + '"')
  238. svg = svg.replace(/fill="#([a-z0-9]{3,6})"/gmi, 'fill="#' + color + '"')
  239. return svg
  240. }
  241. const generateVariablesAliases = function(invert = false) {
  242. let css = ''
  243. Object.keys(variables).forEach(variable => {
  244. if (variable.indexOf('original-') !== -1) {
  245. let finalVariable = variable.replace('original-', '')
  246. if (invert) {
  247. finalVariable = finalVariable.replace('white', 'tempwhite')
  248. .replace('dark', 'white')
  249. .replace('tempwhite', 'dark')
  250. }
  251. css += `${finalVariable}: var(${variable});`
  252. }
  253. })
  254. return css
  255. }
  256. const formatIcon = function(icon, invert = false) {
  257. const color1 = invert ? 'white' : 'dark'
  258. const color2 = invert ? 'dark' : 'white'
  259. return `
  260. .icon-${icon},
  261. .icon-${icon}-dark {
  262. background-image: var(--icon-${icon}-${color1});
  263. }
  264. .icon-${icon}-white,
  265. .icon-${icon}.icon-white {
  266. background-image: var(--icon-${icon}-${color2});
  267. }`
  268. }
  269. const formatIconColor = function(icon) {
  270. const { color } = iconsColor[icon]
  271. return `
  272. .icon-${icon} {
  273. background-image: var(--icon-${icon}-${color});
  274. }`
  275. }
  276. const formatAlias = function(alias, invert = false) {
  277. let icon = iconsAliases[alias]
  278. if (invert) {
  279. icon = icon.replace('white', 'tempwhite')
  280. .replace('dark', 'white')
  281. .replace('tempwhite', 'dark')
  282. }
  283. return `
  284. .${alias} {
  285. background-image: var(--${icon})
  286. }`
  287. }
  288. let css = ''
  289. Object.keys(icons).forEach(icon => {
  290. const path = icons[icon]
  291. const svg = fs.readFileSync(path, 'utf8')
  292. const darkSvg = colorSvg(svg, '000000')
  293. const whiteSvg = colorSvg(svg, 'ffffff')
  294. variables[`--original-icon-${icon}-dark`] = Buffer.from(darkSvg, 'utf-8').toString('base64')
  295. variables[`--original-icon-${icon}-white`] = Buffer.from(whiteSvg, 'utf-8').toString('base64')
  296. })
  297. Object.keys(iconsColor).forEach(icon => {
  298. const { path, color } = iconsColor[icon]
  299. const svg = fs.readFileSync(path, 'utf8')
  300. const coloredSvg = colorSvg(svg, colors[color])
  301. variables[`--icon-${icon}-${color}`] = Buffer.from(coloredSvg, 'utf-8').toString('base64')
  302. })
  303. // ICONS VARIABLES LIST
  304. css += ':root {'
  305. Object.keys(variables).forEach(variable => {
  306. const data = variables[variable]
  307. css += `${variable}: url(data:image/svg+xml;base64,${data});`
  308. })
  309. css += '}'
  310. // DEFAULT THEME
  311. css += 'body {'
  312. css += generateVariablesAliases()
  313. Object.keys(icons).forEach(icon => {
  314. css += formatIcon(icon)
  315. })
  316. Object.keys(iconsColor).forEach(icon => {
  317. css += formatIconColor(icon)
  318. })
  319. Object.keys(iconsAliases).forEach(alias => {
  320. css += formatAlias(alias)
  321. })
  322. css += '}'
  323. // DARK THEME MEDIA QUERY
  324. css += '@media (prefers-color-scheme: dark) { body {'
  325. css += generateVariablesAliases(true)
  326. css += '}}'
  327. // DARK THEME
  328. css += '[data-themes*=light] {'
  329. css += generateVariablesAliases()
  330. css += '}'
  331. // DARK THEME
  332. css += '[data-themes*=dark] {'
  333. css += generateVariablesAliases(true)
  334. css += '}'
  335. // WRITE CSS
  336. fs.writeFileSync(path.join(__dirname, '../../dist', 'icons.css'), sass.compileString(css).css)