diff options
-rw-r--r-- | app/assets/javascripts/ide/components/ide.vue | 6 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/panes/right.vue | 27 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/preview/clientside.vue | 131 | ||||
-rw-r--r-- | app/assets/javascripts/ide/constants.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/ide/services/index.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/getters.js | 2 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/repo.scss | 4 |
7 files changed, 165 insertions, 10 deletions
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 2d2415354f9..257a7432c20 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -9,7 +9,6 @@ import RepoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue'; import RightPane from './panes/right.vue'; import ErrorMessage from './error_message.vue'; -import Preview from './preview.vue'; const originalStopCallback = Mousetrap.stopCallback; @@ -23,7 +22,6 @@ export default { FindFile, RightPane, ErrorMessage, - Preview, }, computed: { ...mapState([ @@ -35,7 +33,6 @@ export default { 'emptyStateSvgPath', 'currentProjectId', 'errorMessage', - 'entries', ]), ...mapGetters(['activeFile', 'hasChanges']), }, @@ -137,9 +134,6 @@ export default { </div> </template> </div> - <preview - v-if="Object.keys(entries).length" - /> <right-pane v-if="currentProjectId" /> diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index e4a5fcc67c4..1f291eb497f 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -1,5 +1,5 @@ <script> -import { mapActions, mapState } from 'vuex'; +import { mapActions, mapState, mapGetters } from 'vuex'; import tooltip from '../../../vue_shared/directives/tooltip'; import Icon from '../../../vue_shared/components/icon.vue'; import { rightSidebarViews } from '../../constants'; @@ -7,6 +7,7 @@ import PipelinesList from '../pipelines/list.vue'; import JobsDetail from '../jobs/detail.vue'; import MergeRequestInfo from '../merge_requests/info.vue'; import ResizablePanel from '../resizable_panel.vue'; +import Clientside from '../preview/clientside.vue'; export default { directives: { @@ -18,9 +19,11 @@ export default { JobsDetail, ResizablePanel, MergeRequestInfo, + Clientside, }, computed: { ...mapState(['rightPane', 'currentMergeRequestId']), + ...mapGetters(['packageJson']), pipelinesActive() { return ( this.rightPane === rightSidebarViews.pipelines || @@ -49,7 +52,7 @@ export default { :collapsible="false" :initial-width="350" :min-size="350" - class="multi-file-commit-panel-inner" + :class="['multi-file-commit-panel-inner', `ide-right-sidebar-${rightPane}`]" side="right" > <component :is="rightPane" /> @@ -98,6 +101,26 @@ export default { /> </button> </li> + <li v-if="packageJson"> + <button + v-tooltip + :title="__('Clientside preview')" + :aria-label="__('Clientside preview')" + :class="{ + active: rightPane === $options.rightSidebarViews.clientSidePreview + }" + data-container="body" + data-placement="left" + class="ide-sidebar-link is-right" + type="button" + @click="clickTab($event, $options.rightSidebarViews.clientSidePreview)" + > + <icon + :size="16" + name="eye" + /> + </button> + </li> </ul> </nav> </div> diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue new file mode 100644 index 00000000000..8ad5817b939 --- /dev/null +++ b/app/assets/javascripts/ide/components/preview/clientside.vue @@ -0,0 +1,131 @@ +<script> +import { mapState } from 'vuex'; +import _ from 'underscore'; +import { Manager } from 'smooshpack'; +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { + Icon, + }, + data() { + return { + iframeSrc: '', + }; + }, + computed: { + ...mapState(['entries']), + normalizedEntries() { + return Object.keys(this.entries).reduce((acc, path) => { + const file = this.entries[path]; + + return { + ...acc, + [`/${path}`]: { + code: file.content || file.raw, + }, + }; + }, {}); + }, + }, + watch: { + entries: { + deep: true, + handler: 'update', + }, + }, + mounted() { + this.manager = new Manager('#ide-preview', { + files: this.normalizedEntries, + entry: '/index.js', + dependencies: {}, + }); + + this.iframeSrc = this.manager.bundlerURL; + }, + methods: { + update: _.debounce(function throttleUpdate() { + this.manager.updatePreview({ + files: this.normalizedEntries, + entry: '/index.js', + dependencies: {}, + }); + }, 500), + }, +}; +</script> + +<template> + <div class="preview h-100 w-100 d-flex flex-column"> + <header class="ide-preview-header d-flex align-items-center"> + <button + type="button" + class="d-transparent border-0" + > + <icon + :size="18" + name="chevron-left" + /> + </button> + <button + type="button" + class="d-transparent border-0" + > + <icon + :size="18" + name="chevron-right" + /> + </button> + <button + type="button" + class="d-transparent border-0" + > + <icon + name="retry" + /> + </button> + <input + v-model="iframeSrc" + type="text" + class="form-control bg-white w-100" + readonly + /> + <a + :href="iframeSrc" + target="_blank" + rel="noopener noreferrer" + class="d-transparent border-0" + > + <icon + name="external-link" + /> + </a> + </header> + <div id="ide-preview"></div> + </div> +</template> + +<style scoped> +.preview { + width: 500px; + border-left: 1px solid #eaeaea; +} + +.ide-preview-header { + padding: 8px; + border-bottom: 1px solid #eaeaea; + background-color: #fafafa; +} + +.ide-preview-header button, +.ide-preview-header a { + height: 16px; + padding: 0 4px; + color: #5c5c5c; +} + +.ide-preview-header input { + margin-left: 8px; + margin-right: 8px; +} +</style> diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 45d36f6f42c..38f2877ec59 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -32,6 +32,7 @@ export const rightSidebarViews = { pipelines: 'pipelines-list', jobsDetail: 'jobs-detail', mergeRequestInfo: 'merge-request-info', + clientSidePreview: 'clientside', }; export const stageKeys = { diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index 49a481f25d5..d4501f6a8f2 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -18,7 +18,7 @@ export default { return axios .get(file.rawPath, { - params: { format: 'json' }, + transformResponse: [res => res], }) .then(({ data }) => data); }, @@ -33,7 +33,7 @@ export default { return axios .get(file.rawPath.replace(`/raw/${file.branchId}/${file.path}`, `/raw/${sha}/${file.path}`), { - params: { format: 'json' }, + transformResponse: [res => res], }) .then(({ data }) => data); }, diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index 5ce268b0d05..357ef449fdb 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -90,5 +90,7 @@ export const lastCommit = (state, getters) => { export const currentBranch = (state, getters) => getters.currentProject && getters.currentProject.branches[state.currentBranchId]; +export const packageJson = state => state.entries['package.json']; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 2d76f0ce004..734b0f66396 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -1158,6 +1158,10 @@ background-color: $white-light; border-left: 1px solid $white-dark; } + + .ide-right-sidebar-clientside { + padding: 0; + } } .ide-pipeline { |