blob: c70e0bea89df902cb9ca4b55151ae652c4a1913b [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import '../log/log.dart';
import '../server.dart';
typedef Writer = void Function(StringSink sink);
/// A class used to write an HTML page.
abstract class PageWriter {
/// The object used to escape special HTML characters.
static final HtmlEscape htmlEscape = HtmlEscape();
/// Initialize a newly create page writer.
PageWriter();
/// Return the length of the common prefix for time stamps associated with the
/// given log [entries].
int computePrefixLength(List<LogEntry> entries) {
var length = entries.length;
if (length < 2) {
return 0;
}
var firstTime = entries[0].timeStamp.toString();
var lastTime = entries[length - 1].timeStamp.toString();
var prefixLength = 0;
var timeLength = firstTime.length;
while (prefixLength < timeLength &&
firstTime.codeUnitAt(prefixLength) ==
lastTime.codeUnitAt(prefixLength)) {
prefixLength++;
}
return prefixLength;
}
/// Return an escaped version of the given [unsafe] text.
String escape(String unsafe) {
// We double escape single quotes because the escaped characters are
// processed as part of reading the HTML, which means that single quotes
// end up terminating string literals too early when they appear in event
// handlers (which in turn leads to JavaScript syntax errors).
return htmlEscape.convert(unsafe).replaceAll('&#39;', '&amp;#39;');
}
/// Write the body of the page (without the 'body' tag) to the given [sink].
void writeBody(StringSink sink);
/// Write the given [date] to the given [sink].
void writeDate(StringSink sink, DateTime date) {
var isoString = date.toIso8601String();
var index = isoString.indexOf('T');
var dateString = isoString.substring(0, index);
var timeString = isoString.substring(index + 1);
sink.write(dateString);
sink.write(' at ');
sink.write(timeString);
}
/// Write the body of the page (without the 'body' tag) to the given [sink].
void writeMenu(StringSink sink) {
sink.writeln('<div class="menu">');
sink.write('<a href="${WebServer.logPath}" class="menuItem">Log</a>');
sink.write('&nbsp;&bullet;&nbsp;');
sink.write('<a href="${WebServer.statsPath}" class="menuItem">Stats</a>');
sink.writeln('</div>');
}
/// Write the contents of the instrumentation log to the given [sink].
void writePage(StringSink sink) {
sink.writeln('<!DOCTYPE html>');
sink.writeln('<html lang="en-US">');
sink.writeln('<head>');
sink.writeln('<meta charset="utf-8">');
sink.writeln(
'<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0">');
sink.writeln('<title>Instrumentation Log</title>');
sink.writeln('<style>');
writeStyleSheet(sink);
sink.writeln('</style>');
sink.writeln('<script>');
writeScripts(sink);
sink.writeln('</script>');
sink.writeln('</head>');
sink.writeln('<body>');
writeBody(sink);
sink.writeln('</body>');
sink.writeln('</html>');
}
/// Write the scripts for the page (without the 'script' tag) to the given
/// [sink].
void writeScripts(StringSink sink) {
// No common scripts.
}
/// Write the content of the style sheet (without the 'script' tag) for the
/// page to the given [sink].
void writeStyleSheet(StringSink sink) {
sink.writeln(r'''
a {
color: #000000;
text-decoration: none;
}
a.menuItem {
font-weight: bold;
}
body {
font-family: sans-serif;
height: 100%;
margin: 0px;
overflow: hidden;
padding: 0px;
width: 100%;
}
div.columnHeader {
}
div.button {
display: inline-block;
border-radius: 4px;
border: 1px solid;
height: 16px;
text-align: center;
vertical-align: middle;
width: 16px;
}
div.inset {
padding: 10px;
}
div.menu {
background-color: #cce6ff;
padding: 5px;
}
html {
height: 100%;
width: 100%;
}
span.button {
border-radius: 5px;
border: 1px solid;
height: 16px;
width: 16px;
}
span.error {
color: #ff0000;
}
span.gray {
color: #777777;
}
span.label {
font-weight: bold;
}
table.fullWidth {
border: 0px;
width: 100%;
}
td.halfWidth {
width: 50%;
vertical-align: top;
}
td.int {
text-align: right;
}
th {
text-align: left;
}
th.narrow {
width: 16px;
}
#container {
height: 100%;
min-height: 100%;
position: relative;
width: 100%;
}
#content {
height: 90%;
width: 100%;
}
''');
}
/// Write to the given [sink] the HTML required to display content in two
/// columns. The content of the columns will be written by the functions
/// [writeLeftColumn], [writeCenterColumn] and [writeRightColumn] and will be
/// contained in 'div' elements with the id's [leftColumnId], [centerColumnId]
/// and [rightColumnId].
void writeThreeColumns(
StringSink sink,
String leftColumnId,
Writer writeLeftColumn,
String centerColumnId,
Writer writeCenterColumn,
String rightColumnId,
Writer writeRightColumn) {
sink.writeln('<div>');
sink.writeln(' <div>');
sink.writeln(' <div id="$leftColumnId">');
sink.writeln(' <div class="inset">');
writeLeftColumn(sink);
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln(' <div id="$rightColumnId">');
sink.writeln(' <div class="inset">');
writeRightColumn(sink);
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln(' <div id="$centerColumnId">');
sink.writeln(' <div class="inset">');
writeCenterColumn(sink);
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln('</div>');
}
/// Writeto the given [sink] the styles needed by a three column section where
/// the columns have the ids [leftColumnId], [centerColumnId] and
/// [rightColumnId].
void writeThreeColumnStyles(StringSink sink, String leftColumnId,
String centerColumnId, String rightColumnId) {
sink.writeln('''
#$leftColumnId {
float: left;
height: 100%;
overflow: auto;
width: 33%;
}
#$centerColumnId {
height: 100%;
overflow: auto;
width: 33%;
}
#$rightColumnId {
float: right;
height: 100%;
overflow: auto;
width: 33%;
}
''');
}
/// Write to the given [sink] the HTML required to display content in two
/// columns. The content of the columns will be written by the functions
/// [writeLeftColumn] and [writeRightColumn] and will be contained in 'div'
/// elements with the id's [leftColumnId] and [rightColumnId].
void writeTwoColumns(StringSink sink, String leftColumnId,
Writer writeLeftColumn, String rightColumnId, Writer writeRightColumn) {
sink.writeln('<div id="container">');
sink.writeln(' <div id="content">');
sink.writeln(' <div id="$leftColumnId">');
sink.writeln(' <div class="inset">');
writeLeftColumn(sink);
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln(' <div id="$rightColumnId">');
sink.writeln(' <div class="inset">');
writeRightColumn(sink);
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln(' </div>');
sink.writeln('</div>');
}
/// Writeto the given [sink] the styles needed by a two column section where
/// the columns have the ids [leftColumnId] and [rightColumnId].
void writeTwoColumnStyles(
StringSink sink, String leftColumnId, String rightColumnId) {
sink.writeln('''
#$leftColumnId {
float: left;
height: 100%;
overflow: auto;
width: 50%;
}
#$rightColumnId {
float: right;
height: 100%;
overflow: auto;
width: 50%;
}
''');
}
}