Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js106
-rw-r--r--app/assets/javascripts/blob/balsamiq_viewer.js6
-rw-r--r--app/assets/javascripts/spinner.js29
-rw-r--r--app/assets/stylesheets/framework/files.scss11
-rw-r--r--app/models/blob.rb6
-rw-r--r--app/views/projects/blob/_bmpr.html.haml4
6 files changed, 162 insertions, 0 deletions
diff --git a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js
new file mode 100644
index 00000000000..5bcd7d5eccc
--- /dev/null
+++ b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js
@@ -0,0 +1,106 @@
+/* global Flash */
+
+import sqljs from 'sql.js';
+import { template as _template } from 'underscore';
+import Spinner from '../../spinner';
+
+class BalsamiqViewer {
+ constructor(viewer) {
+ this.viewer = viewer;
+ this.endpoint = this.viewer.dataset.endpoint;
+ this.spinner = new Spinner(this.viewer);
+ }
+
+ loadFile() {
+ const xhr = new XMLHttpRequest();
+
+ xhr.open('GET', this.endpoint, true);
+ xhr.responseType = 'arraybuffer';
+
+ xhr.onload = this.renderFile.bind(this);
+ xhr.onerror = BalsamiqViewer.onError;
+
+ this.spinner.start();
+
+ xhr.send();
+ }
+
+ renderFile(loadEvent) {
+ this.spinner.stop();
+
+ const container = document.createElement('ul');
+
+ this.initDatabase(loadEvent.target.response);
+
+ const previews = this.getPreviews();
+ const renderedPreviews = previews.map(preview => this.renderPreview(preview));
+
+ container.innerHTML = renderedPreviews.join('');
+ container.classList.add('list-inline', 'previews');
+
+ this.viewer.appendChild(container);
+ }
+
+ initDatabase(data) {
+ const previewBinary = new Uint8Array(data);
+
+ this.database = new sqljs.Database(previewBinary);
+ }
+
+ getPreviews() {
+ const thumbnails = this.database.exec('SELECT * FROM thumbnails');
+
+ return thumbnails[0].values.map(BalsamiqViewer.parsePreview);
+ }
+
+ getTitle(resourceID) {
+ return this.database.exec(`SELECT * FROM resources WHERE id = '${resourceID}'`);
+ }
+
+ renderPreview(preview) {
+ const previewElement = document.createElement('li');
+
+ previewElement.classList.add('preview');
+ previewElement.innerHTML = this.renderTemplate(preview);
+
+ return previewElement.outerHTML;
+ }
+
+ renderTemplate(preview) {
+ const title = this.getTitle(preview.resourceID);
+ const name = BalsamiqViewer.parseTitle(title);
+ const image = preview.image;
+
+ const template = BalsamiqViewer.PREVIEW_TEMPLATE({
+ name,
+ image,
+ });
+
+ return template;
+ }
+
+ static parsePreview(preview) {
+ return JSON.parse(preview[1]);
+ }
+
+ static parseTitle(title) {
+ return JSON.parse(title[0].values[0][2]).name;
+ }
+
+ static onError() {
+ const flash = new Flash('Balsamiq file could not be loaded.');
+
+ return flash;
+ }
+}
+
+BalsamiqViewer.PREVIEW_TEMPLATE = _template(`
+ <div class="panel panel-default">
+ <div class="panel-heading"><%- name %></div>
+ <div class="panel-body">
+ <img class="img-thumbnail" src="data:image/png;base64,<%- image %>"/>
+ </div>
+ </div>
+`);
+
+export default BalsamiqViewer;
diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js
new file mode 100644
index 00000000000..1dacf84470f
--- /dev/null
+++ b/app/assets/javascripts/blob/balsamiq_viewer.js
@@ -0,0 +1,6 @@
+import BalsamiqViewer from './balsamiq/balsamiq_viewer';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const balsamiqViewer = new BalsamiqViewer(document.getElementById('js-balsamiq-viewer'));
+ balsamiqViewer.loadFile();
+});
diff --git a/app/assets/javascripts/spinner.js b/app/assets/javascripts/spinner.js
new file mode 100644
index 00000000000..6b5ac89a576
--- /dev/null
+++ b/app/assets/javascripts/spinner.js
@@ -0,0 +1,29 @@
+class Spinner {
+ constructor(renderable) {
+ this.renderable = renderable;
+
+ this.container = Spinner.createContainer();
+ }
+
+ start() {
+ this.renderable.innerHTML = '';
+ this.renderable.appendChild(this.container);
+ }
+
+ stop() {
+ this.container.remove();
+ }
+
+ static createContainer() {
+ const container = document.createElement('div');
+ container.classList.add('loading');
+
+ container.innerHTML = Spinner.TEMPLATE;
+
+ return container;
+ }
+}
+
+Spinner.TEMPLATE = '<i class="fa fa-spinner fa-spin"></i>';
+
+export default Spinner;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index df819ffe4bc..099187a5193 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -160,6 +160,17 @@
&.code {
padding: 0;
}
+
+ .list-inline.previews {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ padding: $gl-padding;
+
+ .preview {
+ flex-shrink: 0;
+ }
+ }
}
}
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 55872acef51..82333b6f369 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -62,6 +62,10 @@ class Blob < SimpleDelegator
binary? && extension == 'sketch'
end
+ def balsamiq?
+ binary? && extension == 'bmpr'
+ end
+
def stl?
extension == 'stl'
end
@@ -97,6 +101,8 @@ class Blob < SimpleDelegator
'sketch'
elsif stl?
'stl'
+ elsif balsamiq?
+ 'bmpr'
elsif markup?
if only_display_raw?
'too_large'
diff --git a/app/views/projects/blob/_bmpr.html.haml b/app/views/projects/blob/_bmpr.html.haml
new file mode 100644
index 00000000000..573b24ae44f
--- /dev/null
+++ b/app/views/projects/blob/_bmpr.html.haml
@@ -0,0 +1,4 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('balsamiq_viewer')
+
+.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: namespace_project_raw_path(@project.namespace, @project, @id) } }