// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Called by https://pub.dartlang.org/packages/peanut to generate example pages // for hosting. // // Requires at least v3.2.0 of `package:peanut` import 'dart:convert'; import 'dart:io'; import 'package:markdown/markdown.dart'; import 'package:path/path.dart' as p; void main(List<String> args) { final buildDir = args[0]; final fileMap = (jsonDecode(args[1]) as Map<String, dynamic>).cast<String, String>(); if (fileMap.length < 2) { throw StateError('We are assuming there is more than one sample!'); } // This is USUALLY the case – where we have more than one demo for (var exampleDir in fileMap.values) { for (var htmlFile in Directory(p.join(buildDir, exampleDir)) .listSync() .whereType<File>() .where((f) => p.extension(f.path) == '.html')) { _updateHtml(htmlFile, buildDir, exampleDir); } } final tocFile = File(p.join(buildDir, 'index.html')); if (!tocFile.existsSync()) { throw StateError('$tocFile should exist!'); } tocFile.writeAsStringSync( _tocTemplate( fileMap.entries.map( (entry) => _Demo( entry.key, entry.value, ), ), ), flush: true); } void _updateHtml(File htmlFile, String buildDir, String exampleDir) { final content = htmlFile.readAsStringSync(); final filePath = p.relative(htmlFile.path, from: buildDir); if (!content.contains(_standardMeta)) { print('!!! missing standard meta! - $filePath'); } final newContent = content .replaceFirst('<head>', '<head>\n$_analytics') .replaceFirst(_emptyTitle, '<title>${_prettyName(exampleDir)} - Flutter web sample</title>'); if (newContent == content) { print('!!! Did not replace contents in $filePath'); } else { print('Replaced contents in $filePath'); htmlFile.writeAsStringSync(newContent, flush: true); } } class _Demo { final String pkgDir, buildDir; _Demo(this.pkgDir, this.buildDir); String get content { final path = p.normalize(p.join(pkgDir, '..', 'README.md')); final readmeFile = File(path); if (!readmeFile.existsSync()) { print(' $path – No readme!'); return ''; } var readmeContent = readmeFile.readAsStringSync(); final tripleLineIndex = readmeContent.indexOf('\n\n\n'); var secondDoubleIndex = readmeContent.indexOf('\n\n'); if (secondDoubleIndex >= 0) { secondDoubleIndex = readmeContent.indexOf('\n\n', secondDoubleIndex + 1); } final endIndices = ([tripleLineIndex, secondDoubleIndex].where((i) => i >= 0).toList() ..sort()); final endIndex = endIndices.isEmpty ? readmeContent.length : endIndices.first; return markdownToHtml(readmeContent.substring(0, endIndex - 1)); } String get name => _prettyName(buildDir); String get html => ''' <div> <a href='$buildDir'> <img src='${p.url.join(buildDir, 'preview.png')}' width="300" alt="$name"> </a> <a class='demo-title' href='$buildDir'>$name</a> <div> ${_indent(content, 2)} </div> </div> '''; } final _underscoreOrSlash = RegExp('_|/'); String _prettyName(String input) => input.split(_underscoreOrSlash).where((e) => e.isNotEmpty).map((e) { return e.substring(0, 1).toUpperCase() + e.substring(1); }).join(' '); // flutter.github.io const _analyticsId = 'UA-67589403-8'; const _analytics = ''' <script async src="https://www.googletagmanager.com/gtag/js?id=$_analyticsId"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '$_analyticsId'); </script>'''; String _indent(String content, int spaces) => LineSplitter.split(content).join('\n' + ' ' * spaces); const _itemsReplace = r'<!-- ITEMS -->'; const _emptyTitle = '<title></title>'; const _standardMeta = ''' <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> $_emptyTitle'''; String _tocTemplate(Iterable<_Demo> items) => ''' <!DOCTYPE html> <html lang="en"> <head> ${_indent(_analytics, 2)} $_standardMeta <meta name="generator" content="https://pub.dartlang.org/packages/peanut"> <style> body { font-family: "Google Sans", "Roboto", sans-serif; text-align: center; } a { text-decoration: none; color: #1389FD; } a:hover { text-decoration: underline; } #toc { text-align: left; display: flex; flex-wrap: wrap; align-self: center; margin: 0 auto; align-content: space-between; justify-content: center; } #toc > div { width: 300px; padding: 1rem; margin: 0.5rem; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 4px; } #toc > div img { display: block; margin: 0 auto 1rem; } .demo-title { font-size: 1.25rem; } #toc > div p { margin-top: 0.5rem; margin-bottom: 0; } </style> </head> <body> <h2><a href='https://flutter.dev/web'>Flutter for web</a> samples</h2> <a href='https://github.com/flutter/samples/tree/master/web'>Sample source code</a> <div id="toc"> $_itemsReplace </div> </body> </html> ''' .replaceFirst( _itemsReplace, _indent(items.map((d) => d.html).join('\n'), 4)) .replaceFirst(_emptyTitle, '<title>Flutter for web samples</title>');