|
|
|
|
// 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>');
|