1
0
mirror of https://github.com/meineerde/redmine.git synced 2025-10-17 17:01:01 +00:00

Responsive layout for mobile devices (#19097).

Patch by Felix Gliesche.

git-svn-id: http://svn.redmine.org/redmine/trunk@14817 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2015-11-07 13:37:08 +00:00
parent 9c85a341ca
commit e680ae1aa3
4 changed files with 723 additions and 2 deletions

View File

@ -340,6 +340,7 @@ module ApplicationHelper
{ :value => project_path(:id => p, :jump => current_menu_item) }
end
content_tag( :span, nil, :class => 'jump-box-arrow') +
select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
end
end
@ -1267,7 +1268,7 @@ module ApplicationHelper
# Returns the javascript tags that are included in the html layout head
def javascript_heads
tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application')
tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
end

View File

@ -4,11 +4,12 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title><%= html_title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="<%= Redmine::Info.app_name %>" />
<meta name="keywords" content="issue,bug,tracker" />
<%= csrf_meta_tag %>
<%= favicon %>
<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'application', :media => 'all' %>
<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'application', 'responsive', :media => 'all' %>
<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %>
<%= javascript_heads %>
<%= heads_for_theme %>
@ -18,6 +19,44 @@
</head>
<body class="<%= body_css_classes %>">
<div id="wrapper">
<div class="flyout-menu js-flyout-menu">
<% if User.current.logged? || !Setting.login_required? %>
<div class="flyout-menu__search">
<%= form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
<%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
<%= label_tag 'flyout-search', '&#9906;'.html_safe, :class => 'search-magnifier search-magnifier--flyout' %>
<%= text_field_tag 'q', @question, :id => 'flyout-search', :class => 'small js-search-input', :placeholder => l(:label_search) %>
<% end %>
</div>
<% end %>
<% if User.current.logged? %>
<div class="flyout-menu__avatar <% if !Setting.gravatar_enabled? %>flyout-menu__avatar--no-avatar<% end %>">
<% if Setting.gravatar_enabled? %>
<%= link_to(avatar(User.current, :size => "80"), user_path(User.current)) %>
<% end %>
<%= link_to_user(User.current, :format => :username) %>
</div>
<% end %>
<% if display_main_menu?(@project) %>
<h3><%= l(:label_project) %></h3>
<span class="js-project-menu"></span>
<% end %>
<h3><%= l(:label_general) %></h3>
<span class="js-general-menu"></span>
<span class="js-sidebar flyout-menu__sidebar"></span>
<h3><%= l(:label_profile) %></h3>
<span class="js-profile-menu"></span>
</div>
<div id="wrapper2">
<div id="wrapper3">
<div id="top-menu">
@ -29,6 +68,9 @@
</div>
<div id="header">
<a href="#" class="mobile-toggle-button js-flyout-menu-toggle-button"></a>
<% if User.current.logged? || !Setting.login_required? %>
<div id="quick-search">
<%= form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>

View File

@ -0,0 +1,83 @@
// generic layout specific responsive stuff goes here
function openFlyout() {
$('html').addClass('flyout-is-active');
$('#wrapper2').on('click', function(e){
e.preventDefault();
e.stopPropagation();
closeFlyout();
});
}
function closeFlyout() {
$('html').removeClass('flyout-is-active');
$('#wrapper2').off('click');
}
function isMobile() {
return $('.js-flyout-menu-toggle-button').is(":visible");
}
function setupFlyout() {
var mobileInit = false,
desktopInit = false;
/* click handler for mobile menu toggle */
$('.js-flyout-menu-toggle-button').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
if($('html').hasClass('flyout-is-active')) {
closeFlyout();
} else {
openFlyout();
}
});
/* bind resize handler */
$(window).resize(function() {
initMenu();
})
/* menu init function for dom detaching and appending on mobile / desktop view */
function initMenu() {
var _initMobileMenu = function() {
/* only init mobile menu, if it hasn't been done yet */
if(!mobileInit) {
$('#main-menu > ul').detach().appendTo('.js-project-menu');
$('#top-menu > ul').detach().appendTo('.js-general-menu');
$('#sidebar > *').detach().appendTo('.js-sidebar');
$('#account ul').detach().appendTo('.js-profile-menu');
mobileInit = true;
desktopInit = false;
}
}
var _initDesktopMenu = function() {
if(!desktopInit) {
$('.js-project-menu > ul').detach().appendTo('#main-menu');
$('.js-general-menu ul').detach().appendTo('#top-menu');
$('.js-sidebar > *').detach().appendTo('#sidebar');
$('.js-profile-menu ul').detach().appendTo('#account');
desktopInit = true;
mobileInit = false;
}
}
if(isMobile()) {
_initMobileMenu();
} else {
_initDesktopMenu();
}
}
// init menu on page load
initMenu();
}
$(document).ready(setupFlyout);

View File

@ -0,0 +1,595 @@
/*----------------------------------------*\
RESPONSIVE CSS
\*----------------------------------------*/
/*
CONTENTS
A) BASIC MOBILE RESETS
B) HEADER & TOP MENUS
C) MAIN CONTENT & SIDEBAR
D) TOGGLE BUTTON & FLYOUT MENU
*/
/* Hide new elements (toggle button and flyout menu) above 900px */
.mobile-toggle-button,
.flyout-menu
{
display: none;
}
/*
redmine's body is set to min-width: 900px
add first breakpoint here and start adding responsiveness
*/
@media all and (max-width: 899px)
{
/*----------------------------------------*\
A) BASIC MOBILE RESETS
\*----------------------------------------*/
/*
apply natural border box, see: http://www.paulirish.com/2012/box-sizing-border-box-ftw/
this helps us to better deal with percentages and padding / margin
*/
*,
*:before,
*:after
{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body,
html
{
height: 100%;
margin: 0;
padding: 0;
}
html
{
overflow-y: auto; /* avoid 2nd scrollbar on desktop */
}
body
{
overflow-x: hidden; /* hide horizontal overflow */
min-width: 0; /* reset the min-width of 900px */
}
body,
input,
select,
textarea,
button
{
font-size: 14px; /* Set font-size for standard elements to 14px */
}
select
{
max-width: 100%; /* prevent long names within select menues from breaking content */
}
#wrapper
{
position: relative;
max-width: 100%;
}
#wrapper,
#wrapper2
{
margin: 0;
}
/*----------------------------------------*\
B) HEADER & TOP MENUS
\*----------------------------------------*/
#header
{
width: 100%;
height: 64px; /* the height of our header on mobile */
min-height: 0;
margin: 0;
padding: 0;
border: none;
background-color: #628db6;
}
/* Hide project name on mobile (project name is still visible in select menu) */
#header h1
{
display: none !important;
}
/* reset #header a color for mobile toggle button */
#header a.mobile-toggle-button
{
color: #f8f8f8;
}
/* Hide top-menu and main-menu on mobile, because it's placed in our flyout menu */
#top-menu,
#header #main-menu
{
display: none;
}
/* the quick search within header holding search form and #project_quick_jump_box box*/
#header #quick-search
{
float: none;
clear: none; /* there are themes which set clear property, this resets it */
max-width: 100%; /* reset max-width */
margin: 0;
background: inherit;
}
/* this represents the dropdown arrow to left of the mobile project menu */
#header .jump-box-arrow:before
{
/* set a font-size in order to achive same result in different themes */
font-family: Verdana, sans-serif;
font-size: 2em;
line-height: 64px;
position: absolute;
left: 0;
width: 2em;
padding: 0 .5em;
/* achieve dropdwon arrow by scaling a caret character */
content: '^';
-webkit-transform: scale(1,-.8);
-ms-transform: scale(1,-.8);
transform: scale(1,-.8);
text-align: right;
pointer-events: none;
opacity: .6;
}
/* styles for combobox within quick-search (#project_quick_jump_box) */
#header #quick-search select
{
font-size: 1.5em;
font-weight: bold;
line-height: 1.2;
position: absolute;
top: 15px;
left: 0;
float: left;
width: 100%;
max-width: 100%;
height: 2em;
height: 35px;
padding: 5px;
padding-right: 72px;
padding-left: 50px;
text-indent: .01px;
color: inherit;
border: 0;
-webkit-border-radius: 0;
border-radius: 0;
background: none;
-webkit-box-shadow: none;
box-shadow: none;
/* hide default browser arrow */
-webkit-appearance: none;
-moz-appearance: none;
}
#header #quick-search form
{
display: none;
}
/*----------------------------------------*\
C) MAIN CONTENT & SIDEBAR
\*----------------------------------------*/
#main
{
padding: 0;
}
#main.nosidebar #content,
div#content
{
width: 100%;
min-height: 0; /* reset min-height of #content */
margin: 0;
}
/* hide sidebar and sidebar switch panel, since it's placed in mobile flyout menu */
#sidebar,
#sidebar-switch-panel
{
display: none;
}
.splitcontentleft
{
width: 100%; /* use full width */
}
.splitcontentright
{
width: 100%; /* use full width */
}
/*----------------------------------------*\
D) TOGGLE BUTTON & FLYOUT MENU
\*----------------------------------------*/
/* Mobile toggle button */
.mobile-toggle-button
{
font-size: 42px;
line-height: 64px;
position: relative;
z-index: 10;
display: block; /* remove display: none; of non-mobile version */
float: right;
width: 60px;
height: 64px;
margin-top: 0;
text-align: center;
border-left: 1px solid #ddd;
}
.mobile-toggle-button:hover,
.mobile-toggle-button:active
{
text-decoration: none;
}
.mobile-toggle-button:after
{
font-family: Verdana, sans-serif;
display: block;
margin-top: -3px;
content: '\2261';
}
/* search magnifier icon */
.search-magnifier
{
font-family: Verdana;
cursor: pointer;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
color: #bbb;
}
.search-magnifier--flyout
{
font-size: 25px;
line-height: 54px;
position: absolute;
z-index: 1;
left: 12px;
}
/* Flyout Menu */
.flyout-menu
{
position: absolute;
right: -250px;
display: block; /* remove display: none; of non-mobile version */
overflow-x: hidden;
width: 250px;
height: 100%;
margin: 0; /* reset margin for themes that define it */
padding: 0; /* reset padding for themes that define it */
color: white;
background-color: #3e5b76;
}
/* avoid zoom on search input focus for ios devices */
.flyout-menu input[type='text']
{
font-size: 16px;
}
.flyout-menu h3
{
font-size: 11px;
line-height: 19px;
height: 20px;
margin: 0;
padding: 0;
letter-spacing: .1em;
text-transform: uppercase;
color: white;
border-top: 1px solid #506a83;
border-bottom: 1px solid #506a83;
background-color: #628db6;
}
.flyout-menu h4
{
color: white;
}
.flyout-menu h3,
.flyout-menu h4,
.flyout-menu > p,
.flyout-menu > a,
.flyout-menu ul li a,
.flyout-menu__search,
.flyout-menu__sidebar > div,
.flyout-menu__sidebar > p,
.flyout-menu__sidebar > a,
.flyout-menu__sidebar > form,
.flyout-menu > div,
.flyout-menu > form
{
padding-left: 8px;
}
.flyout-menu .flyout-menu__avatar
{
margin-top: -1px; /* move avatar up 1px */
padding-left: 0;
}
.flyout-menu__sidebar > form
{
display: block;
}
.flyout-menu__sidebar > form h3
{
margin-left: -8px;
}
.flyout-menu__sidebar > form label
{
display: inline-block;
margin: 8px 0;
}
.flyout-menu__sidebar > form br br
{
display: none;
}
.flyout-menu ul
{
margin: 0;
padding: 0;
list-style: none;
}
.flyout-menu ul li a
{
line-height: 40px;
display: block;
overflow: hidden;
height: 40px;
white-space: nowrap;
text-overflow: ellipsis;
border-top: 1px solid rgba(255,255,255,.1);
}
.flyout-menu ul li:first-child a
{
line-height: 39px;
height: 39px;
border-top: none;
}
.flyout-menu a
{
color: white;
}
.flyout-menu ul li a:hover
{
text-decoration: none;
}
.flyout-menu ul li a.new-object,
.new-object ~ .menu-children
{
display: none;
}
/* Left flyout search container */
.flyout-menu__search
{
line-height: 54px;
height: 64px;
padding-top: 3px;
padding-right: 8px;
}
.flyout-menu__search input[type='text']
{
line-height: 2;
width: 100%;
height: 38px;
padding-left: 27px;
vertical-align: middle;
border: none;
-webkit-border-radius: 3px;
border-radius: 3px;
background-color: #fff;
}
.flyout-menu__avatar
{
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
width: 100%;
border-top: 1px solid rgba(255,255,255,.1);
}
.flyout-menu__avatar img.gravatar
{
width: 40px;
height: 40px;
padding: 0;
vertical-align: top;
border-width: 0;
}
.flyout-menu__avatar a
{
line-height: 40px;
height: auto;
height: 40px;
text-decoration: none;
color: white;
}
/* avatar */
.flyout-menu__avatar a:first-child
{
line-height: 0;
width: 40px;
padding: 0;
}
.flyout-menu__avatar .user
{
padding-left: 15px;
}
/* user link when no avatar is present */
.flyout-menu__avatar--no-avatar a.user
{
line-height: 40px;
padding-left: 8px;
}
.flyout-is-active body
{
overflow: hidden; /* for body not to have scrollbars when left flyout menu is active */
}
html.flyout-is-active
{
overflow: hidden;
}
.flyout-is-active #wrapper
{
right: 250px; /* when left flyout is active, move body to the right (same amount like flyout-menu's width) */
height: 100%;
}
.flyout-is-active .mobile-toggle-button:after
{
content: '\00D7'; /* close glyph */
}
.flyout-is-active #wrapper2
{
/*
* only relevant for devices with cursor when flyout it active, in order to show,
* that whole wrapper content is clickable and closes flyout menu
*/
cursor: pointer;
}
#admin-menu
{
padding-left: 0;
}
#admin-menu li
{
padding-bottom: 0;
}
#admin-menu a,
#admin-menu a.selected
{
line-height: 40px;
padding: 0;
padding-left: 32px !important;
background-position: 8px 50%;
}
}