Commit fea96df5 authored by Paul Slaughter's avatar Paul Slaughter Committed by Phil Hughes

Extract ide_status_list from ide_status_bar

**Why?**
The ide_status_list will be used and extended in EE.
parent 26f09226
...@@ -146,7 +146,7 @@ export default { ...@@ -146,7 +146,7 @@ export default {
</div> </div>
<component :is="rightPaneComponent" v-if="currentProjectId" /> <component :is="rightPaneComponent" v-if="currentProjectId" />
</div> </div>
<ide-status-bar :file="activeFile" /> <ide-status-bar />
<new-modal /> <new-modal />
</article> </article>
</template> </template>
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import IdeStatusList from 'ee_else_ce/ide/components/ide_status_list.vue';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import timeAgoMixin from '~/vue_shared/mixins/timeago'; import timeAgoMixin from '~/vue_shared/mixins/timeago';
...@@ -12,18 +13,12 @@ export default { ...@@ -12,18 +13,12 @@ export default {
icon, icon,
userAvatarImage, userAvatarImage,
CiIcon, CiIcon,
IdeStatusList,
}, },
directives: { directives: {
tooltip, tooltip,
}, },
mixins: [timeAgoMixin], mixins: [timeAgoMixin],
props: {
file: {
type: Object,
required: false,
default: null,
},
},
data() { data() {
return { return {
lastCommitFormatedAge: null, lastCommitFormatedAge: null,
...@@ -125,11 +120,6 @@ export default { ...@@ -125,11 +120,6 @@ export default {
>{{ lastCommitFormatedAge }}</time >{{ lastCommitFormatedAge }}</time
> >
</div> </div>
<div v-if="file" class="ide-status-file">{{ file.name }}</div> <ide-status-list class="ml-auto" />
<div v-if="file" class="ide-status-file">{{ file.eol }}</div>
<div v-if="file && !file.binary" class="ide-status-file">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
<div v-if="file" class="ide-status-file">{{ file.fileLanguage }}</div>
</footer> </footer>
</template> </template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['activeFile']),
},
};
</script>
<template>
<div class="ide-status-list d-flex">
<template v-if="activeFile">
<div class="ide-status-file">{{ activeFile.name }}</div>
<div class="ide-status-file">{{ activeFile.eol }}</div>
<div v-if="!activeFile.binary" class="ide-status-file">
{{ activeFile.editorRow }}:{{ activeFile.editorColumn }}
</div>
<div class="ide-status-file">{{ activeFile.fileLanguage }}</div>
</template>
<slot></slot>
</div>
</template>
...@@ -396,10 +396,6 @@ $ide-commit-header-height: 48px; ...@@ -396,10 +396,6 @@ $ide-commit-header-height: 48px;
font-size: inherit; font-size: inherit;
} }
> div + div {
padding-left: $gl-padding;
}
svg { svg {
vertical-align: sub; vertical-align: sub;
} }
...@@ -410,13 +406,14 @@ $ide-commit-header-height: 48px; ...@@ -410,13 +406,14 @@ $ide-commit-header-height: 48px;
} }
} }
.ide-status-list {
> div + div {
padding-left: $gl-padding;
}
}
.ide-status-file { .ide-status-file {
text-align: right; text-align: right;
.ide-status-branch + &,
&:first-child {
margin-left: auto;
}
} }
// Not great, but this is to deal with our current output // Not great, but this is to deal with our current output
.multi-file-preview-holder { .multi-file-preview-holder {
......
<script>
import IdeStatusList from '~/ide/components/ide_status_list.vue';
import TerminalSyncStatusSafe from './terminal_sync/terminal_sync_status_safe.vue';
export default {
components: {
IdeStatusList,
TerminalSyncStatusSafe,
},
};
</script>
<template>
<ide-status-list>
<terminal-sync-status-safe />
</ide-status-list>
</template>
<script>
import _ from 'underscore';
import { GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import { mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import {
MSG_TERMINAL_SYNC_CONNECTING,
MSG_TERMINAL_SYNC_UPLOADING,
MSG_TERMINAL_SYNC_RUNNING,
} from '../../messages';
export default {
components: {
Icon,
GlLoadingIcon,
},
directives: {
'gl-tooltip': GlTooltipDirective,
},
data() {
return { isLoading: false };
},
computed: {
...mapState('terminalSync', ['isError', 'isStarted', 'message']),
...mapState('terminalSync', {
isLoadingState: 'isLoading',
}),
status() {
if (this.isLoading) {
return {
icon: '',
text: this.isStarted ? MSG_TERMINAL_SYNC_UPLOADING : MSG_TERMINAL_SYNC_CONNECTING,
};
} else if (this.isError) {
return {
icon: 'warning',
text: this.message,
};
} else if (this.isStarted) {
return {
icon: 'mobile-issue-close',
text: MSG_TERMINAL_SYNC_RUNNING,
};
}
return null;
},
},
watch: {
// We want to throttle the `isLoading` updates so that
// the user actually sees an indicator that changes are sent.
isLoadingState: _.throttle(function watchIsLoadingState(val) {
this.isLoading = val;
}, 150),
},
created() {
this.isLoading = this.isLoadingState;
},
};
</script>
<template>
<div
v-if="status"
v-gl-tooltip
:title="status.text"
role="note"
class="d-flex align-items-center"
>
<span>{{ __('Terminal') }}:</span>
<span class="square s16 d-flex-center ml-1" :aria-label="status.text">
<gl-loading-icon v-if="isLoading" inline size="sm" class="d-flex-center" />
<icon v-else-if="status.icon" :name="status.icon" :size="16" />
</span>
</div>
</template>
<script>
import { mapState } from 'vuex';
import TerminalSyncStatus from './terminal_sync_status.vue';
/**
* It is possible that the vuex module is not registered.
*
* This component will gracefully handle this so the actual one can simply use `mapState(moduleName, ...)`.
*/
export default {
components: {
TerminalSyncStatus,
},
computed: {
...mapState(['terminalSync']),
},
};
</script>
<template>
<terminal-sync-status v-if="terminalSync" />
</template>
import { __ } from '~/locale';
export const MSG_TERMINAL_SYNC_CONNECTING = __('Connecting to terminal sync service');
export const MSG_TERMINAL_SYNC_UPLOADING = __('Uploading changes to terminal');
export const MSG_TERMINAL_SYNC_RUNNING = __('Terminal sync service is running');
---
title: Sync file changes from Web IDE to Web Terminal
merge_request: 14035
author:
type: added
import Vuex from 'vuex';
import { createStore } from '~/ide/stores';
import { mount, createLocalVue } from '@vue/test-utils';
import TerminalSyncStatusSafe from 'ee/ide/components/terminal_sync/terminal_sync_status_safe.vue';
import IdeStatusList from 'ee/ide/components/ide_status_list.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('ee/ide/components/ide_status_list', () => {
let store;
let wrapper;
const createComponent = () => {
store = createStore();
wrapper = mount(localVue.extend(IdeStatusList), {
localVue,
sync: false,
store,
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders terminal sync status', () => {
createComponent();
expect(wrapper.find(TerminalSyncStatusSafe).exists()).toBe(true);
});
});
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import TerminalSyncStatus from 'ee/ide/components/terminal_sync/terminal_sync_status.vue';
import TerminalSyncStatusSafe from 'ee/ide/components/terminal_sync/terminal_sync_status_safe.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('ee/ide/components/terminal_sync/terminal_sync_status_safe', () => {
let store;
let wrapper;
const createComponent = () => {
store = new Vuex.Store({
state: {},
});
wrapper = shallowMount(localVue.extend(TerminalSyncStatusSafe), {
localVue,
sync: false,
store,
});
};
beforeEach(createComponent);
afterEach(() => {
wrapper.destroy();
});
describe('with terminal sync module in store', () => {
beforeEach(() => {
store.registerModule('terminalSync', {
state: {},
});
});
it('renders terminal sync status', () => {
expect(wrapper.find(TerminalSyncStatus).exists()).toBe(true);
});
});
describe('without terminal sync module', () => {
it('does not render terminal sync status', () => {
expect(wrapper.find(TerminalSyncStatus).exists()).toBe(false);
});
});
});
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import TerminalSyncStatus from 'ee/ide/components/terminal_sync/terminal_sync_status.vue';
import {
MSG_TERMINAL_SYNC_CONNECTING,
MSG_TERMINAL_SYNC_UPLOADING,
MSG_TERMINAL_SYNC_RUNNING,
} from 'ee/ide/messages';
const TEST_MESSAGE = 'lorem ipsum dolar sit';
const START_LOADING = 'START_LOADING';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('ee/ide/components/terminal_sync/terminal_sync_status', () => {
let moduleState;
let store;
let wrapper;
const createComponent = () => {
store = new Vuex.Store({
modules: {
terminalSync: {
namespaced: true,
state: moduleState,
mutations: {
[START_LOADING]: state => {
state.isLoading = true;
},
},
},
},
});
wrapper = shallowMount(localVue.extend(TerminalSyncStatus), {
localVue,
sync: false,
store,
});
};
beforeEach(() => {
moduleState = {
isLoading: false,
isStarted: false,
isError: false,
message: '',
};
});
afterEach(() => {
wrapper.destroy();
});
describe('when doing nothing', () => {
it('shows nothing', () => {
createComponent();
expect(wrapper.isEmpty()).toBe(true);
});
});
describe.each`
description | state | statusMessage | icon
${'when loading'} | ${{ isLoading: true }} | ${MSG_TERMINAL_SYNC_CONNECTING} | ${''}
${'when loading and started'} | ${{ isLoading: true, isStarted: true }} | ${MSG_TERMINAL_SYNC_UPLOADING} | ${''}
${'when error'} | ${{ isError: true, message: TEST_MESSAGE }} | ${TEST_MESSAGE} | ${'warning'}
${'when started'} | ${{ isStarted: true }} | ${MSG_TERMINAL_SYNC_RUNNING} | ${'mobile-issue-close'}
`('$description', ({ state, statusMessage, icon }) => {
beforeEach(() => {
Object.assign(moduleState, state);
createComponent();
});
it('shows message', () => {
expect(wrapper.attributes('data-original-title')).toContain(statusMessage);
});
if (!icon) {
it('does not render icon', () => {
expect(wrapper.find(Icon).exists()).toBe(false);
});
it('renders loading icon', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
} else {
it('renders icon', () => {
expect(wrapper.find(Icon).props('name')).toEqual(icon);
});
it('does not render loading icon', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
}
});
});
...@@ -3454,6 +3454,9 @@ msgstr "" ...@@ -3454,6 +3454,9 @@ msgstr ""
msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled." msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
msgstr "" msgstr ""
msgid "Connecting to terminal sync service"
msgstr ""
msgid "Connecting..." msgid "Connecting..."
msgstr "" msgstr ""
...@@ -12939,6 +12942,9 @@ msgstr "" ...@@ -12939,6 +12942,9 @@ msgstr ""
msgid "Terminal for environment" msgid "Terminal for environment"
msgstr "" msgstr ""
msgid "Terminal sync service is running"
msgstr ""
msgid "Terms of Service Agreement and Privacy Policy" msgid "Terms of Service Agreement and Privacy Policy"
msgstr "" msgstr ""
...@@ -14382,6 +14388,9 @@ msgstr "" ...@@ -14382,6 +14388,9 @@ msgstr ""
msgid "Uploaded on" msgid "Uploaded on"
msgstr "" msgstr ""
msgid "Uploading changes to terminal"
msgstr ""
msgid "Uploads" msgid "Uploads"
msgstr "" msgstr ""
......
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import IdeStatusList from '~/ide/components/ide_status_list';
const TEST_FILE = {
name: 'lorem.md',
eol: 'LF',
editorRow: 3,
editorColumn: 23,
fileLanguage: 'markdown',
};
const localVue = createLocalVue();
localVue.use(Vuex);
describe('ide/components/ide_status_list', () => {
let activeFile;
let store;
let wrapper;
const createComponent = (options = {}) => {
store = new Vuex.Store({
getters: {
activeFile: () => activeFile,
},
});
wrapper = shallowMount(localVue.extend(IdeStatusList), {
localVue,
sync: false,
store,
...options,
});
};
beforeEach(() => {
activeFile = TEST_FILE;
});
afterEach(() => {
wrapper.destroy();
store = null;
wrapper = null;
});
const getEditorPosition = file => `${file.editorRow}:${file.editorColumn}`;
describe('with regular file', () => {
beforeEach(() => {
createComponent();
});
it('shows file name', () => {
expect(wrapper.text()).toContain(TEST_FILE.name);
});
it('shows file eol', () => {
expect(wrapper.text()).toContain(TEST_FILE.name);
});
it('shows file editor position', () => {
expect(wrapper.text()).toContain(getEditorPosition(TEST_FILE));
});
it('shows file language', () => {
expect(wrapper.text()).toContain(TEST_FILE.fileLanguage);
});
});
describe('with binary file', () => {
beforeEach(() => {
activeFile.binary = true;
createComponent();
});
it('does not show file editor position', () => {
expect(wrapper.text()).not.toContain(getEditorPosition(TEST_FILE));
});
});
it('adds slot as child of list', () => {
createComponent({
slots: {
default: ['<div class="js-test">Hello</div>', '<div class="js-test">World</div>'],
},
});
expect(wrapper.find('.ide-status-list').findAll('.js-test').length).toEqual(2);
});
});
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