From 9739271c04646ca49d8937043e45a6c9f6930bbc Mon Sep 17 00:00:00 2001 From: Thomas Welton Date: Wed, 11 Jun 2014 11:06:32 +0100 Subject: [PATCH] Add drag and drop config import; closes #11004 Closes #13790 by merging a rebased version of it. --- docs/_jade/customizer-nav.jade | 2 + docs/assets/css/src/docs.css | 24 ++++++++ docs/assets/js/src/customizer.js | 100 ++++++++++++++++++++++++++----- docs/customize.html | 14 ++++- 4 files changed, 122 insertions(+), 18 deletions(-) diff --git a/docs/_jade/customizer-nav.jade b/docs/_jade/customizer-nav.jade index c4f6ddf105..eba71ef6bf 100644 --- a/docs/_jade/customizer-nav.jade +++ b/docs/_jade/customizer-nav.jade @@ -1,4 +1,6 @@ // NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-nav.jade template. +li + a(href='#import') Import li a(href='#less') Less components li diff --git a/docs/assets/css/src/docs.css b/docs/assets/css/src/docs.css index 142519bd5f..ea0739893d 100644 --- a/docs/assets/css/src/docs.css +++ b/docs/assets/css/src/docs.css @@ -1378,6 +1378,30 @@ h1[id] { box-shadow: inset 0 2px 4px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1); } +.bs-dropzone { + position: relative; + padding: 20px; + margin-bottom: 20px; + color: #777; + text-align: center; + border: 2px dashed #eee; + border-radius: 4px; +} +.bs-dropzone h2 { + margin-top: 0; + margin-bottom: 5px; +} +.bs-dropzone .lead { + margin-bottom: 10px; + font-weight: normal; + color: #333; +} +.bs-dropzone hr { + width: 100px; +} +.bs-dropzone p:last-child { + margin-bottom: 0; +} /* * Brand guidelines diff --git a/docs/assets/js/src/customizer.js b/docs/assets/js/src/customizer.js index c3a212f2c7..7340f1668f 100644 --- a/docs/assets/js/src/customizer.js +++ b/docs/assets/js/src/customizer.js @@ -16,6 +16,9 @@ window.onload = function () { // wait for load in a dumb way because B-0 ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' + ' */\n\n' + var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob) + var importDropTarget = $('#import-drop-target') + function showError(msg, err) { $('
' + '
' + @@ -46,6 +49,11 @@ window.onload = function () { // wait for load in a dumb way because B-0 } } + function showAlert(type, msg, insertAfter) { + $('
' + msg + '
') + .insertAfter(insertAfter) + } + function getQueryParam(key) { key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, '\\$&') // escape RegEx meta chars var match = location.search.match(new RegExp('[?&]' + key + '=([^&]+)(&|$)')) @@ -106,6 +114,24 @@ window.onload = function () { // wait for load in a dumb way because B-0 return data } + function updateCustomizerFromJson(data) { + if (data.js) { + $('#plugin-section input').each(function () { + $(this).prop('checked', ~$.inArray(this.value, data.js)) + }) + } + if (data.css) { + $('#less-section input').each(function () { + $(this).prop('checked', ~$.inArray(this.value, data.css)) + }) + } + if (data.vars) { + for (var i in data.vars) { + $('input[data-var="' + i + '"]').val(data.vars[i]) + } + } + } + function parseUrl() { var id = getQueryParam('id') @@ -118,21 +144,7 @@ window.onload = function () { // wait for load in a dumb way because B-0 }) .success(function (result) { var data = JSON.parse(result.files['config.json'].content) - if (data.js) { - $('#plugin-section input').each(function () { - $(this).prop('checked', ~$.inArray(this.value, data.js)) - }) - } - if (data.css) { - $('#less-section input').each(function () { - $(this).prop('checked', ~$.inArray(this.value, data.css)) - }) - } - if (data.vars) { - for (var i in data.vars) { - $('input[data-var="' + i + '"]').val(data.vars[i]) - } - } + updateCustomizerFromJson(data) }) .error(function (err) { showError('Error fetching bootstrap config file', err) @@ -324,6 +336,61 @@ window.onload = function () { // wait for load in a dumb way because B-0 } } + function removeImportAlerts() { + importDropTarget.nextAll('.alert').remove() + } + + function handleConfigFileSelect(e) { + e.stopPropagation() + e.preventDefault() + + var file = (e.originalEvent.hasOwnProperty('dataTransfer')) ? e.originalEvent.dataTransfer.files[0] : e.originalEvent.target.files[0] + + if (!file.type.match('application/json')) { + return showAlert('danger', 'Ruh roh. We can only read .json files. Please try again.', importDropTarget) + } + + var reader = new FileReader() + + reader.onload = (function () { + return function (e) { + var text = e.target.result + + try { + var json = JSON.parse(text) + + if (typeof json != 'object') { + throw new Error('JSON data from config file is not an object.') + } + + updateCustomizerFromJson(json) + showAlert('success', 'Woohoo! Your configuration was successfully uploaded. Tweak your settings, then hit Download.', importDropTarget) + } catch (err) { + return showAlert('danger', 'Shucks. We can only read valid .json files. Please try again.', importDropTarget) + } + } + })(file) + + reader.readAsText(file) + } + + function handleConfigDragOver(e) { + e.stopPropagation() + e.preventDefault() + e.originalEvent.dataTransfer.dropEffect = 'copy' + + removeImportAlerts() + } + + if (supportsFile) { + importDropTarget + .on('dragover', handleConfigDragOver) + .on('drop', handleConfigFileSelect) + } + + $('#import-file-select').on('select', handleConfigFileSelect) + $('#import-manual-trigger').on('click', removeImportAlerts) + var inputsComponent = $('#less-section input') var inputsPlugin = $('#plugin-section input') var inputsVariables = $('#less-variables-section input') @@ -410,7 +477,8 @@ window.onload = function () { // wait for load in a dumb way because B-0 { type: 'image/svg+xml;charset=utf-8' } ) var objectUrl = url.createObjectURL(svg); - if (/^blob:/.exec(objectUrl) === null) { + + if (/^blob:/.exec(objectUrl) === null || !supportsFile) { // `URL.createObjectURL` created a URL that started with something other // than "blob:", which means it has been polyfilled and is not supported by // this browser. diff --git a/docs/customize.html b/docs/customize.html index 2e067b8f48..2e918bd6ee 100644 --- a/docs/customize.html +++ b/docs/customize.html @@ -12,6 +12,7 @@ lead: Customize Bootstrap's components, Less variables, and jQuery plugins to ge + +
+
+

+

Have an existing configuration? Upload your config.json to import it.

+

Drag and drop here, or .

+
+

Don't have one? That's okay—just start customizing the fields below.

+
+
+
@@ -358,8 +370,6 @@ lead: Customize Bootstrap's components, Less variables, and jQuery plugins to ge {% include customizer-variables.html %}
- -

Download