From 13fb19c17f6530d4bca685a5b8c98246d7e6bb45 Mon Sep 17 00:00:00 2001 From: Sebastien Barre Date: Fri, 9 Dec 2016 22:26:42 -0500 Subject: [PATCH 1/7] add screenshots to components gallery --- build.js | 33 ++-- package.json | 3 + src/components-build.js | 176 ++++++++++-------- src/components-screenshot-build.js | 123 ++++++++++++ src/components/banners/info.html | 5 +- src/components/banners/single-cta.html | 5 +- .../buttons/basic-previous-next.html | 2 +- .../buttons/basic-rounded-extra-small.html | 8 +- .../buttons/basic-rounded-small.html | 8 +- src/components/buttons/basic-rounded.html | 8 +- src/components/buttons/basic.html | 8 +- src/components/buttons/pill-grow.html | 8 +- src/components/buttons/pill.html | 8 +- src/components/cards/album-centered.html | 2 +- src/components/cards/album-left.html | 2 +- src/components/cards/basic-text-card.html | 5 +- src/components/cards/text-card.html | 5 +- src/components/definition-lists/inline.html | 5 +- src/components/definition-lists/simple.html | 5 +- src/components/footers/simple-large-type.html | 5 +- src/components/forms/checkbox-list.html | 5 +- src/components/forms/input-text-label.html | 5 +- .../forms/newsletter-subscription.html | 5 +- src/components/forms/password.html | 5 +- src/components/forms/sign-up.html | 7 +- src/components/forms/textarea-label.html | 7 +- src/components/layout/aspect-ratio-16x9.html | 5 +- src/components/layout/aspect-ratio-1x1.html | 5 +- src/components/layout/aspect-ratio-3x4.html | 5 +- src/components/layout/aspect-ratio-4x3.html | 5 +- src/components/layout/aspect-ratio-4x6.html | 5 +- src/components/layout/aspect-ratio-5x7.html | 5 +- src/components/layout/aspect-ratio-5x8.html | 5 +- src/components/layout/aspect-ratio-6x4.html | 5 +- src/components/layout/aspect-ratio-7x5.html | 5 +- src/components/layout/aspect-ratio-8x5.html | 5 +- src/components/layout/aspect-ratio-9x16.html | 5 +- src/components/layout/centered-container.html | 5 +- .../five-column-collapse-alternate.html | 5 +- .../five-column-collapse-asymmetrical.html | 5 +- .../layout/five-column-collapse-mixed.html | 5 +- .../layout/five-column-collapse-one.html | 5 +- src/components/layout/five-column.html | 5 +- .../fixed-table-layout-grid-mixed-rows.html | 5 +- .../layout/fixed-table-layout-grid.html | 5 +- ...four-column-collapse-two-collapse-one.html | 5 +- .../layout/four-column-collapse-two.html | 5 +- src/components/layout/four-column.html | 5 +- src/components/layout/full-bleed-16x9.html | 45 ++--- src/components/layout/full-bleed-4x6.html | 45 ++--- src/components/layout/full-bleed-5x7.html | 45 ++--- src/components/layout/full-bleed-5x8.html | 45 ++--- src/components/layout/full-bleed-6x4.html | 45 ++--- src/components/layout/full-bleed-7x5.html | 45 ++--- src/components/layout/full-bleed-8x5.html | 45 ++--- src/components/layout/full-bleed-9x16.html | 45 ++--- src/components/layout/full-bleed-square.html | 45 ++--- .../layout/three-column-collapse-one.html | 5 +- src/components/layout/three-column.html | 5 +- .../layout/two-column-collapse-one.html | 5 +- src/components/layout/two-column.html | 5 +- .../links/animate-background-color.html | 7 +- .../lists/block-item-dotted-border.html | 2 +- .../nav/fixed-semi-transparent.html | 5 +- src/components/nav/list-overflow.html | 5 +- .../nav/logo-links-inline-collapse.html | 5 +- src/components/nav/logo-links-inline.html | 5 +- src/components/nav/minimal-sign-up.html | 19 +- src/components/nav/title-link-list.html | 5 +- src/components/quotes/left-border.html | 7 +- src/components/text/large-paragraph.html | 5 +- src/components/text/narrow-paragraph.html | 5 +- src/components/text/paragraph.html | 5 +- .../text/small-narrow-paragraph.html | 5 +- src/components/text/small-paragraph.html | 5 +- src/components/text/wide-paragraph.html | 5 +- src/templates/components-index.html | 16 +- src/templates/components.html | 2 + 78 files changed, 739 insertions(+), 362 deletions(-) create mode 100644 src/components-screenshot-build.js diff --git a/build.js b/build.js index c65d8135b..b907174c1 100644 --- a/build.js +++ b/build.js @@ -1,12 +1,21 @@ -require('./src/header-build')() -require('./src/gallery-build')() -require('./src/resources-build')() -console.log('header build complete') -require('./src/table-of-styles-build')() -console.log('table of styles build complete') -require('./src/table-of-properties-build')() -console.log('table of properties build complete') -require('./src/home-build')() -console.log('home build complete') -//require('./src/components-build')() -//console.log('components build complete') +// require('./src/header-build')() +// require('./src/gallery-build')() +// require('./src/resources-build')() +// console.log('header build complete') +// require('./src/table-of-styles-build')() +// console.log('table of styles build complete') +// require('./src/table-of-properties-build')() +// console.log('table of properties build complete') +// require('./src/home-build')() +// console.log('home build complete') + +// The rest of the build process is async and requires some promises +// var comp_build = Promise.resolve(true); // uncomment and comment next to skip build +var comp_build = require('./src/components-build')(); +comp_build.then(function () { + console.log('components build complete') +}).then(function () { + return require('./src/components-screenshot-build')().then(function () { + console.log('components screenshots complete') + }) +}) diff --git a/package.json b/package.json index bcd45e178..03b59cf4d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,9 @@ "url": "https://github.com/tachyons-css/tachyons-css.github.io/issues" }, "dependencies": { + "co": "^4.6.0", + "express": "^4.14.0", + "nightmare": "^2.8.1", "tachyons-background-size": "^5.0.3", "tachyons-base": "^1.2.5", "tachyons-border-colors": "^4.2.2", diff --git a/src/components-build.js b/src/components-build.js index 822f5e71f..9d416344e 100644 --- a/src/components-build.js +++ b/src/components-build.js @@ -28,93 +28,105 @@ var header = fs.readFileSync('src/templates/header.html', 'utf8') var highlight = fs.readFileSync('src/templates/highlight.html', 'utf8') module.exports = function () { - glob('src/components/**/*.html', {}, function (err, components) { - if (err) { - console.error(err) - return - } - - var template = fs.readFileSync('src/templates/components.html', 'utf8') - var indexTemplate = fs.readFileSync('src/templates/components-index.html', 'utf8') - - var componentsForNav = {} - components.map(function (component) { - var componentTokens = component.replace('src/components/', '').split('/') - var category = componentTokens[0] - - componentsForNav[category] = componentsForNav[category] || [] - componentsForNav[category].push({ - href: component.replace('src', '').replace('.html', '') + '/index.html', - name: getName(component) - }) - }) - - var compiledPage = _.template(indexTemplate)({ - componentsForNav: componentsForNav, - title: 'Components', - analytics: analytics, - footer: footer, - head: head, - header: header, - }) - - mkdirp.sync('components') - fs.writeFileSync('components/index.html', compiledPage) - - components.forEach(function (component) { - var newDir = rmHtmlExt(component.replace('src/', '')) - var newFile = newDir + '/index.html' - var componentHtml = fs.readFileSync(component, 'utf8') - - var fmParsed = fm.parse(componentHtml) - var frontMatter = fmParsed.attributes || {} - frontMatter.bodyClass = frontMatter.bodyClass || '' - frontMatter.title = frontMatter.title || getTitle(component) - frontMatter.name = frontMatter.name || getName(component) - frontMatter.classes = getClasses(fmParsed.body).map(function(klass) { - return '.' + klass + return new Promise(function (resolve, reject) { + glob('src/components/**/*.html', {}, function (err, components) { + if (err) { + console.error(err) + return reject(err) + } + + var template = fs.readFileSync('src/templates/components.html', 'utf8') + var indexTemplate = fs.readFileSync('src/templates/components-index.html', 'utf8') + + var componentsForNav = {} + components.map(function (component) { + var componentTokens = component.replace('src/components/', '').split('/') + var category = componentTokens[0] + + // Check the front matter for screenshot overrides + var componentHtml = fs.readFileSync(component, 'utf8') + var fmParsed = fm.parse(componentHtml) + var frontMatter = fmParsed.attributes || {} + var screenshot = frontMatter.screenshot || {} + screenshot.path = component.replace('src', '').replace('.html', '') + '/screenshot.png' + + componentsForNav[category] = componentsForNav[category] || [] + componentsForNav[category].push({ + href: component.replace('src', '').replace('.html', '') + '/index.html', + name: getName(component), + screenshot: screenshot + }) }) - frontMatter.componentHtml = componentHtml - frontMatter.content = fmParsed.body - frontMatter.escapedHtml = escapeHtml(fmParsed.body) - frontMatter.footer = footer - frontMatter.analytics = analytics - frontMatter.head = head - frontMatter.highlight = highlight - frontMatter.componentsForNav = componentsForNav - - var moduleSrcs = {} - var getModules = postcss.plugin('get-modules', function () { - return function (css, result) { - css.walkRules(function (rule) { - moduleSrcs[rule.source.input.from] = true - }) - } + + var compiledPage = _.template(indexTemplate)({ + componentsForNav: componentsForNav, + title: 'Components', + analytics: analytics, + footer: footer, + head: head, + header: header, }) - postcss([ - atImport(), cssVariables(), conditionals(), customMedia(), select(frontMatter.classes), - removeComments({ removeAll: true }), mqPacker(), removeEmpty(), getModules(), perfectionist() - ]).process(tachyonsCss, { - from: 'src/css/tachyons.css' - }).then(function (result) { - console.log('component css selection complete for', component) - frontMatter.componentCss = result.css - frontMatter.stats = cssstats(frontMatter.componentCss) - - // TODO: Update me once src/ uses the npm modules - frontMatter.modules = Object.keys(moduleSrcs).map(function (module) { - return 'tachyons-' + module.split('/_')[1].replace('.css', '') + mkdirp.sync('components') + fs.writeFileSync('components/index.html', compiledPage) + + var promises = [] + components.forEach(function (component) { + var newDir = rmHtmlExt(component.replace('src/', '')) + var newFile = newDir + '/index.html' + var componentHtml = fs.readFileSync(component, 'utf8') + + var fmParsed = fm.parse(componentHtml) + var frontMatter = fmParsed.attributes || {} + frontMatter.bodyClass = frontMatter.bodyClass || '' + frontMatter.title = frontMatter.title || getTitle(component) + frontMatter.name = frontMatter.name || getName(component) + frontMatter.classes = getClasses(fmParsed.body).map(function(klass) { + return '.' + klass + }) + frontMatter.componentHtml = componentHtml + frontMatter.content = fmParsed.body + frontMatter.escapedHtml = escapeHtml(fmParsed.body) + frontMatter.footer = footer + frontMatter.analytics = analytics + frontMatter.head = head + frontMatter.highlight = highlight + frontMatter.componentsForNav = componentsForNav + + var moduleSrcs = {} + var getModules = postcss.plugin('get-modules', function () { + return function (css, result) { + css.walkRules(function (rule) { + moduleSrcs[rule.source.input.from] = true + }) + } }) - var compiledPage = _.template(template)(frontMatter) - console.log('creating new dir', newDir) - mkdirp.sync(newDir) - fs.writeFileSync(newFile, compiledPage) - console.log('finished component build for', component) - }).catch(function (e) { console.log(e) }) - }) - }) + promises.push(postcss([ + atImport(), cssVariables(), conditionals(), customMedia(), select(frontMatter.classes), + removeComments({ removeAll: true }), mqPacker(), removeEmpty(), getModules(), perfectionist() + ]).process(tachyonsCss, { + from: 'src/css/tachyons.css' + }).then(function (result) { + console.log('component css selection complete for', component) + frontMatter.componentCss = result.css + frontMatter.stats = cssstats(frontMatter.componentCss) + + // TODO: Update me once src/ uses the npm modules + frontMatter.modules = Object.keys(moduleSrcs).map(function (module) { + return 'tachyons-' + module.split('/_')[1].replace('.css', '') + }) + + var compiledPage = _.template(template)(frontMatter) + console.log('creating new dir', newDir) + mkdirp.sync(newDir) + fs.writeFileSync(newFile, compiledPage) + console.log('finished component build for', component) + }).catch(function (e) { console.log(e) })) + }) + resolve(Promise.all(promises)) + }) // glob + }) // return promise } function getTitle(component) { diff --git a/src/components-screenshot-build.js b/src/components-screenshot-build.js new file mode 100644 index 000000000..bc0a39cee --- /dev/null +++ b/src/components-screenshot-build.js @@ -0,0 +1,123 @@ +var co = require('co') +var express = require('express'); +var fm = require('json-front-matter') +var fs = require('fs') +var glob = require('glob') +var Nightmare = require('nightmare') +var path = require('path') +var rmHtmlExt = require('remove-html-extension') + +var screenshotTargetWidth = 1024 +var screenshotMaxHeight = 768 +var screenshotName = 'screenshot.png' +var screenshotSelector = '[data-name="component-container"]' + +module.exports = function () { + return new Promise(function (resolve, reject) { + glob('src/components/**/*.html', {}, function (err, components) { + if (err) { + console.error(err) + return reject(err) + } + + // Setup a quick static HTML server so that CSS is loaded correctly. + var app = express() + app.set('port', 3333) + app.use(express.static(path.join(__dirname, '..'))) + var server = app.listen(app.get('port'), function() { + console.log('Starting static HTML server on port ' + server.address().port) + }) + + // Initialize nightmware now. + var nightmare = Nightmare({ + show: false, + frame: false, + useContentSize: true, + switches: { + // Unfortunately .viewport() sets the viewport size in *CSS pixels*. + // Let's force device pixel ratio to 1 otherwise screenshots will vary in size + // depending on the machine running this script (double for Retina Macbook Pro) + // https://github.com/segmentio/nightmare/issues/498#issuecomment-265948400 + 'force-device-scale-factor': '1' + } + }) + + return co(function *() { + for (var i = 0; i < components.length; i++) { + var newDir = rmHtmlExt(components[i].replace('src/', '')) + var newFile = newDir + '/index.html' + + // Check the front matter for screenshot overrides + var componentHtml = fs.readFileSync(components[i], 'utf8') + var fmParsed = fm.parse(componentHtml) + var frontMatter = fmParsed.attributes || {} + var selector = screenshotSelector + if (frontMatter.screenshot && frontMatter.screenshot.selector) { + selector += ' ' + frontMatter.screenshot.selector + } + + // Grab the size of the component enclosing rectangle + // https://github.com/segmentio/nightmare/issues/498#issuecomment-189156529 + var rect = yield nightmare + .viewport(screenshotTargetWidth, screenshotMaxHeight) + // .wait(1000) + .goto('http://localhost:' + app.get('port') + '/' + newFile) + .wait(selector) + .evaluate(function(_selector) { + // Hide scrollbar that could pop up due to .scrollTo + var sheet = document.styleSheets[0] + sheet.insertRule('::-webkit-scrollbar { display:none; }') + var element = document.querySelector(_selector) + if (element) { + var rect = element.getBoundingClientRect() + return { + x: Math.round(rect.left), + y: Math.round(rect.top), + width: Math.round(rect.width), + height: Math.round(rect.height) + } + } + return false + }, selector) + + // Capture the component + if (rect !== false) { + rect.height = Math.min(rect.height, screenshotMaxHeight) + yield nightmare + // we can not use .screenshot() with rect, so constrain the viewport instead + .viewport(rect.width, rect.height) + .scrollTo(rect.y, rect.x) + // .wait(1000) + // do *not* use rect in .screenshot() below or risk distortions + .screenshot(newDir + '/' + screenshotName) + .then(function () { + console.log('screenshotted: ' + newFile) + }) + } + } + + yield nightmare.end() + }).then(function () { + console.log('finished rendering screenshots') + }, function(err) { + console.error(err) + }).then(function () { + console.log('closing static HTML server') + server.close() + }) + + // TODO: optimize all PNG files, eventually resize to smaller width + // const imagemin = require('imagemin'); + // const imageminPngquant = require('imagemin-pngquant'); + // imagemin(['components/*.{jpg,png}'], ??, { + // plugins: [ + // imageminPngquant({quality: '65-80'}) + // ] + // }).then(files => { + // console.log(files); + // //=> [{data: , path: 'build/images/foo.jpg'}, …] + // }); + + }) // glob + }) // return promise +} diff --git a/src/components/banners/info.html b/src/components/banners/info.html index 8eb1dbcc7..bb779db4a 100644 --- a/src/components/banners/info.html +++ b/src/components/banners/info.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white pt5" + "bodyClass" : "bg-white pt5", + "screenshot" : { + "background-size" : "contain" + } }}}