summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/blob
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/blob')
-rw-r--r--app/assets/javascripts/blob/3d_viewer/index.js147
-rw-r--r--app/assets/javascripts/blob/3d_viewer/mesh_object.js49
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js2
-rw-r--r--app/assets/javascripts/blob/blob_fork_suggestion.js15
-rw-r--r--app/assets/javascripts/blob/file_template_mediator.js241
-rw-r--r--app/assets/javascripts/blob/file_template_selector.js60
-rw-r--r--app/assets/javascripts/blob/notebook/index.js85
-rw-r--r--app/assets/javascripts/blob/notebook_viewer.js3
-rw-r--r--app/assets/javascripts/blob/pdf/index.js62
-rw-r--r--app/assets/javascripts/blob/pdf_viewer.js3
-rw-r--r--app/assets/javascripts/blob/sketch/index.js73
-rw-r--r--app/assets/javascripts/blob/sketch_viewer.js8
-rw-r--r--app/assets/javascripts/blob/stl_viewer.js19
-rw-r--r--app/assets/javascripts/blob/template_selector.js (renamed from app/assets/javascripts/blob/template_selectors/template_selector.js)0
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js9
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js23
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js9
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js23
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js9
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js23
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_license_selector.js13
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_license_selectors.js24
-rw-r--r--app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js32
-rw-r--r--app/assets/javascripts/blob/template_selectors/dockerfile_selector.js32
-rw-r--r--app/assets/javascripts/blob/template_selectors/gitignore_selector.js31
-rw-r--r--app/assets/javascripts/blob/template_selectors/license_selector.js38
-rw-r--r--app/assets/javascripts/blob/template_selectors/type_selector.js25
27 files changed, 924 insertions, 134 deletions
diff --git a/app/assets/javascripts/blob/3d_viewer/index.js b/app/assets/javascripts/blob/3d_viewer/index.js
new file mode 100644
index 00000000000..68d4ddad551
--- /dev/null
+++ b/app/assets/javascripts/blob/3d_viewer/index.js
@@ -0,0 +1,147 @@
+import * as THREE from 'three/build/three.module';
+import STLLoaderClass from 'three-stl-loader';
+import OrbitControlsClass from 'three-orbit-controls';
+import MeshObject from './mesh_object';
+
+const STLLoader = STLLoaderClass(THREE);
+const OrbitControls = OrbitControlsClass(THREE);
+
+export default class Renderer {
+ constructor(container) {
+ this.renderWrapper = this.render.bind(this);
+ this.objects = [];
+
+ this.container = container;
+ this.width = this.container.offsetWidth;
+ this.height = 500;
+
+ this.loader = new STLLoader();
+
+ this.fov = 45;
+ this.camera = new THREE.PerspectiveCamera(
+ this.fov,
+ this.width / this.height,
+ 1,
+ 1000,
+ );
+
+ this.scene = new THREE.Scene();
+
+ this.scene.add(this.camera);
+
+ // Setup the viewer
+ this.setupRenderer();
+ this.setupGrid();
+ this.setupLight();
+
+ // Setup OrbitControls
+ this.controls = new OrbitControls(
+ this.camera,
+ this.renderer.domElement,
+ );
+ this.controls.minDistance = 5;
+ this.controls.maxDistance = 30;
+ this.controls.enableKeys = false;
+
+ this.loadFile();
+ }
+
+ setupRenderer() {
+ this.renderer = new THREE.WebGLRenderer({
+ antialias: true,
+ });
+
+ this.renderer.setClearColor(0xFFFFFF);
+ this.renderer.setPixelRatio(window.devicePixelRatio);
+ this.renderer.setSize(
+ this.width,
+ this.height,
+ );
+ }
+
+ setupLight() {
+ // Point light illuminates the object
+ const pointLight = new THREE.PointLight(
+ 0xFFFFFF,
+ 2,
+ 0,
+ );
+
+ pointLight.castShadow = true;
+
+ this.camera.add(pointLight);
+
+ // Ambient light illuminates the scene
+ const ambientLight = new THREE.AmbientLight(
+ 0xFFFFFF,
+ 1,
+ );
+ this.scene.add(ambientLight);
+ }
+
+ setupGrid() {
+ this.grid = new THREE.GridHelper(
+ 20,
+ 20,
+ 0x000000,
+ 0x000000,
+ );
+
+ this.scene.add(this.grid);
+ }
+
+ loadFile() {
+ this.loader.load(this.container.dataset.endpoint, (geo) => {
+ const obj = new MeshObject(geo);
+
+ this.objects.push(obj);
+ this.scene.add(obj);
+
+ this.start();
+ this.setDefaultCameraPosition();
+ });
+ }
+
+ start() {
+ // Empty the container first
+ this.container.innerHTML = '';
+
+ // Add to DOM
+ this.container.appendChild(this.renderer.domElement);
+
+ // Make controls visible
+ this.container.parentNode.classList.remove('is-stl-loading');
+
+ this.render();
+ }
+
+ render() {
+ this.renderer.render(
+ this.scene,
+ this.camera,
+ );
+
+ requestAnimationFrame(this.renderWrapper);
+ }
+
+ changeObjectMaterials(type) {
+ this.objects.forEach((obj) => {
+ obj.changeMaterial(type);
+ });
+ }
+
+ setDefaultCameraPosition() {
+ const obj = this.objects[0];
+ const radius = (obj.geometry.boundingSphere.radius / 1.5);
+ const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2));
+
+ this.camera.position.set(
+ 0,
+ dist + 1,
+ dist,
+ );
+
+ this.camera.lookAt(this.grid);
+ this.controls.update();
+ }
+}
diff --git a/app/assets/javascripts/blob/3d_viewer/mesh_object.js b/app/assets/javascripts/blob/3d_viewer/mesh_object.js
new file mode 100644
index 00000000000..96758884abf
--- /dev/null
+++ b/app/assets/javascripts/blob/3d_viewer/mesh_object.js
@@ -0,0 +1,49 @@
+import {
+ Matrix4,
+ MeshLambertMaterial,
+ Mesh,
+} from 'three/build/three.module';
+
+const defaultColor = 0xE24329;
+const materials = {
+ default: new MeshLambertMaterial({
+ color: defaultColor,
+ }),
+ wireframe: new MeshLambertMaterial({
+ color: defaultColor,
+ wireframe: true,
+ }),
+};
+
+export default class MeshObject extends Mesh {
+ constructor(geo) {
+ super(
+ geo,
+ materials.default,
+ );
+
+ this.geometry.computeBoundingSphere();
+
+ this.rotation.set(-Math.PI / 2, 0, 0);
+
+ if (this.geometry.boundingSphere.radius > 4) {
+ const scale = 4 / this.geometry.boundingSphere.radius;
+
+ this.geometry.applyMatrix(
+ new Matrix4().makeScale(
+ scale,
+ scale,
+ scale,
+ ),
+ );
+ this.geometry.computeBoundingSphere();
+
+ this.position.x = -this.geometry.boundingSphere.center.x;
+ this.position.z = this.geometry.boundingSphere.center.y;
+ }
+ }
+
+ changeMaterial(type) {
+ this.material = materials[type];
+ }
+}
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index c9fe23aec75..4568b86f298 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -35,7 +35,7 @@ export default class BlobFileDropzone {
this.removeFile(file);
});
this.on('sending', function (file, xhr, formData) {
- formData.append('target_branch', form.find('input[name="target_branch"]').val());
+ formData.append('branch_name', form.find('input[name="branch_name"]').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
diff --git a/app/assets/javascripts/blob/blob_fork_suggestion.js b/app/assets/javascripts/blob/blob_fork_suggestion.js
new file mode 100644
index 00000000000..aa9a4e1c99a
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_fork_suggestion.js
@@ -0,0 +1,15 @@
+function BlobForkSuggestion(openButton, cancelButton, suggestionSection) {
+ if (openButton) {
+ openButton.addEventListener('click', () => {
+ suggestionSection.classList.remove('hidden');
+ });
+ }
+
+ if (cancelButton) {
+ cancelButton.addEventListener('click', () => {
+ suggestionSection.classList.add('hidden');
+ });
+ }
+}
+
+export default BlobForkSuggestion;
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
new file mode 100644
index 00000000000..3062cd51ee3
--- /dev/null
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -0,0 +1,241 @@
+/* eslint-disable class-methods-use-this */
+/* global Flash */
+
+import FileTemplateTypeSelector from './template_selectors/type_selector';
+import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
+import DockerfileSelector from './template_selectors/dockerfile_selector';
+import GitignoreSelector from './template_selectors/gitignore_selector';
+import LicenseSelector from './template_selectors/license_selector';
+
+export default class FileTemplateMediator {
+ constructor({ editor, currentAction }) {
+ this.editor = editor;
+ this.currentAction = currentAction;
+
+ this.initTemplateSelectors();
+ this.initTemplateTypeSelector();
+ this.initDomElements();
+ this.initDropdowns();
+ this.initPageEvents();
+ }
+
+ initTemplateSelectors() {
+ // Order dictates template type dropdown item order
+ this.templateSelectors = [
+ GitignoreSelector,
+ BlobCiYamlSelector,
+ DockerfileSelector,
+ LicenseSelector,
+ ].map(TemplateSelectorClass => new TemplateSelectorClass({ mediator: this }));
+ }
+
+ initTemplateTypeSelector() {
+ this.typeSelector = new FileTemplateTypeSelector({
+ mediator: this,
+ dropdownData: this.templateSelectors
+ .map((templateSelector) => {
+ const cfg = templateSelector.config;
+
+ return {
+ name: cfg.name,
+ key: cfg.key,
+ };
+ }),
+ });
+ }
+
+ initDomElements() {
+ const $templatesMenu = $('.template-selectors-menu');
+ const $undoMenu = $templatesMenu.find('.template-selectors-undo-menu');
+ const $fileEditor = $('.file-editor');
+
+ this.$templatesMenu = $templatesMenu;
+ this.$undoMenu = $undoMenu;
+ this.$undoBtn = $undoMenu.find('button');
+ this.$templateSelectors = $templatesMenu.find('.template-selector-dropdowns-wrap');
+ this.$filenameInput = $fileEditor.find('.js-file-path-name-input');
+ this.$fileContent = $fileEditor.find('#file-content');
+ this.$commitForm = $fileEditor.find('form');
+ this.$navLinks = $fileEditor.find('.nav-links');
+ }
+
+ initDropdowns() {
+ if (this.currentAction === 'create') {
+ this.typeSelector.show();
+ } else {
+ this.hideTemplateSelectorMenu();
+ }
+
+ this.displayMatchedTemplateSelector();
+ }
+
+ initPageEvents() {
+ this.listenForFilenameInput();
+ this.prepFileContentForSubmit();
+ this.listenForPreviewMode();
+ }
+
+ listenForFilenameInput() {
+ this.$filenameInput.on('keyup blur', () => {
+ this.displayMatchedTemplateSelector();
+ });
+ }
+
+ prepFileContentForSubmit() {
+ this.$commitForm.submit(() => {
+ this.$fileContent.val(this.editor.getValue());
+ });
+ }
+
+ listenForPreviewMode() {
+ this.$navLinks.on('click', 'a', (e) => {
+ const urlPieces = e.target.href.split('#');
+ const hash = urlPieces[1];
+ if (hash === 'preview') {
+ this.hideTemplateSelectorMenu();
+ } else if (hash === 'editor') {
+ this.showTemplateSelectorMenu();
+ }
+ });
+ }
+
+ selectTemplateType(item, el, e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.templateSelectors.forEach((selector) => {
+ if (selector.config.key === item.key) {
+ selector.show();
+ } else {
+ selector.hide();
+ }
+ });
+
+ this.typeSelector.setToggleText(item.name);
+
+ this.cacheToggleText();
+ }
+
+ selectTemplateFile(selector, query, data) {
+ selector.renderLoading();
+ // in case undo menu is already already there
+ this.destroyUndoMenu();
+ this.fetchFileTemplate(selector.config.endpoint, query, data)
+ .then((file) => {
+ this.showUndoMenu();
+ this.setEditorContent(file);
+ this.setFilename(selector.config.name);
+ selector.renderLoaded();
+ })
+ .catch(err => new Flash(`An error occurred while fetching the template: ${err}`));
+ }
+
+ displayMatchedTemplateSelector() {
+ const currentInput = this.getFilename();
+ this.templateSelectors.forEach((selector) => {
+ const match = selector.config.pattern.test(currentInput);
+
+ if (match) {
+ this.typeSelector.show();
+ this.selectTemplateType(selector.config);
+ this.showTemplateSelectorMenu();
+ }
+ });
+ }
+
+ fetchFileTemplate(apiCall, query, data) {
+ return new Promise((resolve) => {
+ const resolveFile = file => resolve(file);
+
+ if (!data) {
+ apiCall(query, resolveFile);
+ } else {
+ apiCall(query, data, resolveFile);
+ }
+ });
+ }
+
+ setEditorContent(file) {
+ if (!file && file !== '') return;
+
+ const newValue = file.content || file;
+
+ this.editor.setValue(newValue, 1);
+
+ this.editor.focus();
+
+ this.editor.navigateFileStart();
+ }
+
+ findTemplateSelectorByKey(key) {
+ return this.templateSelectors.find(selector => selector.config.key === key);
+ }
+
+ showUndoMenu() {
+ this.$undoMenu.removeClass('hidden');
+
+ this.$undoBtn.on('click', () => {
+ this.restoreFromCache();
+ this.destroyUndoMenu();
+ });
+ }
+
+ destroyUndoMenu() {
+ this.cacheFileContents();
+ this.cacheToggleText();
+ this.$undoMenu.addClass('hidden');
+ this.$undoBtn.off('click');
+ }
+
+ hideTemplateSelectorMenu() {
+ this.$templatesMenu.hide();
+ }
+
+ showTemplateSelectorMenu() {
+ this.$templatesMenu.show();
+ }
+
+ cacheToggleText() {
+ this.cachedToggleText = this.getTemplateSelectorToggleText();
+ }
+
+ cacheFileContents() {
+ this.cachedContent = this.editor.getValue();
+ this.cachedFilename = this.getFilename();
+ }
+
+ restoreFromCache() {
+ this.setEditorContent(this.cachedContent);
+ this.setFilename(this.cachedFilename);
+ this.setTemplateSelectorToggleText();
+ }
+
+ getTemplateSelectorToggleText() {
+ return this.$templateSelectors
+ .find('.js-template-selector-wrap:visible .dropdown-toggle-text')
+ .text();
+ }
+
+ setTemplateSelectorToggleText() {
+ return this.$templateSelectors
+ .find('.js-template-selector-wrap:visible .dropdown-toggle-text')
+ .text(this.cachedToggleText);
+ }
+
+ getTypeSelectorToggleText() {
+ return this.typeSelector.getToggleText();
+ }
+
+ getFilename() {
+ return this.$filenameInput.val();
+ }
+
+ setFilename(name) {
+ this.$filenameInput.val(name);
+ }
+
+ getSelected() {
+ return this.templateSelectors.find(selector => selector.selected);
+ }
+}
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
new file mode 100644
index 00000000000..31dd45fac89
--- /dev/null
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -0,0 +1,60 @@
+/* global Api */
+
+export default class FileTemplateSelector {
+ constructor(mediator) {
+ this.mediator = mediator;
+ this.$dropdown = null;
+ this.$wrapper = null;
+ }
+
+ init() {
+ const cfg = this.config;
+
+ this.$dropdown = $(cfg.dropdown);
+ this.$wrapper = $(cfg.wrapper);
+ this.$loadingIcon = this.$wrapper.find('.fa-chevron-down');
+ this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text');
+
+ this.initDropdown();
+ }
+
+ show() {
+ if (this.$dropdown === null) {
+ this.init();
+ }
+
+ this.$wrapper.removeClass('hidden');
+ }
+
+ hide() {
+ if (this.$dropdown !== null) {
+ this.$wrapper.addClass('hidden');
+ }
+ }
+
+ getToggleText() {
+ return this.$dropdownToggleText.text();
+ }
+
+ setToggleText(text) {
+ this.$dropdownToggleText.text(text);
+ }
+
+ renderLoading() {
+ this.$loadingIcon
+ .addClass('fa-spinner fa-spin')
+ .removeClass('fa-chevron-down');
+ }
+
+ renderLoaded() {
+ this.$loadingIcon
+ .addClass('fa-chevron-down')
+ .removeClass('fa-spinner fa-spin');
+ }
+
+ reportSelection(query, el, e, data) {
+ e.preventDefault();
+ return this.mediator.selectTemplateFile(this, query, data);
+ }
+}
+
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js
new file mode 100644
index 00000000000..9b8bfbfc8c0
--- /dev/null
+++ b/app/assets/javascripts/blob/notebook/index.js
@@ -0,0 +1,85 @@
+/* eslint-disable no-new */
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+import NotebookLab from 'vendor/notebooklab';
+
+Vue.use(VueResource);
+Vue.use(NotebookLab);
+
+export default () => {
+ const el = document.getElementById('js-notebook-viewer');
+
+ new Vue({
+ el,
+ data() {
+ return {
+ error: false,
+ loadError: false,
+ loading: true,
+ json: {},
+ };
+ },
+ template: `
+ <div class="container-fluid md prepend-top-default append-bottom-default">
+ <div
+ class="text-center loading"
+ v-if="loading && !error">
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"
+ aria-label="iPython notebook loading">
+ </i>
+ </div>
+ <notebook-lab
+ v-if="!loading && !error"
+ :notebook="json"
+ code-css-class="code white" />
+ <p
+ class="text-center"
+ v-if="error">
+ <span v-if="loadError">
+ An error occured whilst loading the file. Please try again later.
+ </span>
+ <span v-else>
+ An error occured whilst parsing the file.
+ </span>
+ </p>
+ </div>
+ `,
+ methods: {
+ loadFile() {
+ this.$http.get(el.dataset.endpoint)
+ .then((res) => {
+ this.json = res.json();
+ this.loading = false;
+ })
+ .catch((e) => {
+ if (e.status) {
+ this.loadError = true;
+ }
+
+ this.error = true;
+ });
+ },
+ },
+ mounted() {
+ if (gon.katex_css_url) {
+ const katexStyles = document.createElement('link');
+ katexStyles.setAttribute('rel', 'stylesheet');
+ katexStyles.setAttribute('href', gon.katex_css_url);
+ document.head.appendChild(katexStyles);
+ }
+
+ if (gon.katex_js_url) {
+ const katexScript = document.createElement('script');
+ katexScript.addEventListener('load', () => {
+ this.loadFile();
+ });
+ katexScript.setAttribute('src', gon.katex_js_url);
+ document.head.appendChild(katexScript);
+ } else {
+ this.loadFile();
+ }
+ },
+ });
+};
diff --git a/app/assets/javascripts/blob/notebook_viewer.js b/app/assets/javascripts/blob/notebook_viewer.js
new file mode 100644
index 00000000000..b7a0a195a92
--- /dev/null
+++ b/app/assets/javascripts/blob/notebook_viewer.js
@@ -0,0 +1,3 @@
+import renderNotebook from './notebook';
+
+document.addEventListener('DOMContentLoaded', renderNotebook);
diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js
new file mode 100644
index 00000000000..a74c2db9a61
--- /dev/null
+++ b/app/assets/javascripts/blob/pdf/index.js
@@ -0,0 +1,62 @@
+/* eslint-disable no-new */
+import Vue from 'vue';
+import PDFLab from 'vendor/pdflab';
+import workerSrc from 'vendor/pdf.worker';
+
+Vue.use(PDFLab, {
+ workerSrc,
+});
+
+export default () => {
+ const el = document.getElementById('js-pdf-viewer');
+
+ return new Vue({
+ el,
+ data() {
+ return {
+ error: false,
+ loadError: false,
+ loading: true,
+ pdf: el.dataset.endpoint,
+ };
+ },
+ methods: {
+ onLoad() {
+ this.loading = false;
+ },
+ onError(error) {
+ this.loading = false;
+ this.loadError = true;
+ this.error = error;
+ },
+ },
+ template: `
+ <div class="container-fluid md prepend-top-default append-bottom-default">
+ <div
+ class="text-center loading"
+ v-if="loading && !error">
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"
+ aria-label="PDF loading">
+ </i>
+ </div>
+ <pdf-lab
+ v-if="!loadError"
+ :pdf="pdf"
+ @pdflabload="onLoad"
+ @pdflaberror="onError" />
+ <p
+ class="text-center"
+ v-if="error">
+ <span v-if="loadError">
+ An error occured whilst loading the file. Please try again later.
+ </span>
+ <span v-else>
+ An error occured whilst decoding the file.
+ </span>
+ </p>
+ </div>
+ `,
+ });
+};
diff --git a/app/assets/javascripts/blob/pdf_viewer.js b/app/assets/javascripts/blob/pdf_viewer.js
new file mode 100644
index 00000000000..91abe9dd699
--- /dev/null
+++ b/app/assets/javascripts/blob/pdf_viewer.js
@@ -0,0 +1,3 @@
+import renderPDF from './pdf';
+
+document.addEventListener('DOMContentLoaded', renderPDF);
diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js
new file mode 100644
index 00000000000..0799991aa40
--- /dev/null
+++ b/app/assets/javascripts/blob/sketch/index.js
@@ -0,0 +1,73 @@
+import JSZip from 'jszip';
+import JSZipUtils from 'jszip-utils';
+
+export default class SketchLoader {
+ constructor(container) {
+ this.container = container;
+ this.loadingIcon = this.container.querySelector('.js-loading-icon');
+
+ this.load();
+ }
+
+ load() {
+ return this.getZipFile()
+ .then(data => JSZip.loadAsync(data))
+ .then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array'))
+ .then((content) => {
+ const url = window.URL || window.webkitURL;
+ const blob = new Blob([new Uint8Array(content)], {
+ type: 'image/png',
+ });
+ const previewUrl = url.createObjectURL(blob);
+
+ this.render(previewUrl);
+ })
+ .catch(this.error.bind(this));
+ }
+
+ getZipFile() {
+ return new JSZip.external.Promise((resolve, reject) => {
+ JSZipUtils.getBinaryContent(this.container.dataset.endpoint, (err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
+ });
+ }
+
+ render(previewUrl) {
+ const previewLink = document.createElement('a');
+ const previewImage = document.createElement('img');
+
+ previewLink.href = previewUrl;
+ previewLink.target = '_blank';
+ previewImage.src = previewUrl;
+ previewImage.className = 'img-responsive';
+
+ previewLink.appendChild(previewImage);
+ this.container.appendChild(previewLink);
+
+ this.removeLoadingIcon();
+ }
+
+ error() {
+ const errorMsg = document.createElement('p');
+
+ errorMsg.className = 'prepend-top-default append-bottom-default text-center';
+ errorMsg.textContent = `
+ Cannot show preview. For previews on sketch files, they must have the file format
+ introduced by Sketch version 43 and above.
+ `;
+ this.container.appendChild(errorMsg);
+
+ this.removeLoadingIcon();
+ }
+
+ removeLoadingIcon() {
+ if (this.loadingIcon) {
+ this.loadingIcon.remove();
+ }
+ }
+}
diff --git a/app/assets/javascripts/blob/sketch_viewer.js b/app/assets/javascripts/blob/sketch_viewer.js
new file mode 100644
index 00000000000..0640dd26855
--- /dev/null
+++ b/app/assets/javascripts/blob/sketch_viewer.js
@@ -0,0 +1,8 @@
+/* eslint-disable no-new */
+import SketchLoader from './sketch';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const el = document.getElementById('js-sketch-viewer');
+
+ new SketchLoader(el);
+});
diff --git a/app/assets/javascripts/blob/stl_viewer.js b/app/assets/javascripts/blob/stl_viewer.js
new file mode 100644
index 00000000000..f611c4fe640
--- /dev/null
+++ b/app/assets/javascripts/blob/stl_viewer.js
@@ -0,0 +1,19 @@
+import Renderer from './3d_viewer';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const viewer = new Renderer(document.getElementById('js-stl-viewer'));
+
+ [].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
+ el.addEventListener('click', (e) => {
+ const target = e.target;
+
+ e.preventDefault();
+
+ document.querySelector('.js-material-changer.active').classList.remove('active');
+ target.classList.add('active');
+ target.blur();
+
+ viewer.changeObjectMaterials(target.dataset.type);
+ });
+ });
+});
diff --git a/app/assets/javascripts/blob/template_selectors/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index d7c1c32efbd..d7c1c32efbd 100644
--- a/app/assets/javascripts/blob/template_selectors/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js
deleted file mode 100644
index 5a5954e7751..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobCiYamlSelector extends TemplateSelector {
- requestFile(query) {
- return Api.gitlabCiYml(query.name, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js
deleted file mode 100644
index 7a4d6a42a03..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* global Api */
-
-import BlobCiYamlSelector from './blob_ci_yaml_selector';
-
-export default class BlobCiYamlSelectors {
- constructor({ editor, $dropdowns }) {
- this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
- this.initSelectors(editor);
- }
-
- initSelectors(editor) {
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
- return new BlobCiYamlSelector({
- editor,
- pattern: /(.gitlab-ci.yml)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
- dropdown: $dropdown,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js
deleted file mode 100644
index 19f8820a0cb..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobDockerfileSelector extends TemplateSelector {
- requestFile(query) {
- return Api.dockerfileYml(query.name, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js
deleted file mode 100644
index da067035b43..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import BlobDockerfileSelector from './blob_dockerfile_selector';
-
-export default class BlobDockerfileSelectors {
- constructor({ editor, $dropdowns }) {
- this.editor = editor;
- this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
- this.initSelectors();
- }
-
- initSelectors() {
- const editor = this.editor;
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
- return new BlobDockerfileSelector({
- editor,
- pattern: /(Dockerfile)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
- dropdown: $dropdown,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js
deleted file mode 100644
index 0b6b02fc2b3..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobGitignoreSelector extends TemplateSelector {
- requestFile(query) {
- return Api.gitignoreText(query.name, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js
deleted file mode 100644
index dc485d97677..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import BlobGitignoreSelector from './blob_gitignore_selector';
-
-export default class BlobGitignoreSelectors {
- constructor({ editor, $dropdowns }) {
- this.$dropdowns = $dropdowns || $('.js-gitignore-selector');
- this.editor = editor;
- this.initSelectors();
- }
-
- initSelectors() {
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
-
- return new BlobGitignoreSelector({
- pattern: /(.gitignore)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
- dropdown: $dropdown,
- editor: this.editor,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selector.js b/app/assets/javascripts/blob/template_selectors/blob_license_selector.js
deleted file mode 100644
index e9cb31cc2dc..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_license_selector.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobLicenseSelector extends TemplateSelector {
- requestFile(query) {
- const data = {
- project: this.dropdown.data('project'),
- fullname: this.dropdown.data('fullname'),
- };
- return Api.licenseText(query.id, data, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js
deleted file mode 100644
index a44f4f78b2d..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* eslint-disable no-unused-vars, no-param-reassign */
-
-import BlobLicenseSelector from './blob_license_selector';
-
-export default class BlobLicenseSelectors {
- constructor({ $dropdowns, editor }) {
- this.$dropdowns = $dropdowns || $('.js-license-selector');
- this.initSelectors(editor);
- }
-
- initSelectors(editor) {
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
-
- return new BlobLicenseSelector({
- editor,
- pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-license-selector-wrap'),
- dropdown: $dropdown,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js
new file mode 100644
index 00000000000..935df07677c
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js
@@ -0,0 +1,32 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class BlobCiYamlSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'gitlab-ci-yaml',
+ name: '.gitlab-ci.yml',
+ pattern: /(.gitlab-ci.yml)/,
+ endpoint: Api.gitlabCiYml,
+ dropdown: '.js-gitlab-ci-yml-selector',
+ wrapper: '.js-gitlab-ci-yml-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ // maybe move to super class as well
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => this.reportSelection(query.name, el, e),
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
new file mode 100644
index 00000000000..b4b4d09c315
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
@@ -0,0 +1,32 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class DockerfileSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'dockerfile',
+ name: 'Dockerfile',
+ pattern: /(Dockerfile)/,
+ endpoint: Api.dockerfileYml,
+ dropdown: '.js-dockerfile-selector',
+ wrapper: '.js-dockerfile-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ // maybe move to super class as well
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => this.reportSelection(query.name, el, e),
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js
new file mode 100644
index 00000000000..aefae54ae71
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js
@@ -0,0 +1,31 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class BlobGitignoreSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'gitignore',
+ name: '.gitignore',
+ pattern: /(.gitignore)/,
+ endpoint: Api.gitignoreText,
+ dropdown: '.js-gitignore-selector',
+ wrapper: '.js-gitignore-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => this.reportSelection(query.name, el, e),
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js
new file mode 100644
index 00000000000..c8abd689ab4
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/license_selector.js
@@ -0,0 +1,38 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class BlobLicenseSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'license',
+ name: 'LICENSE',
+ pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
+ endpoint: Api.licenseText,
+ dropdown: '.js-license-selector',
+ wrapper: '.js-license-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => {
+ const data = {
+ project: this.$dropdown.data('project'),
+ fullname: this.$dropdown.data('fullname'),
+ };
+
+ this.reportSelection(query.id, el, e, data);
+ },
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/type_selector.js b/app/assets/javascripts/blob/template_selectors/type_selector.js
new file mode 100644
index 00000000000..56f23ef0568
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/type_selector.js
@@ -0,0 +1,25 @@
+import FileTemplateSelector from '../file_template_selector';
+
+export default class FileTemplateTypeSelector extends FileTemplateSelector {
+ constructor({ mediator, dropdownData }) {
+ super(mediator);
+ this.mediator = mediator;
+ this.config = {
+ dropdown: '.js-template-type-selector',
+ wrapper: '.js-template-type-selector-wrap',
+ dropdownData,
+ };
+ }
+
+ initDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.config.dropdownData,
+ filterable: false,
+ selectable: true,
+ toggleLabel: item => item.name,
+ clicked: (item, el, e) => this.mediator.selectTemplateType(item, el, e),
+ text: item => item.name,
+ });
+ }
+
+}