Bug 661688: Make the file tree and control panel float to the left of the file viewer:

• The file tree and controls are restricted to the height of the
   viewport and the boundaries of the content wrapper.

 • When the file tree is collapsed, the control buttons remain
   visible and the file tree is visible on hover.

Style and RTL fixes per review.
This commit is contained in:
Kris Maglione 2011-08-12 14:31:09 -04:00 коммит произвёл Chris Van
Родитель b8b0546647
Коммит 44715acb29
5 изменённых файлов: 295 добавлений и 120 удалений

Просмотреть файл

@ -15,61 +15,109 @@
data-validation-failed="{{ _("Validation failed:") }}"></div>
<h3>
<a href="{{ file_link['url'] }}">{{ addon.name }} {{ version }}</a>
{% if file.platform.id != amo.PLATFORM_ALL.id %}({{ file.platform }}){% endif %}
<a href="{{ file_link['url'] }}">{{ addon.name }} {{ version }}</a>
{% if file.platform.id != amo.PLATFORM_ALL.id %}({{ file.platform }}){% endif %}
</h3>
<ol id="top" class="breadcrumbs">
<li><a href="{{ file_link['url'] }}">{{ addon.name }}</a></li>
<li><a href="{{ url('addons.versions', addon.slug) }}">{{ version }}</a></li>
<li><a href="{{ file_link['url'] }}">{{ addon.name }}</a></li>
<li><a href="{{ url('addons.versions', addon.slug) }}">{{ version }}</a></li>
</ol>
<div id="file-viewer" class="featured">
<div class="featured-inner">
<div class="featured-inner">
<div id="file-viewer-inner">
<div id="messages">
{% if not status %}
<p class="waiting" id="extracting" data-url="{{ poll_url }}">
{{ _('Add-on file being processed, please wait.') }}
</p>
<div class="notification-box error js-hidden"></div>
<p class="waiting" id="extracting" data-url="{{ poll_url }}">
{{ _('Add-on file being processed, please wait.') }}
</p>
{% endif %}
<div id="validating" class="js-hidden">
<span class="waiting">{{ _('Fetching validation results...') }}</span>
<span class="waiting">{{ _('Fetching validation results...') }}</span>
</div>
<div id="files">
{% if files %}
</div>
<div id="files">
<div id="files-inner">
{% if files %}
<div id="files-wrapper">
<div id="files-tree">
<h4>{{ _('Files:') }}</h4>
{{ file_tree(files, key) }}
{% if diff and files_deleted %}
<h4>{{ _('Deleted files:') }}</h4>
{{ file_tree(files_deleted, key) }}
<h4>{{ _('Deleted files:') }}</h4>
{{ file_tree(files_deleted, key) }}
{% endif %}
<p id="controls">
<div id="tab-stops-container">
<label class="control" for="tab-stops">{{ _('Tab stops:') }}</label>
<input id="tab-stops" type="range" size="1" />
</div>
</p>
</div>
</div>
<div id="controls">
<div id="controls-inner">
<div id="tab-stops-container">
<label class="control" for="tab-stops">{{ _('Tab stops:') }}</label>
<input id="tab-stops" type="range" size="1" />
</div>
<table id="commands">
<tr><th><code>k</code></th> <td><a href="#" id="files-up">{{ _('Previous file') }}</a></td></tr>
<tr><th><code>j</code></th> <td><a href="#" id="files-down">{{ _('Next file') }}</a></td></tr>
{% if diff %}
<tr><th><code>[c</code></th> <td><a href="#" id="files-change-prev">{{ _('Previous change') }}</a></td></tr>
<tr><th><code>]c</code></th> <td><a href="#" id="files-change-next">{{ _('Next change') }}</a></td></tr>
{% endif %}
<tr><th><code>e</code></th> <td><a href="#" id="files-expand-all">{{ _('Expand all') }}</a></td></tr>
<tr><th><code>h</code></th> <td><a href="#" id="files-hide">{{ _('Hide or unhide tree') }}</a></td></tr>
<tr><th><code>w</code></th> <td><a href="#" id="files-wrap">{{ _('Wrap or unwrap text') }}</a></td></tr>
<tr><th></th> <td><a href="{{ file_link['url'] }}">{{ file_link['text'] }}</a></td></tr>
<tr id="files-up">
<th><code title="{{ _('Up file') }}">k</code></th>
<td><a href="#" class="command">{{ _('Up file') }}</a></td>
</tr>
<tr id="files-down">
<th><code title="{{ _('Down file') }}">j</code></th>
<td><a href="#" class="command">{{ _('Down file') }}</a></td>
</tr>
<tr id="files-change-prev">
{% if diff %}
<th><code title="{{ _('Previous diff') }}">[c</code></th>
<td><a href="#" class="command">{{ _('Previous diff') }}</a></td>
{% else %}
<th><code title="{{ _('Previous note') }}">[c</code></th>
<td><a href="#" class="command">{{ _('Previous note') }}</a></td>
{% endif %}
</tr>
<tr id="files-change-next">
{% if diff %}
<th><code title="{{ _('Next diff') }}">]c</code></th>
<td><a href="#" class="command">{{ _('Next diff') }}</a></td>
{% else %}
<th><code title="{{ _('Next note') }}">]c</code></th>
<td><a href="#" class="command">{{ _('Next note') }}</a></td>
{% endif %}
</tr>
<tr id="files-expand-all">
<th><code title="{{ _('Expand all') }}">e</code></th>
<td><a href="#" class="command">{{ _('Expand all') }}</a></td>
</tr>
<tr id="files-hide">
<th><code title="{{ _('Hide or unhide tree') }}">h</code></th>
<td><a href="#" class="command">{{ _('Hide or unhide tree') }}</a></td>
</tr>
<tr id="files-wrap">
<th><code title="{{ _('Wrap or unwrap text') }}">w</code></th>
<td><a href="#" class="command">{{ _('Wrap or unwrap text') }}</a></td>
</tr>
<tr><th></th> <td><a href="{{ file_link['url'] }}" class="command">{{ file_link['text'] }}</a></td></tr>
</table>
{% elif not files and status %}
<div>{{ _('No files in the uploaded file.') }}</div>
{% endif %}
</div>
</div>
{% elif not files and status %}
<div>{{ _('No files in the uploaded file.') }}</div>
{% endif %}
</div>
<div id="thinking" class="js-hidden">
<p class="waiting">
{{ _('Fetching file.') }}
</p>
</div>
{% include "files/content.html" %}
</div>
<div id="thinking" class="js-hidden">
<p class="waiting">
{{ _('Fetching file.') }}
</p>
</div>
{% include "files/content.html" %}
</div>
</div>
</div>
{% endblock %}

Просмотреть файл

@ -1,11 +1,31 @@
#file-viewer, #files-wrapper, #file-viewer table tr td {
/* The file viewer currently completely falls apart in RTL mode.
The contents also tend to be formatted for LTR regardless of
locale. */
direction: ltr;
text-align: left;
}
.html-rtl #files,
.html-rtl h4,
.html-rtl #diff-wrapper + * {
direction: rtl;
text-align: right;
}
#file-viewer div.featured-inner {
padding: 1em;
}
#file-viewer-inner {
position: relative;
min-height: 40em;
}
#files {
width: 20%;
display: block;
float: left;
position: absolute;
width: 20em;
z-index: 2;
}
#files li a {
@ -14,13 +34,90 @@
}
#files ul.root {
overflow-x: auto;
white-space: nowrap;
overflow: visible;
}
#files ul.root ul {
padding-left: 20px;
}
#files-inner {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#files-wrapper {
max-height: 100%;
overflow: auto;
}
#content-wrapper, #messages, #thinking {
padding-left: 23em;
}
#controls {
height: 0;
}
#controls-inner {
padding-bottom: 1ex;
}
.collapsed-files #files {
width: 2em;
}
.collapsed-files #content-wrapper, .collapsed-files #messages, .collapsed-collapsed #thinking {
padding-left: 3em;
}
.collapsed-files #files-wrapper {
height: 1.8em;
width: 1.8em;
overflow: visible;
background: url(../../img/developers/folder.png) no-repeat center center;
}
.collapsed-files #files-wrapper:hover {
border-top: 1px solid #000;
margin-top: -1px;
}
.collapsed-files #files-wrapper:hover > #files-tree {
display: block;
background: #fff;
position: absolute;
margin: -1px 0 0 1.8em;
max-height: 100%;
overflow-y: auto;
padding-left: 1em;
width: 20em;
border: 1px solid #000;
border-left: none;
border-radius: 0 1em 1em 0;
}
.collapsed-files #files-tree {
display: none;
}
.collapsed-files .command,
.collapsed-files #tab-stops-container {
display: none !important;
}
.collapsed-files #files {
padding-bottom: 0 !important;
}
/* Hack to find the default font's em width. */
#metadata {
width: 1em;
}
/* The following fixes the minimum width and makes
the pages wrap correctly on very large peices of text. */
body.file-viewer div.section {
@ -33,18 +130,10 @@ body.file-viewer div.section {
max-width: 58em;
}
.full .syntaxhighlighter .line {
.collapsed-files .syntaxhighlighter .line {
max-width: 80em;
}
#content-wrapper, #thinking {
padding-left: 25%;
}
#content-wrapper.full, #thinking.full {
padding-left: 0;
}
#validating {
text-align: center;
margin-bottom: 1em;
@ -77,10 +166,14 @@ div.diff-bar .delete, #diff td.code div.delete, #diff td.code div.delete .conten
color: #444444;
}
#diff .syntaxhighlighter, #diff table {
.syntaxhighlighter, .syntaxhighlighter table {
margin: 0;
}
#diff-wrapper {
margin-bottom: 1em;
}
a.closed {
background: url('../../img/icons/plus.gif') 0 no-repeat;
}
@ -127,7 +220,7 @@ a.selected > span {
margin: -1px;
-moz-border-radius: 3px;
border-radius: 3px;
border: 1px solid black;
border: 1px solid #000;
}
.code .notice, .code .warning, .code .error {
margin: -1px;
@ -179,6 +272,7 @@ div.diff-bar {
padding: 0px 2px;
border-radius: 2px;
-moz-border-radius: 2px;
cursor: pointer;
}
#commands td {
@ -189,6 +283,10 @@ div.diff-bar {
padding-left: 11px;
}
.diff-bar.js-hidden + #diff, .diff-bar.js-hidden + #content {
padding-left: 0;
}
#diff-wrapper {
position: relative;
}
@ -338,9 +436,10 @@ td.gutter, td.code {
.number-combo-button-up:hover,
.number-combo-button-down:hover {
text-shadow: black 0 0 .01em;
text-shadow: #000 0 0 .01em;
}
#tab-stops-container {
display: none;
margin: 1ex 0;
}

Просмотреть файл

@ -931,6 +931,7 @@ div.popup-shim {
position: absolute;
padding: 0.5em 1em;
pointer-events: none;
z-index: 16389;
}
#tooltip.error {

Двоичные данные
media/img/developers/folder.png Executable file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 537 B

Просмотреть файл

@ -192,29 +192,14 @@ function bind_viewer(nodes) {
this.hidden = false;
this.top = null;
this.last = null;
this.fix_vertically = function($inner, $outer) {
var $self = this;
if (!$self.top) {
$self.top = $outer.position().top;
}
function update() {
var sb_bottom = $self.top + $outer.height() - $inner.height();
if ($(window).scrollTop() > sb_bottom) {
$inner.css({'position': 'absolute', 'top': sb_bottom});
} else if ($(window).scrollTop() > $self.top) {
$inner.css({'position': 'fixed', 'top': 0});
} else {
$inner.css({'position': 'absolute', 'top': $self.top});
}
}
$(window).scroll(debounce(update), 200);
update();
};
this.size_line_numbers = function($node, deleted) {
/* We need to re-size the line numbers correctly depending upon
the wrapping. */
$node.each(function() {
/* Be sure to make all size calculations before modifying
* the DOM so that we don't force unnecessary reflows. */
var $self = $(this),
long_lines = $(this).find('td.code div.longline'),
changes = [];
@ -229,14 +214,14 @@ function bind_viewer(nodes) {
$self.find(change[0]).css('height', change[1]);
});
});
this.updateViewport(true);
this.update_viewport(true);
};
this.compute = function(node) {
var $diff = node.find('#diff'),
$content = node.find('#content');
if ($content && !$diff.length) {
SyntaxHighlighter.highlight($content);
SyntaxHighlighter.highlight({}, $content[0]);
// Note SyntaxHighlighter has nuked the node and replaced it.
this.size_line_numbers(node.find('#content'), false);
}
@ -251,7 +236,7 @@ function bind_viewer(nodes) {
/* Reset the syntax highlighter variables. */
SyntaxHighlighter.amo_vars = {'deletions': {}, 'additions': {}, 'is_diff': true};
SyntaxHighlighter.highlight($diff);
SyntaxHighlighter.highlight({}, $diff[0]);
// Note SyntaxHighlighter has nuked the node and replaced it.
$diff = node.find('#diff');
this.size_line_numbers($diff, true);
@ -262,6 +247,8 @@ function bind_viewer(nodes) {
if (window.location.hash && window.location.hash != 'top') {
window.location = window.location;
}
this.show_selected();
};
this.message_type_map = {
'error': 'error',
@ -312,6 +299,8 @@ function bind_viewer(nodes) {
$sb.empty();
if ($lines.length) {
/* Be sure to make all size calculations before modifying
* the DOM so that we don't force unnecessary reflows. */
var changes = [];
var flush = function($line, bottom) {
var height = (bottom - $start.offset().top) * 100 / $gutter.height(),
@ -360,12 +349,12 @@ function bind_viewer(nodes) {
$sb.append(this.$viewport);
$diff.addClass('diff-bar-height');
$sb.show();
$sb.removeClass('js-hidden');
this.updateViewport(true);
this.update_viewport(true);
}
}
}
};
this.update_validation = function(data) {
var viewer = this;
@ -444,33 +433,60 @@ function bind_viewer(nodes) {
data.error) }));
}
};
this.updateViewport = function(resize) {
var $viewport = this.$viewport,
$gutter = this.$gutter,
$diffbar = this.$diffbar;
if (!$viewport) {
return;
this.fix_vertically = function($node, changes, resize) {
var rect = $node.parent()[0].getBoundingClientRect(),
window_height = $(window).height();
if (resize) {
changes.push([$node, { 'height': Math.min(rect.bottom - rect.top,
window_height) + 'px' }]);
}
var gutter = $gutter[0].getBoundingClientRect(),
gutter_height = gutter.bottom - gutter.top,
window_height = $(window).height(),
diffbar_top = -Math.min(0, gutter.top) * 100 / gutter_height + "%",
height = Math.max(0, gutter_height + Math.min(0, gutter.top) - Math.max(gutter.bottom - window_height, 0));
if (rect.bottom <= window_height) {
changes.push([$node, { 'position': 'absolute', 'top': '', 'bottom': '0' }]);
} else if (rect.top > 0) {
changes.push([$node, { 'position': 'absolute', 'top': '0', 'bottom': '' }]);
} else {
changes.push([$node, { 'position': 'fixed', 'top': '0px', 'bottom': '' }]);
}
};
this.update_viewport = function(resize) {
/* Be sure to make all size calculations before modifying
* the DOM so that we don't force unnecessary reflows. */
var $viewport = this.$viewport,
changes = [];
if (resize) {
$diffbar.css({ 'height': Math.min($('#diff-wrapper').height(), window_height) + "px" });
var height = $('#controls-inner').height() / $('#metadata').width();
changes.push([$('#files-inner'), { 'padding-bottom': height + 'em' }]);
}
$viewport.css({ 'height': height * 100 / gutter_height + '%', 'top': diffbar_top });
this.fix_vertically(this.nodes.$files, changes, resize);
if (gutter.bottom <= window_height) {
$diffbar.css({ 'position': 'absolute', 'top': '', 'bottom': '0' });
} else if (gutter.top > 0) {
$diffbar.css({ 'position': 'absolute', 'top': '0', 'bottom': '' });
} else {
$diffbar.css({ 'position': 'fixed', 'top': '0px', 'bottom': '' });
if ($viewport) {
var $gutter = this.$gutter,
$diffbar = this.$diffbar,
gr = $gutter[0].getBoundingClientRect(),
gh = gr.bottom - gr.top,
height = Math.max(0, Math.min(gr.bottom, $(window).height())
- Math.max(gr.top, 0));
changes.push([$viewport,
{ 'height': height / gh * 100 + '%',
'top': -Math.min(0, gr.top) / gh * 100 + '%' }]);
this.fix_vertically($diffbar, changes, resize);
}
$.each(changes, function (i, change) {
change[0].css(change[1]);
});
if (resize && !($.browser.mozilla || $.browser.webkit)) {
/* Opera does not handle max-height:100% properly in
* this case, and I won't place any bets on IE. */
var $wrapper = $('#files-wrapper');
$wrapper.css({ 'max-height': $wrapper.parent().height() + 'px' });
}
};
this.toggle_leaf = function($leaf) {
@ -501,6 +517,23 @@ function bind_viewer(nodes) {
} else {
$('.breadcrumbs').append(format('<li>{0}</li>', $link.attr('data-short')));
}
this.show_selected();
};
this.show_selected = function() {
var $sel = this.nodes.$files.find('.selected'),
$cont = $('#files-wrapper');
if ($sel.position().top < 0) {
$cont.scrollTop($cont.scrollTop() + $sel.position().top);
} else {
/* Unfortunately, jQuery does not provide anything
comparable to clientHeight, which we can't do without */
var offset = $sel.position().top + $sel.outerHeight() - $cont[0].clientHeight;
if (offset > 0) {
$cont.scrollTop($cont.scrollTop() + offset);
}
}
};
this.load = function($link) {
/* Accepts a jQuery wrapped node, which is part of the tree.
@ -510,6 +543,7 @@ function bind_viewer(nodes) {
$old_wrapper = $('#content-wrapper');
$old_wrapper.hide();
this.nodes.$thinking.show();
this.update_viewport(true);
if (location.hash != 'top') {
if (history.pushState !== undefined) {
this.last = $link.attr('href');
@ -525,9 +559,6 @@ function bind_viewer(nodes) {
var $new_wrapper = $('#content-wrapper');
self.compute($new_wrapper);
$new_wrapper.slideDown();
if (self.hidden) {
self.toggle_files('hide');
}
}
}
);
@ -558,18 +589,14 @@ function bind_viewer(nodes) {
this.size_line_numbers($('#content-wrapper'), false);
}
};
this.toggle_files = function(state) {
this.hidden = (state == 'hide' || !this.hidden);
if (this.hidden) {
this.nodes.$files.hide();
this.nodes.$commands.detach().appendTo('div.featured-inner:first');
this.nodes.$thinking.addClass('full');
} else {
this.nodes.$files.show();
this.nodes.$commands.detach().appendTo(this.nodes.$files);
this.nodes.$thinking.removeClass('full');
}
$('#content-wrapper').toggleClass('full');
this.toggle_files = function(action) {
var collapse = null;
if (action == 'hide')
collapse = true;
else if (action == 'show')
collapse = false;
$('#file-viewer').toggleClass('collapsed-files', collapse);
this.size_line_numbers($('#content-wrapper'), false);
};
this.next_changed = function(offset) {
@ -580,9 +607,11 @@ function bind_viewer(nodes) {
var a = [], list = a;
$files.each(function() {
var $file = $(this);
if (this == selected) {
list = [selected];
} else if (config.needreview_pattern.test($(this).attr('data-short')) && $(this).is(filter)) {
} else if (($file.is('.notice, .warning, .error') || config.needreview_pattern.test($file.attr('data-short')))
&& $file.is(filter)) {
list.push(this);
}
});
@ -635,18 +664,16 @@ function bind_viewer(nodes) {
var viewer = new Viewer();
if (viewer.nodes.$files.find('li').length == 1) {
viewer.toggle_files();
$('#files-down').parent().hide();
$('#files-up').parent().hide();
$('#files-expand-all').parent().hide();
viewer.toggle_files('hide');
$('#files-down, #files-up, #files-expand-all').hide();
}
viewer.nodes.$files.find('.directory').click(_pd(function() {
viewer.toggle_leaf($(this));
}));
$(window).resize(debounce(function() { viewer.updateViewport(true); }));
$(window).scroll(debounce(function() { viewer.updateViewport(false); }));
$(window).resize(debounce(function() { viewer.update_viewport(true); }))
.scroll(debounce(function() { viewer.update_viewport(false); }));
$('#files-up').click(_pd(function() {
viewer.next_changed(-1);