Automatically generate spritesheets
Unified · Split
| @@ -22,6 +22,8 @@ group :app do · 21 unmodified lines | |||
| 22 | gem 'browser' | 22 | gem 'browser' |
| 23 | gem 'sass' | 23 | gem 'sass' |
| 24 | gem 'coffee-script' | 24 | gem 'coffee-script' |
| 25 | gem 'chunky_png' | ||
| 26 | gem 'sprockets-sass' | ||
| 25 | end | 27 | end |
| 26 | 28 | ||
| 27 | group :production do | 29 | group :production do |
| @@ -12,6 +12,7 @@ GEM · 11 unmodified lines | |||
| 12 | erubi (>= 1.0.0) | 12 | erubi (>= 1.0.0) |
| 13 | rack (>= 0.9.0) | 13 | rack (>= 0.9.0) |
| 14 | browser (2.5.3) | 14 | browser (2.5.3) |
| 15 | chunky_png (1.3.10) | ||
| 15 | coderay (1.1.2) | 16 | coderay (1.1.2) |
| 16 | coffee-script (2.4.1) | 17 | coffee-script (2.4.1) |
| 17 | coffee-script-source | 18 | coffee-script-source |
| @@ -96,6 +97,8 @@ GEM · 78 unmodified lines | |||
| 96 | rack (> 1, < 3) | 97 | rack (> 1, < 3) |
| 97 | sprockets-helpers (1.2.1) | 98 | sprockets-helpers (1.2.1) |
| 98 | sprockets (>= 2.2) | 99 | sprockets (>= 2.2) |
| 100 | sprockets-sass (2.0.0.beta2) | ||
| 101 | sprockets (>= 2.0, < 4.0) | ||
| 99 | strings (0.1.4) | 102 | strings (0.1.4) |
| 100 | strings-ansi (~> 0.1.0) | 103 | strings-ansi (~> 0.1.0) |
| 101 | unicode-display_width (~> 1.4.0) | 104 | unicode-display_width (~> 1.4.0) |
| @@ -132,6 +135,7 @@ DEPENDENCIES · 30 unmodified lines | |||
| 132 | activesupport (~> 5.2) | 135 | activesupport (~> 5.2) |
| 133 | better_errors | 136 | better_errors |
| 134 | browser | 137 | browser |
| 138 | chunky_png | ||
| 135 | coffee-script | 139 | coffee-script |
| 136 | erubi | 140 | erubi |
| 137 | html-pipeline | 141 | html-pipeline |
| @@ -153,6 +157,7 @@ DEPENDENCIES · 15 unmodified lines | |||
| 153 | sinatra-contrib | 157 | sinatra-contrib |
| 154 | sprockets | 158 | sprockets |
| 155 | sprockets-helpers | 159 | sprockets-helpers |
| 160 | sprockets-sass | ||
| 156 | thin | 161 | thin |
| 157 | thor | 162 | thor |
| 158 | tty-pager | 163 | tty-pager |
| @@ -0,0 +1 @@ | |||
| 1 | sprites/**/* | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -1,7 +1,6 @@ | |||
| 1 | //= depend_on docs-1.png | 1 | //= depend_on sprites/docs.png |
| 2 | //= depend_on docs-1@2x.png | 2 | //= depend_on sprites/docs@2x.png |
| 3 | //= depend_on docs-2.png | 3 | //= depend_on sprites/docs.json |
| 4 | //= depend_on docs-2@2x.png | ||
| 5 | 4 | ||
| 6 | /*! | 5 | /*! |
| 7 | * Copyright 2013-2019 Thibaut Courouble and other contributors | 6 | * Copyright 2013-2019 Thibaut Courouble and other contributors |
| @@ -1,182 +0,0 @@ | |||
| 1 | %svg-icon { | ||
| 2 | display: inline-block; | ||
| 3 | vertical-align: top; | ||
| 4 | width: 1rem; | ||
| 5 | height: 1rem; | ||
| 6 | pointer-events: none; | ||
| 7 | fill: currentColor; | ||
| 8 | } | ||
| 9 | |||
| 10 | %doc-icon { | ||
| 11 | content: ''; | ||
| 12 | display: block; | ||
| 13 | width: 1rem; | ||
| 14 | height: 1rem; | ||
| 15 | background-image: image-url('docs-1.png'); | ||
| 16 | background-size: 10rem 10rem; | ||
| 17 | } | ||
| 18 | |||
| 19 | %doc-icon-2 { background-image: image-url('docs-2.png') !important; } | ||
| 20 | |||
| 21 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { | ||
| 22 | %doc-icon { background-image: image-url('docs-1@2x.png'); } | ||
| 23 | %doc-icon-2 { background-image: image-url('docs-2@2x.png') !important; } | ||
| 24 | } | ||
| 25 | |||
| 26 | html._theme-dark { | ||
| 27 | %darkIconFix { | ||
| 28 | filter: invert(100%) grayscale(100%); | ||
| 29 | -webkit-filter: invert(100%) grayscale(100%); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | ._icon-jest:before { background-position: 0 0; } | ||
| 34 | ._icon-liquid:before { background-position: -1rem 0; } | ||
| 35 | ._icon-openjdk:before { background-position: -2rem 0; } | ||
| 36 | ._icon-codeceptjs:before { background-position: -3rem 0; } | ||
| 37 | ._icon-codeception:before { background-position: -4rem 0; } | ||
| 38 | ._icon-sqlite:before { background-position: -5rem 0; @extend %darkIconFix !optional; } | ||
| 39 | ._icon-async:before { background-position: -6rem 0; @extend %darkIconFix !optional; } | ||
| 40 | ._icon-http:before { background-position: -7rem 0; @extend %darkIconFix !optional; } | ||
| 41 | ._icon-jquery:before { background-position: -8rem 0; @extend %darkIconFix !optional; } | ||
| 42 | ._icon-underscore:before { background-position: -9rem 0; @extend %darkIconFix !optional; } | ||
| 43 | ._icon-html:before { background-position: 0 -1rem; } | ||
| 44 | ._icon-css:before { background-position: -1rem -1rem; } | ||
| 45 | ._icon-dom:before { background-position: -2rem -1rem; } | ||
| 46 | ._icon-dom_events:before { background-position: -3rem -1rem; } | ||
| 47 | ._icon-javascript:before { background-position: -4rem -1rem; } | ||
| 48 | ._icon-backbone:before { background-position: -5rem -1rem; @extend %darkIconFix !optional; } | ||
| 49 | ._icon-node:before, | ||
| 50 | ._icon-node_lts:before { background-position: -6rem -1rem; } | ||
| 51 | ._icon-sass:before { background-position: -7rem -1rem; } | ||
| 52 | ._icon-less:before { background-position: -8rem -1rem; } | ||
| 53 | ._icon-angularjs:before { background-position: -9rem -1rem; } | ||
| 54 | ._icon-coffeescript:before { background-position: 0 -2rem; @extend %darkIconFix !optional; } | ||
| 55 | ._icon-ember:before { background-position: -1rem -2rem; } | ||
| 56 | ._icon-yarn:before { background-position: -2rem -2rem; } | ||
| 57 | ._icon-immutable:before { background-position: -3rem -2rem; @extend %darkIconFix !optional; } | ||
| 58 | ._icon-jqueryui:before { background-position: -4rem -2rem; } | ||
| 59 | ._icon-jquerymobile:before { background-position: -5rem -2rem; } | ||
| 60 | ._icon-lodash:before { background-position: -6rem -2rem; } | ||
| 61 | ._icon-php:before { background-position: -7rem -2rem; } | ||
| 62 | ._icon-ruby:before, | ||
| 63 | ._icon-minitest:before { background-position: -8rem -2rem; } | ||
| 64 | ._icon-rails:before { background-position: -9rem -2rem; } | ||
| 65 | ._icon-python:before, | ||
| 66 | ._icon-python2:before { background-position: 0 -3rem; } | ||
| 67 | ._icon-git:before { background-position: -1rem -3rem; } | ||
| 68 | ._icon-redis:before { background-position: -2rem -3rem; } | ||
| 69 | ._icon-postgresql:before { background-position: -3rem -3rem; } | ||
| 70 | ._icon-d3:before { background-position: -4rem -3rem; } | ||
| 71 | ._icon-knockout:before { background-position: -5rem -3rem; } | ||
| 72 | ._icon-moment:before { background-position: -6rem -3rem; @extend %darkIconFix !optional; } | ||
| 73 | ._icon-c:before { background-position: -7rem -3rem; } | ||
| 74 | ._icon-statsmodels:before { background-position: -8rem -3rem; } | ||
| 75 | ._icon-yii:before, | ||
| 76 | ._icon-yii1:before { background-position: -9rem -3rem; } | ||
| 77 | ._icon-cpp:before { background-position: 0 -4rem; } | ||
| 78 | ._icon-go:before { background-position: -1rem -4rem; } | ||
| 79 | ._icon-express:before { background-position: -2rem -4rem; } | ||
| 80 | ._icon-grunt:before { background-position: -3rem -4rem; } | ||
| 81 | ._icon-rust:before { background-position: -4rem -4rem; @extend %darkIconFix !optional; } | ||
| 82 | ._icon-laravel:before { background-position: -5rem -4rem; } | ||
| 83 | ._icon-haskell:before { background-position: -6rem -4rem; } | ||
| 84 | ._icon-requirejs:before { background-position: -7rem -4rem; } | ||
| 85 | ._icon-chai:before { background-position: -8rem -4rem; } | ||
| 86 | ._icon-sinon:before { background-position: -9rem -4rem; } | ||
| 87 | ._icon-cordova:before { background-position: 0 -5rem; } | ||
| 88 | ._icon-markdown:before { background-position: -1rem -5rem; @extend %darkIconFix !optional; } | ||
| 89 | ._icon-django:before { background-position: -2rem -5rem; } | ||
| 90 | ._icon-xslt_xpath:before { background-position: -3rem -5rem; } | ||
| 91 | ._icon-nginx:before, | ||
| 92 | ._icon-nginx_lua_module:before { background-position: -4rem -5rem; } | ||
| 93 | ._icon-svg:before { background-position: -5rem -5rem; } | ||
| 94 | ._icon-marionette:before { background-position: -6rem -5rem; } | ||
| 95 | ._icon-jsdoc:before, | ||
| 96 | ._icon-koa:before, | ||
| 97 | ._icon-graphite:before, | ||
| 98 | ._icon-mongoose:before { background-position: -7rem -5rem; } | ||
| 99 | ._icon-phpunit:before { background-position: -8rem -5rem; } | ||
| 100 | ._icon-nokogiri:before { background-position: -9rem -5rem; @extend %darkIconFix !optional; } | ||
| 101 | ._icon-rethinkdb:before { background-position: 0 -6rem; } | ||
| 102 | ._icon-react:before { background-position: -1rem -6rem; } | ||
| 103 | ._icon-socketio:before { background-position: -2rem -6rem; } | ||
| 104 | ._icon-modernizr:before { background-position: -3rem -6rem; } | ||
| 105 | ._icon-bower:before { background-position: -4rem -6rem; } | ||
| 106 | ._icon-fish:before { background-position: -5rem -6rem; @extend %darkIconFix !optional; } | ||
| 107 | ._icon-scikit_image:before { background-position: -6rem -6rem; } | ||
| 108 | ._icon-twig:before { background-position: -7rem -6rem; } | ||
| 109 | ._icon-pandas:before { background-position: -8rem -6rem; } | ||
| 110 | ._icon-scikit_learn:before { background-position: -9rem -6rem; } | ||
| 111 | ._icon-bottle:before { background-position: 0 -7rem; } | ||
| 112 | ._icon-docker:before { background-position: -1rem -7rem; } | ||
| 113 | ._icon-cakephp:before { background-position: -2rem -7rem; } | ||
| 114 | ._icon-lua:before { background-position: -3rem -7rem; @extend %darkIconFix !optional; } | ||
| 115 | ._icon-clojure:before { background-position: -4rem -7rem; } | ||
| 116 | ._icon-symfony:before { background-position: -5rem -7rem; } | ||
| 117 | ._icon-mocha:before { background-position: -6rem -7rem; } | ||
| 118 | ._icon-meteor:before { background-position: -7rem -7rem; @extend %darkIconFix !optional; } | ||
| 119 | ._icon-npm:before { background-position: -8rem -7rem; } | ||
| 120 | ._icon-apache_http_server:before { background-position: -9rem -7rem; } | ||
| 121 | ._icon-drupal:before { background-position: 0 -8rem; } | ||
| 122 | ._icon-webpack:before { background-position: -1rem -8rem; } | ||
| 123 | ._icon-phaser:before { background-position: -2rem -8rem; } | ||
| 124 | ._icon-vue:before { background-position: -3rem -8rem; } | ||
| 125 | ._icon-opentsdb:before { background-position: -4rem -8rem; } | ||
| 126 | ._icon-q:before { background-position: -5rem -8rem; } | ||
| 127 | ._icon-crystal:before { background-position: -6rem -8rem; @extend %darkIconFix !optional; } | ||
| 128 | ._icon-julia:before { background-position: -7rem -8rem; @extend %darkIconFix !optional; } | ||
| 129 | ._icon-redux:before { background-position: -8rem -8rem; @extend %darkIconFix !optional; } | ||
| 130 | ._icon-bootstrap:before { background-position: -9rem -8rem; } | ||
| 131 | ._icon-react_native:before { background-position: 0 -9rem; } | ||
| 132 | ._icon-phalcon:before { background-position: -1rem -9rem; } | ||
| 133 | ._icon-matplotlib:before { background-position: -2rem -9rem; } | ||
| 134 | ._icon-cmake:before { background-position: -3rem -9rem; } | ||
| 135 | ._icon-elixir:before { background-position: -4rem -9rem; @extend %darkIconFix !optional; } | ||
| 136 | ._icon-vagrant:before { background-position: -5rem -9rem; } | ||
| 137 | ._icon-dojo:before { background-position: -6rem -9rem; } | ||
| 138 | ._icon-flow:before { background-position: -7rem -9rem; } | ||
| 139 | ._icon-relay:before { background-position: -8rem -9rem; } | ||
| 140 | ._icon-phoenix:before { background-position: -9rem -9rem; } | ||
| 141 | |||
| 142 | ._icon-tcl_tk:before { background-position: 0 0; @extend %doc-icon-2; } | ||
| 143 | ._icon-erlang:before { background-position: -1rem 0; @extend %doc-icon-2; } | ||
| 144 | ._icon-chef:before { background-position: -2rem 0; @extend %doc-icon-2; } | ||
| 145 | ._icon-ramda:before { background-position: -3rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| 146 | ._icon-codeigniter:before { background-position: -4rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| 147 | ._icon-influxdata:before { background-position: -5rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| 148 | ._icon-tensorflow:before { background-position: -6rem 0; @extend %doc-icon-2; } | ||
| 149 | ._icon-haxe:before { background-position: -7rem 0; @extend %doc-icon-2; } | ||
| 150 | ._icon-ansible:before { background-position: -8rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| 151 | ._icon-typescript:before { background-position: -9rem 0; @extend %doc-icon-2; } | ||
| 152 | ._icon-browser_support_tables:before { background-position: 0rem -1rem; @extend %doc-icon-2; } | ||
| 153 | ._icon-gnu_fortran:before { background-position: -1rem -1rem; @extend %doc-icon-2; } | ||
| 154 | ._icon-gcc:before { background-position: -2rem -1rem; @extend %doc-icon-2; } | ||
| 155 | ._icon-perl:before { background-position: -3rem -1rem; @extend %doc-icon-2; } | ||
| 156 | ._icon-apache_pig:before { background-position: -4rem -1rem; @extend %doc-icon-2; } | ||
| 157 | ._icon-numpy:before { background-position: -5rem -1rem; @extend %doc-icon-2; } | ||
| 158 | ._icon-kotlin:before { background-position: -6rem -1rem; @extend %doc-icon-2; } | ||
| 159 | ._icon-padrino:before { background-position: -7rem -1rem; @extend %doc-icon-2; } | ||
| 160 | ._icon-angular:before { background-position: -8rem -1rem; @extend %doc-icon-2; } | ||
| 161 | ._icon-love:before { background-position: -9rem -1rem; @extend %doc-icon-2; } | ||
| 162 | ._icon-jasmine:before { background-position: 0 -2rem; @extend %doc-icon-2; } | ||
| 163 | ._icon-pug:before { background-position: -1rem -2rem; @extend %doc-icon-2; } | ||
| 164 | ._icon-electron:before { background-position: -2rem -2rem; @extend %doc-icon-2; } | ||
| 165 | ._icon-falcon:before { background-position: -3rem -2rem; @extend %doc-icon-2; } | ||
| 166 | ._icon-godot:before { background-position: -4rem -2rem; @extend %doc-icon-2; } | ||
| 167 | ._icon-nim:before { background-position: -5rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| 168 | ._icon-vulkan:before { background-position: -6rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| 169 | ._icon-d:before { background-position: -7rem -2rem; @extend %doc-icon-2; } | ||
| 170 | ._icon-bluebird:before { background-position: -8rem -2rem; @extend %doc-icon-2; } | ||
| 171 | ._icon-eslint:before { background-position: -9rem -2rem; @extend %doc-icon-2; } | ||
| 172 | ._icon-homebrew:before { background-position: 0 -3rem; @extend %doc-icon-2; } | ||
| 173 | ._icon-jekyll:before { background-position: -1rem -3rem; @extend %doc-icon-2; } | ||
| 174 | ._icon-babel:before { background-position: -2rem -3rem; @extend %doc-icon-2; } | ||
| 175 | ._icon-leaflet:before { background-position: -3rem -3rem; @extend %doc-icon-2; } | ||
| 176 | ._icon-terraform:before { background-position: -4rem -3rem; @extend %doc-icon-2; } | ||
| 177 | ._icon-pygame:before { background-position: -5rem -3rem; @extend %doc-icon-2; } | ||
| 178 | ._icon-bash:before { background-position: -6rem -3rem; @extend %doc-icon-2; } | ||
| 179 | ._icon-dart:before { background-position: -7rem -3rem; @extend %doc-icon-2; } | ||
| 180 | ._icon-qt:before { background-position: -8rem -3rem; @extend %doc-icon-2; } | ||
| 181 | ._icon-puppeteer:before { background-position: -9rem -3rem; @extend %doc-icon-2; } | ||
| 182 | ._icon-handlebars:before { background-position: 0 -4rem; @extend %doc-icon-2; @extend %darkIconFix !optional; } | ||
| @@ -0,0 +1,43 @@ | |||
| 1 | <% manifest = JSON.parse(File.read('assets/images/sprites/docs.json')) %> | ||
| 2 | |||
| 3 | %svg-icon { | ||
| 4 | display: inline-block; | ||
| 5 | vertical-align: top; | ||
| 6 | width: 1rem; | ||
| 7 | height: 1rem; | ||
| 8 | pointer-events: none; | ||
| 9 | fill: currentColor; | ||
| 10 | } | ||
| 11 | |||
| 12 | %doc-icon { | ||
| 13 | content: ''; | ||
| 14 | display: block; | ||
| 15 | width: 1rem; | ||
| 16 | height: 1rem; | ||
| 17 | background-image: image-url('sprites/docs.png'); | ||
| 18 | background-size: <%= manifest['icons_per_row'] %>rem <%= manifest['icons_per_row'] %>rem; | ||
| 19 | } | ||
| 20 | |||
| 21 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { | ||
| 22 | %doc-icon { background-image: image-url('sprites/docs@2x.png'); } | ||
| 23 | } | ||
| 24 | |||
| 25 | html._theme-dark { | ||
| 26 | %darkIconFix { | ||
| 27 | filter: invert(100%) grayscale(100%); | ||
| 28 | -webkit-filter: invert(100%) grayscale(100%); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | <%= | ||
| 33 | items = [] | ||
| 34 | |||
| 35 | manifest['items'].each do |item| | ||
| 36 | rules = [] | ||
| 37 | rules << "background-position: -#{item['col']}rem -#{item['row']}rem;" | ||
| 38 | rules << "@extend %darkIconFix !optional;" if item['dark_icon_fix'] | ||
| 39 | items << "._icon-#{item['type']}:before { #{rules.join(' ')} }" | ||
| 40 | end | ||
| 41 | |||
| 42 | items.join('') | ||
| 43 | %> | ||
| @@ -53,6 +53,11 @@ class App < Sinatra::Application · 52 unmodified lines | |||
| 53 | end | 53 | end |
| 54 | 54 | ||
| 55 | configure :test, :development do | 55 | configure :test, :development do |
| 56 | require 'thor' | ||
| 57 | load 'tasks/sprites.thor' | ||
| 58 | |||
| 59 | SpritesCLI.new.invoke(:generate) | ||
| 60 | |||
| 56 | require 'active_support/per_thread_registry' | 61 | require 'active_support/per_thread_registry' |
| 57 | require 'active_support/cache' | 62 | require 'active_support/cache' |
| 58 | sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s) | 63 | sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s) |
| @@ -199,10 +204,8 @@ class App < Sinatra::Application · 140 unmodified lines | |||
| 199 | @@service_worker_asset_urls ||= [ | 204 | @@service_worker_asset_urls ||= [ |
| 200 | javascript_path('application', asset_host: false), | 205 | javascript_path('application', asset_host: false), |
| 201 | stylesheet_path('application'), | 206 | stylesheet_path('application'), |
| 202 | image_path('docs-1.png'), | 207 | image_path('sprites/docs.png'), |
| 203 | image_path('docs-1@2x.png'), | 208 | image_path('sprites/docs@2x.png'), |
| 204 | image_path('docs-2.png'), | ||
| 205 | image_path('docs-2@2x.png'), | ||
| 206 | asset_path('docs.js'), | 209 | asset_path('docs.js'), |
| 207 | App.production? ? nil : javascript_path('debug'), | 210 | App.production? ? nil : javascript_path('debug'), |
| 208 | ].compact | 211 | ].compact |
| @@ -14,6 +14,7 @@ class AssetsCLI < Thor · 13 unmodified lines | |||
| 14 | option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep' | 14 | option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep' |
| 15 | option :verbose, type: :boolean | 15 | option :verbose, type: :boolean |
| 16 | def compile | 16 | def compile |
| 17 | invoke 'sprites:generate', [], :verbose => options[:verbose] | ||
| 17 | manifest.compile App.assets_compile | 18 | manifest.compile App.assets_compile |
| 18 | manifest.clean(options[:keep]) if options[:clean] | 19 | manifest.clean(options[:keep]) if options[:clean] |
| 19 | end | 20 | end |
| @@ -0,0 +1,205 @@ | |||
| 1 | class SpritesCLI < Thor | ||
| 2 | def self.to_s | ||
| 3 | 'Sprites' | ||
| 4 | end | ||
| 5 | |||
| 6 | def initialize(*args) | ||
| 7 | require 'docs' | ||
| 8 | require 'chunky_png' | ||
| 9 | require 'fileutils' | ||
| 10 | super | ||
| 11 | end | ||
| 12 | |||
| 13 | desc 'generate [--verbose]', 'Generate the documentation icon spritesheets' | ||
| 14 | option :verbose, type: :boolean | ||
| 15 | def generate | ||
| 16 | items = get_items | ||
| 17 | items_with_icons = items.select {|item| item[:has_icons]} | ||
| 18 | items_without_icons = items.select {|item| !item[:has_icons]} | ||
| 19 | icons_per_row = Math.sqrt(items_with_icons.length).ceil | ||
| 20 | |||
| 21 | bg_color = get_sidebar_background | ||
| 22 | |||
| 23 | items_with_icons.each_with_index do |item, index| | ||
| 24 | item[:row] = (index / icons_per_row).floor | ||
| 25 | item[:col] = index - item[:row] * icons_per_row | ||
| 26 | |||
| 27 | item[:icon_16] = get_icon(item[:path_16], 16) | ||
| 28 | item[:icon_32] = get_icon(item[:path_32], 32) | ||
| 29 | |||
| 30 | item[:dark_icon_fix] = needs_dark_icon_fix(item[:icon_32], bg_color) | ||
| 31 | end | ||
| 32 | |||
| 33 | log_details(items_with_icons, icons_per_row) | ||
| 34 | |||
| 35 | generate_spritesheet(16, items_with_icons, 'assets/images/sprites/docs.png') {|item| item[:icon_16]} | ||
| 36 | generate_spritesheet(32, items_with_icons, 'assets/images/sprites/docs@2x.png') {|item| item[:icon_32]} | ||
| 37 | |||
| 38 | # Add Mongoose's icon details to docs without custom icons | ||
| 39 | default_item = items_with_icons.find {|item| item[:type] == 'mongoose'} | ||
| 40 | items_without_icons.each do |item| | ||
| 41 | item[:row] = default_item[:row] | ||
| 42 | item[:col] = default_item[:col] | ||
| 43 | item[:dark_icon_fix] = default_item[:dark_icon_fix] | ||
| 44 | end | ||
| 45 | |||
| 46 | save_manifest(items, icons_per_row, 'assets/images/sprites/docs.json') | ||
| 47 | end | ||
| 48 | |||
| 49 | private | ||
| 50 | |||
| 51 | def get_items | ||
| 52 | items = Docs.all.map do |doc| | ||
| 53 | base_path = "public/icons/docs/#{doc.slug}" | ||
| 54 | { | ||
| 55 | :type => doc.slug, | ||
| 56 | :path_16 => "#{base_path}/16.png", | ||
| 57 | :path_32 => "#{base_path}/16@2x.png" | ||
| 58 | } | ||
| 59 | end | ||
| 60 | |||
| 61 | # Checking paths against an array of possible paths is faster than 200+ File.exist? calls | ||
| 62 | files = Dir.glob('public/icons/docs/**/*.png') | ||
| 63 | |||
| 64 | items.each do |item| | ||
| 65 | item[:has_icons] = files.include?(item[:path_16]) && files.include?(item[:path_32]) | ||
| 66 | end | ||
| 67 | end | ||
| 68 | |||
| 69 | def get_icon(path, max_size) | ||
| 70 | icon = ChunkyPNG::Image.from_file(path) | ||
| 71 | |||
| 72 | # Check if the icon is too big | ||
| 73 | # If it is, resize the image without changing the aspect ratio | ||
| 74 | if icon.width > max_size || icon.height > max_size | ||
| 75 | ratio = icon.width.to_f / icon.height | ||
| 76 | new_width = (icon.width >= icon.height ? max_size : max_size * ratio).floor | ||
| 77 | new_height = (icon.width >= icon.height ? max_size / ratio : max_size).floor | ||
| 78 | |||
| 79 | logger.warn("Icon #{path} is too big: max size is #{max_size} x #{max_size}, icon is #{icon.width} x #{icon.height}, resizing to #{new_width} x #{new_height}") | ||
| 80 | |||
| 81 | icon.resample_nearest_neighbor!(new_width, new_height) | ||
| 82 | end | ||
| 83 | |||
| 84 | icon | ||
| 85 | end | ||
| 86 | |||
| 87 | def get_sidebar_background | ||
| 88 | # This is a hacky way to get the background color of the sidebar | ||
| 89 | # Unfortunately, it's not possible to get the value of a SCSS variable from a Thor task | ||
| 90 | # Because hard-coding the value is even worse, we extract it using some regex | ||
| 91 | path = 'assets/stylesheets/global/_variables-dark.scss' | ||
| 92 | regex = /--sidebarBackground:\s+([^;]+);/ | ||
| 93 | ChunkyPNG::Color.parse(File.read(path)[regex, 1]) | ||
| 94 | end | ||
| 95 | |||
| 96 | def needs_dark_icon_fix(icon, bg_color) | ||
| 97 | # Determine whether the icon needs to be grayscaled if the user has enabled the dark theme | ||
| 98 | # The logic is roughly based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast | ||
| 99 | contrast = icon.pixels.select {|pixel| ChunkyPNG::Color.a(pixel) > 0}.map do |pixel| | ||
| 100 | get_contrast(bg_color, pixel) | ||
| 101 | end | ||
| 102 | |||
| 103 | avg = contrast.reduce(:+) / contrast.size.to_f | ||
| 104 | avg < 3.5 | ||
| 105 | end | ||
| 106 | |||
| 107 | def get_contrast(base, other) | ||
| 108 | # Calculating the contrast ratio as described in the WCAG 2.0: | ||
| 109 | # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef | ||
| 110 | l1 = get_luminance(base) + 0.05 | ||
| 111 | l2 = get_luminance(other) + 0.05 | ||
| 112 | ratio = l1 / l2 | ||
| 113 | l2 > l1 ? 1 / ratio : ratio | ||
| 114 | end | ||
| 115 | |||
| 116 | def get_luminance(color) | ||
| 117 | rgb = [ | ||
| 118 | ChunkyPNG::Color.r(color).to_f, | ||
| 119 | ChunkyPNG::Color.g(color).to_f, | ||
| 120 | ChunkyPNG::Color.b(color).to_f | ||
| 121 | ] | ||
| 122 | |||
| 123 | # Calculating the relative luminance as described in the WCAG 2.0: | ||
| 124 | # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef | ||
| 125 | |||
| 126 | rgb.map! do |value| | ||
| 127 | value /= 255 | ||
| 128 | value < 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4 | ||
| 129 | end | ||
| 130 | |||
| 131 | 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] | ||
| 132 | end | ||
| 133 | |||
| 134 | def generate_spritesheet(size, items_with_icons, output_path, &item_to_icon) | ||
| 135 | logger.info("Generating spritesheet #{output_path} with icons of size #{size} x #{size}") | ||
| 136 | |||
| 137 | icons_per_row = Math.sqrt(items_with_icons.length).ceil | ||
| 138 | spritesheet = ChunkyPNG::Image.new(size * icons_per_row, size * icons_per_row) | ||
| 139 | |||
| 140 | items_with_icons.each do |item| | ||
| 141 | icon = item_to_icon.call(item) | ||
| 142 | |||
| 143 | # Calculate the base coordinates | ||
| 144 | base_x = item[:col] * size | ||
| 145 | base_y = item[:row] * size | ||
| 146 | |||
| 147 | # Center the icon if it's not a perfect rectangle | ||
| 148 | x = base_x + ((size - icon.width) / 2).floor | ||
| 149 | y = base_y + ((size - icon.height) / 2).floor | ||
| 150 | |||
| 151 | spritesheet.compose!(icon, x, y) | ||
| 152 | end | ||
| 153 | |||
| 154 | FileUtils.mkdir_p(File.dirname(output_path)) | ||
| 155 | spritesheet.save(output_path) | ||
| 156 | end | ||
| 157 | |||
| 158 | def save_manifest(items, icons_per_row, path) | ||
| 159 | logger.info("Saving spritesheet details to #{path}") | ||
| 160 | |||
| 161 | FileUtils.mkdir_p(File.dirname(path)) | ||
| 162 | |||
| 163 | # Only save the details that the scss file needs | ||
| 164 | manifest_items = items.map do |item| | ||
| 165 | { | ||
| 166 | :type => item[:type], | ||
| 167 | :row => item[:row], | ||
| 168 | :col => item[:col], | ||
| 169 | :dark_icon_fix => item[:dark_icon_fix] | ||
| 170 | } | ||
| 171 | end | ||
| 172 | |||
| 173 | manifest = {:icons_per_row => icons_per_row, :items => manifest_items} | ||
| 174 | |||
| 175 | File.open(path, 'w') do |f| | ||
| 176 | f.write(JSON.generate(manifest)) | ||
| 177 | end | ||
| 178 | end | ||
| 179 | |||
| 180 | def log_details(items_with_icons, icons_per_row) | ||
| 181 | logger.debug("Amount of icons: #{items_with_icons.length}") | ||
| 182 | logger.debug("Amount of icons needing the dark icon fix: #{items_with_icons.count {|item| item[:dark_icon_fix]}}") | ||
| 183 | logger.debug("Amount of icons per row: #{icons_per_row}") | ||
| 184 | |||
| 185 | max_type_length = items_with_icons.map {|item| item[:type].length}.max | ||
| 186 | border = "+#{'-' * (max_type_length + 2)}+#{'-' * 5}+#{'-' * 8}+#{'-' * 15}+" | ||
| 187 | |||
| 188 | logger.debug(border) | ||
| 189 | logger.debug("| #{'Type'.ljust(max_type_length)} | Row | Column | Dark icon fix |") | ||
| 190 | logger.debug(border) | ||
| 191 | |||
| 192 | items_with_icons.each do |item| | ||
| 193 | logger.debug("| #{item[:type].ljust(max_type_length)} | #{item[:row].to_s.ljust(3)} | #{item[:col].to_s.ljust(6)} | #{(item[:dark_icon_fix] ? 'Yes' : 'No').ljust(13)} |") | ||
| 194 | end | ||
| 195 | |||
| 196 | logger.debug(border) | ||
| 197 | end | ||
| 198 | |||
| 199 | def logger | ||
| 200 | @logger ||= Logger.new($stdout).tap do |logger| | ||
| 201 | logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO | ||
| 202 | logger.formatter = proc {|severity, datetime, progname, msg| "#{msg}\n"} | ||
| 203 | end | ||
| 204 | end | ||
| 205 | end | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.