Commit ead6120f authored by Mark Chao's avatar Mark Chao Committed by Phil Hughes

Fix sidebar in epics block view on mobile

parent ea7da4a4
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import loadingButton from '~/vue_shared/components/loading_button.vue'; import loadingButton from '~/vue_shared/components/loading_button.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import eventHub from '../../event_hub';
export default { export default {
name: 'EpicHeader', name: 'EpicHeader',
...@@ -43,6 +44,9 @@ ...@@ -43,6 +44,9 @@
this.$emit('deleteEpic'); this.$emit('deleteEpic');
} }
}, },
toggleSidebar() {
eventHub.$emit('toggleSidebar');
},
}, },
}; };
</script> </script>
...@@ -71,5 +75,14 @@ ...@@ -71,5 +75,14 @@
container-class="btn btn-remove btn-inverted flex-right" container-class="btn btn-remove btn-inverted flex-right"
@click="deleteEpic" @click="deleteEpic"
/> />
<button
:aria-label="__('toggle collapse')"
class="btn btn-default float-right d-block d-sm-none
gutter-toggle issuable-gutter-toggle js-sidebar-toggle"
type="button"
@click="toggleSidebar"
>
<i class="fa fa-angle-double-left"></i>
</button>
</div> </div>
</template> </template>
...@@ -195,7 +195,7 @@ ...@@ -195,7 +195,7 @@
</script> </script>
<template> <template>
<div> <div class="epic-page-container">
<epic-header <epic-header
:author="author" :author="author"
:created="created" :created="created"
......
import Vue from 'vue'; import Vue from 'vue';
import Cookies from 'js-cookie';
import bp from '~/breakpoints';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import EpicShowApp from './components/epic_show_app.vue'; import EpicShowApp from './components/epic_show_app.vue';
...@@ -7,6 +9,12 @@ export default () => { ...@@ -7,6 +9,12 @@ export default () => {
const metaData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.meta)); const metaData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.meta));
const initialData = JSON.parse(el.dataset.initial); const initialData = JSON.parse(el.dataset.initial);
// Collapse the sidebar on mobile screens by default
const bpBreakpoint = bp.getBreakpointSize();
if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
Cookies.set('collapsed_gutter', true);
}
const props = Object.assign({}, initialData, metaData, el.dataset); const props = Object.assign({}, initialData, metaData, el.dataset);
return new Vue({ return new Vue({
......
import $ from 'jquery'; import $ from 'jquery';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import Cookies from 'js-cookie';
import bp from '~/breakpoints';
export default class SidebarContext { export default class SidebarContext {
constructor() { constructor() {
...@@ -33,6 +35,14 @@ export default class SidebarContext { ...@@ -33,6 +35,14 @@ export default class SidebarContext {
setTimeout(() => $block.find('.js-label-select').trigger('click'), 0); setTimeout(() => $block.find('.js-label-select').trigger('click'), 0);
} }
}); });
window.addEventListener('beforeunload', () => {
// collapsed_gutter cookie hides the sidebar
const bpBreakpoint = bp.getBreakpointSize();
if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
Cookies.set('collapsed_gutter', true);
}
});
} }
static openSidebarDropdown($block) { static openSidebarDropdown($block) {
......
import Vue from 'vue';
export default new Vue();
...@@ -11,6 +11,7 @@ import SidebarTodo from '~/sidebar/components/todo_toggle/todo.vue'; ...@@ -11,6 +11,7 @@ import SidebarTodo from '~/sidebar/components/todo_toggle/todo.vue';
import SidebarCollapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue'; import SidebarCollapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue'; import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
import SidebarLabelsSelect from '~/vue_shared/components/sidebar/labels_select/base.vue'; import SidebarLabelsSelect from '~/vue_shared/components/sidebar/labels_select/base.vue';
import eventHub from '../../event_hub';
import SidebarDatePicker from './sidebar_date_picker.vue'; import SidebarDatePicker from './sidebar_date_picker.vue';
import SidebarParticipants from './sidebar_participants.vue'; import SidebarParticipants from './sidebar_participants.vue';
import SidebarSubscriptions from './sidebar_subscriptions.vue'; import SidebarSubscriptions from './sidebar_subscriptions.vue';
...@@ -214,6 +215,12 @@ export default { ...@@ -214,6 +215,12 @@ export default {
: this.store.dueDateTimeFromMilestones; : this.store.dueDateTimeFromMilestones;
}, },
}, },
mounted() {
eventHub.$on('toggleSidebar', this.toggleSidebar);
},
beforeDestroy() {
eventHub.$off('toggleSidebar', this.toggleSidebar);
},
methods: { methods: {
getDateValidity(startDate, endDate) { getDateValidity(startDate, endDate) {
// If both dates are defined // If both dates are defined
...@@ -425,7 +432,7 @@ export default { ...@@ -425,7 +432,7 @@ export default {
/> />
</div> </div>
<div <div
v-if="collapsed" v-if="collapsed && isUserSignedIn"
class="block todo" class="block todo"
> >
<sidebar-todo <sidebar-todo
...@@ -456,6 +463,7 @@ export default { ...@@ -456,6 +463,7 @@ export default {
block-class="start-date" block-class="start-date"
@saveDate="saveStartDate" @saveDate="saveStartDate"
@toggleDateType="changeStartDateType" @toggleDateType="changeStartDateType"
@toggleCollapse="toggleSidebar"
/> />
<sidebar-date-picker <sidebar-date-picker
v-if="!collapsed" v-if="!collapsed"
......
...@@ -7,6 +7,7 @@ import popover from '~/vue_shared/directives/popover'; ...@@ -7,6 +7,7 @@ import popover from '~/vue_shared/directives/popover';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import DatePicker from '~/vue_shared/components/pikaday.vue'; import DatePicker from '~/vue_shared/components/pikaday.vue';
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue'; import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
const label = __('Date picker'); const label = __('Date picker');
const pickerLabel = __('Fixed date'); const pickerLabel = __('Fixed date');
...@@ -20,6 +21,7 @@ export default { ...@@ -20,6 +21,7 @@ export default {
Icon, Icon,
DatePicker, DatePicker,
CollapsedCalendarIcon, CollapsedCalendarIcon,
ToggleSidebar,
}, },
props: { props: {
blockClass: { blockClass: {
...@@ -32,6 +34,11 @@ export default { ...@@ -32,6 +34,11 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
showToggleSidebar: {
type: Boolean,
required: false,
default: false,
},
isLoading: { isLoading: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -159,6 +166,9 @@ export default { ...@@ -159,6 +166,9 @@ export default {
toggleDateType(dateTypeFixed) { toggleDateType(dateTypeFixed) {
this.$emit('toggleDateType', dateTypeFixed); this.$emit('toggleDateType', dateTypeFixed);
}, },
toggleSidebar() {
this.$emit('toggleCollapse');
},
}, },
}; };
</script> </script>
...@@ -193,6 +203,11 @@ export default { ...@@ -193,6 +203,11 @@ export default {
> >
{{ __('Edit') }} {{ __('Edit') }}
</button> </button>
<toggle-sidebar
v-if="showToggleSidebar"
:collapsed="collapsed"
@toggle="toggleSidebar"
/>
</div> </div>
</div> </div>
<div class="value"> <div class="value">
......
...@@ -34,6 +34,12 @@ ...@@ -34,6 +34,12 @@
} }
} }
@include media-breakpoint-down(xs) {
.epic-page-container .detail-page-header {
display: flex;
}
}
.tooltip .tooltip-inner .milestone-date-range { .tooltip .tooltip-inner .milestone-date-range {
color: $gl-text-color-tertiary; color: $gl-text-color-tertiary;
} }
...@@ -16,24 +16,19 @@ module EpicsHelper ...@@ -16,24 +16,19 @@ module EpicsHelper
todo_exists: todo.present?, todo_exists: todo.present?,
todo_path: group_todos_path(group), todo_path: group_todos_path(group),
start_date: epic.start_date, start_date: epic.start_date,
due_date: epic.due_date,
end_date: epic.end_date
}
epic_meta[:todo_delete_path] = dashboard_todo_path(todo) if todo.present?
if Ability.allowed?(current_user, :update_epic, epic.group)
epic_meta.merge!(
start_date_fixed: epic.start_date_fixed,
start_date_is_fixed: epic.start_date_is_fixed?, start_date_is_fixed: epic.start_date_is_fixed?,
start_date_fixed: epic.start_date_fixed,
start_date_from_milestones: epic.start_date_from_milestones, start_date_from_milestones: epic.start_date_from_milestones,
start_date_sourcing_milestone_title: epic.start_date_sourcing_milestone&.title, start_date_sourcing_milestone_title: epic.start_date_sourcing_milestone&.title,
due_date_fixed: epic.due_date_fixed, due_date: epic.due_date,
due_date_is_fixed: epic.due_date_is_fixed?, due_date_is_fixed: epic.due_date_is_fixed?,
due_date_fixed: epic.due_date_fixed,
due_date_from_milestones: epic.due_date_from_milestones, due_date_from_milestones: epic.due_date_from_milestones,
due_date_sourcing_milestone_title: epic.due_date_sourcing_milestone&.title due_date_sourcing_milestone_title: epic.due_date_sourcing_milestone&.title,
) end_date: epic.end_date
end }
epic_meta[:todo_delete_path] = dashboard_todo_path(todo) if todo.present?
participants = UserSerializer.new.represent(epic.participants) participants = UserSerializer.new.represent(epic.participants)
initial = opts[:initial].merge(labels: epic.labels, initial = opts[:initial].merge(labels: epic.labels,
......
...@@ -5,31 +5,7 @@ describe EpicsHelper do ...@@ -5,31 +5,7 @@ describe EpicsHelper do
describe '#epic_show_app_data' do describe '#epic_show_app_data' do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:epic) { create(:epic, author: user) }
before do
allow(helper).to receive(:current_user).and_return(user)
stub_licensed_features(epics: true)
end
it 'returns the correct json' do
data = helper.epic_show_app_data(epic, initial: {}, author_icon: 'icon_path')
meta_data = JSON.parse(data[:meta])
expected_keys = %i(initial meta namespace labels_path toggle_subscription_path labels_web_url epics_web_url)
expect(data.keys).to match_array(expected_keys)
expect(meta_data.keys).to match_array(%w[created author start_date end_date due_date epic_id todo_exists todo_path])
expect(meta_data['author']).to eq({
'name' => user.name,
'url' => "/#{user.username}",
'username' => "@#{user.username}",
'src' => 'icon_path'
})
end
context 'when user has edit permission' do
let(:milestone) { create(:milestone, title: 'make me a sandwich') } let(:milestone) { create(:milestone, title: 'make me a sandwich') }
let!(:epic) do let!(:epic) do
create( create(
:epic, :epic,
...@@ -42,25 +18,33 @@ describe EpicsHelper do ...@@ -42,25 +18,33 @@ describe EpicsHelper do
end end
before do before do
epic.group.add_developer(user) allow(helper).to receive(:current_user).and_return(user)
stub_licensed_features(epics: true)
end end
it 'returns extra date fields if user can edit' do it 'returns the correct json' do
data = helper.epic_show_app_data(epic, initial: {}, author_icon: 'icon_path') data = helper.epic_show_app_data(epic, initial: {}, author_icon: 'icon_path')
meta_data = JSON.parse(data[:meta]) meta_data = JSON.parse(data[:meta])
expected_keys = %i(initial meta namespace labels_path toggle_subscription_path labels_web_url epics_web_url)
expect(data.keys).to match_array(expected_keys)
expect(meta_data.keys).to match_array(%w[ expect(meta_data.keys).to match_array(%w[
created author epic_id todo_exists todo_path created author epic_id todo_exists todo_path
start_date start_date_fixed start_date_is_fixed start_date_from_milestones start_date_sourcing_milestone_title start_date start_date_fixed start_date_is_fixed start_date_from_milestones start_date_sourcing_milestone_title
end_date due_date due_date_fixed due_date_is_fixed due_date_from_milestones due_date_sourcing_milestone_title end_date due_date due_date_fixed due_date_is_fixed due_date_from_milestones due_date_sourcing_milestone_title
]) ])
expect(meta_data['author']).to eq({
'name' => user.name,
'url' => "/#{user.username}",
'username' => "@#{user.username}",
'src' => 'icon_path'
})
expect(meta_data['start_date']).to eq('2000-01-01') expect(meta_data['start_date']).to eq('2000-01-01')
expect(meta_data['start_date_sourcing_milestone_title']).to eq(milestone.title) expect(meta_data['start_date_sourcing_milestone_title']).to eq(milestone.title)
expect(meta_data['due_date']).to eq('2000-01-02') expect(meta_data['due_date']).to eq('2000-01-02')
expect(meta_data['due_date_sourcing_milestone_title']).to eq(milestone.title) expect(meta_data['due_date_sourcing_milestone_title']).to eq(milestone.title)
end end
end end
end
describe '#epic_endpoint_query_params' do describe '#epic_endpoint_query_params' do
it 'it includes epic specific options in JSON format' do it 'it includes epic specific options in JSON format' do
......
...@@ -34,6 +34,10 @@ describe('epicHeader', () => { ...@@ -34,6 +34,10 @@ describe('epicHeader', () => {
); );
}); });
it('should render sidebar toggle button', () => {
expect(vm.$el.querySelector('button.js-sidebar-toggle')).not.toBe(null);
});
describe('canDelete', () => { describe('canDelete', () => {
it('should not show loading button by default', () => { it('should not show loading button by default', () => {
expect(vm.$el.querySelector('.btn-remove')).toBeNull(); expect(vm.$el.querySelector('.btn-remove')).toBeNull();
......
...@@ -177,6 +177,14 @@ describe('SidebarParticipants', () => { ...@@ -177,6 +177,14 @@ describe('SidebarParticipants', () => {
expect(vm.$emit).toHaveBeenCalledWith('toggleDateType', true); expect(vm.$emit).toHaveBeenCalledWith('toggleDateType', true);
}); });
}); });
describe('toggleSidebar', () => {
it('emits `toggleCollapse` event on component', () => {
spyOn(vm, '$emit');
vm.toggleSidebar();
expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse');
});
});
}); });
describe('template', () => { describe('template', () => {
...@@ -188,6 +196,16 @@ describe('SidebarParticipants', () => { ...@@ -188,6 +196,16 @@ describe('SidebarParticipants', () => {
expect(vm.$el.querySelector('.sidebar-collapsed-icon')).not.toBe(null); expect(vm.$el.querySelector('.sidebar-collapsed-icon')).not.toBe(null);
}); });
it('renders collapse button when `showToggleSidebar` prop is `true`', done => {
vm.showToggleSidebar = true;
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('button.btn-sidebar-action')).not.toBe(null);
})
.then(done)
.catch(done.fail);
});
it('renders title element', () => { it('renders title element', () => {
expect(vm.$el.querySelector('.title')).not.toBe(null); expect(vm.$el.querySelector('.title')).not.toBe(null);
}); });
......
...@@ -9173,6 +9173,9 @@ msgstr "" ...@@ -9173,6 +9173,9 @@ msgstr ""
msgid "to help your contributors communicate effectively!" msgid "to help your contributors communicate effectively!"
msgstr "" msgstr ""
msgid "toggle collapse"
msgstr ""
msgid "username" msgid "username"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment