diff --git a/app/assets/images/import-example.png b/app/assets/images/import-example.png new file mode 100644 index 00000000..3f013d58 Binary files /dev/null and b/app/assets/images/import-example.png differ diff --git a/app/assets/images/import.png b/app/assets/images/import.png new file mode 100644 index 00000000..5c66e984 Binary files /dev/null and b/app/assets/images/import.png differ diff --git a/app/assets/javascripts/src/Metamaps.Erb.js.erb b/app/assets/javascripts/src/Metamaps.Erb.js.erb index e8f3a25b..916d175a 100644 --- a/app/assets/javascripts/src/Metamaps.Erb.js.erb +++ b/app/assets/javascripts/src/Metamaps.Erb.js.erb @@ -14,6 +14,7 @@ Metamaps.Erb['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>' Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>' Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>' Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>' +Metamaps.Erb['import-example.png'] = '<%= asset_path('import-example.png') %>' Metamaps.Erb['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>' Metamaps.Erb['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>' Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %> diff --git a/app/assets/stylesheets/application.css.erb b/app/assets/stylesheets/application.scss.erb similarity index 99% rename from app/assets/stylesheets/application.css.erb rename to app/assets/stylesheets/application.scss.erb index 97276b9f..e4e7762e 100644 --- a/app/assets/stylesheets/application.css.erb +++ b/app/assets/stylesheets/application.scss.erb @@ -1526,9 +1526,8 @@ h3.filterBox { background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); } /* map info box */ -/* map info box */ -.wrapper div.mapInfoBox { +.wrapper .mapInfoBox { display: none; position: absolute; bottom: 40px; @@ -1536,12 +1535,34 @@ h3.filterBox { background-color: #424242; color: #F5F5F5; border-radius: 2px; + box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0px 3px 3px rgba(0,0,0,0.16); + text-align: center; + font-style: normal; +} +.import-dialog{ + button { + margin: 1em 0.5em; + } + .import-blue-button { + display: inline-block; + box-sizing: border-box; + margin: 0.75em; + padding: 0.75em; + height: 3em; + background-color: #AAB0FB; + border-radius: 0.3em; + color: white; + cursor: pointer; + } + .fileupload { + width: 75%; + text-align: center; + } +} +.wrapper .mapInfoBox { width: 360px; min-height: 300px; padding: 0; - font-style: normal; - text-align: center; - box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0px 3px 3px rgba(0,0,0,0.16); } .requestTitle { display: none; diff --git a/app/assets/stylesheets/clean.css.erb b/app/assets/stylesheets/clean.css.erb index b41f14ca..deb2719f 100644 --- a/app/assets/stylesheets/clean.css.erb +++ b/app/assets/stylesheets/clean.css.erb @@ -188,7 +188,7 @@ .upperRightIcon { width: 32px; height: 32px; - background-image: url(<%= asset_data_uri('topright_sprite.png') %>); + background-image: url(<%= asset_path('topright_sprite.png') %>); background-repeat: no-repeat; cursor: pointer; } @@ -325,7 +325,7 @@ } .fullWidthWrapper.withPartners { - background: url(<%= asset_data_uri('homepage_bg_fade.png') %>) no-repeat center -300px; + background: url(<%= asset_path('homepage_bg_fade.png') %>) no-repeat center -300px; } .homeWrapper.homePartners { padding: 64px 0 280px; @@ -364,7 +364,7 @@ cursor: pointer; } .openCheatsheet { - background-image: url(<%= asset_data_uri('help_sprite.png') %>); + background-image: url(<%= asset_path('help_sprite.png') %>); background-repeat:no-repeat; } .openCheatsheet:hover { @@ -373,7 +373,7 @@ .mapInfoIcon { position: relative; top: 56px; /* puts it just offscreen */ - background-image: url(<%= asset_data_uri('mapinfo_sprite.png') %>); + background-image: url(<%= asset_path('mapinfo_sprite.png') %>); background-repeat:no-repeat; } .mapInfoIcon:hover { @@ -382,8 +382,14 @@ .mapPage .mapInfoIcon { top: 0; } +.importDialog { + background-image: url(<%= asset_path('import.png') %>); + background-position: 0 0; + background-repeat: no-repeat; + width: 32px; +} .starMap { - background-image: url(<%= asset_data_uri('starmap_sprite.png') %>); + background-image: url(<%= asset_path('starmap_sprite.png') %>); background-position: 0 0; background-repeat: no-repeat; width: 32px; @@ -437,7 +443,7 @@ .takeScreenshot { margin-bottom: 5px; border-radius: 2px; - background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>); + background-image: url(<%= asset_path 'screenshot_sprite.png' %>); display: none; } .takeScreenshot:hover { @@ -450,7 +456,7 @@ .zoomExtents { margin-bottom:5px; border-radius: 2px; - background-image: url(<%= asset_data_uri('extents_sprite.png') %>); + background-image: url(<%= asset_path('extents_sprite.png') %>); } .zoomExtents:hover { @@ -458,7 +464,7 @@ } .zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder, - .mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin { + .mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, importDialog:hover .tooltipsAbove, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin { display: block; } @@ -623,7 +629,7 @@ } .zoomIn { - background-image: url(<%= asset_data_uri('zoom_sprite.png') %>); + background-image: url(<%= asset_path('zoom_sprite.png') %>); background-position: 0 /…0; border-top-left-radius: 2px; border-top-right-radius: 2px; @@ -632,7 +638,7 @@ background-position: -32px 0; } .zoomOut { - background-image: url(<%= asset_data_uri('zoom_sprite.png') %>); + background-image: url(<%= asset_path('zoom_sprite.png') %>); background-position:0 -32px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; @@ -740,23 +746,23 @@ left:5px; } .exploreMapsCenter .myMaps .exploreMapsIcon { - background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); + background-image: url(<%= asset_path 'exploremaps_sprite.png' %>); background-position: -32px 0; } .exploreMapsCenter .sharedMaps .exploreMapsIcon { - background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); + background-image: url(<%= asset_path 'exploremaps_sprite.png' %>); background-position: -128px 0; } .exploreMapsCenter .activeMaps .exploreMapsIcon { - background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); + background-image: url(<%= asset_path 'exploremaps_sprite.png' %>); background-position: 0 0; } .exploreMapsCenter .featuredMaps .exploreMapsIcon { - background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); + background-image: url(<%= asset_path 'exploremaps_sprite.png' %>); background-position: -96px 0; } .exploreMapsCenter .starredMaps .exploreMapsIcon { - background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); + background-image: url(<%= asset_path 'exploremaps_sprite.png' %>); background-position: -96px 0; } .myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon { diff --git a/app/assets/stylesheets/mobile.scss.erb b/app/assets/stylesheets/mobile.scss.erb index cf416e37..a646fea4 100644 --- a/app/assets/stylesheets/mobile.scss.erb +++ b/app/assets/stylesheets/mobile.scss.erb @@ -56,7 +56,7 @@ width: 100%; } - .wrapper div.mapInfoBox { + .wrapper .mapInfoBox { position: fixed; top: 50px; right: 0px; diff --git a/app/views/layouts/_lowermapelements.html.erb b/app/views/layouts/_lowermapelements.html.erb index fe120219..b8b7f868 100644 --- a/app/views/layouts/_lowermapelements.html.erb +++ b/app/views/layouts/_lowermapelements.html.erb @@ -8,6 +8,7 @@
<%= render :partial => 'maps/mapinfobox' %> +
Import data
<% starred = current_user && @map && current_user.starred_map?(@map) starClass = starred ? 'starred' : '' tooltip = starred ? 'Star' : 'Unstar' %> diff --git a/frontend/src/Metamaps/GlobalUI/ImportDialog.js b/frontend/src/Metamaps/GlobalUI/ImportDialog.js new file mode 100644 index 00000000..3671cd90 --- /dev/null +++ b/frontend/src/Metamaps/GlobalUI/ImportDialog.js @@ -0,0 +1,36 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import outdent from 'outdent' + +import ImportDialogBox from '../../components/ImportDialogBox' + +import PasteInput from '../PasteInput' + +const ImportDialog = { + openLightbox: null, + closeLightbox: null, + + init: function(serverData, openLightbox, closeLightbox) { + const self = ImportDialog + self.openLightbox = openLightbox + self.closeLightbox = closeLightbox + + $('#lightbox_content').append($(outdent` +
+
+
+ `)) + ReactDOM.render(React.createElement(ImportDialogBox, { + onFileAdded: PasteInput.handleFile, + exampleImageUrl: serverData['import-example.png'], + }), $('.importDialogWrapper').get(0)) + }, + show: function() { + self.openLightbox('import-dialog') + }, + hide: function() { + self.closeLightbox('import-dialog') + } +} + +export default ImportDialog diff --git a/frontend/src/Metamaps/GlobalUI/index.js b/frontend/src/Metamaps/GlobalUI/index.js index 3b16375e..4eb29bb7 100644 --- a/frontend/src/Metamaps/GlobalUI/index.js +++ b/frontend/src/Metamaps/GlobalUI/index.js @@ -6,6 +6,7 @@ import Create from '../Create' import Search from './Search' import CreateMap from './CreateMap' import Account from './Account' +import ImportDialog from './ImportDialog' /* * Metamaps.Backbone @@ -21,6 +22,7 @@ const GlobalUI = { self.Search.init() self.CreateMap.init() self.Account.init() + self.ImportDialog.init(Metamaps.Erb, self.openLightbox, self.closeLightbox) if ($('#toast').html().trim()) self.notifyUser($('#toast').html()) @@ -141,5 +143,5 @@ const GlobalUI = { } } -export { Search, CreateMap, Account } +export { Search, CreateMap, Account, ImportDialog } export default GlobalUI diff --git a/frontend/src/Metamaps/Import.js b/frontend/src/Metamaps/Import.js index f70a1290..05d762e5 100644 --- a/frontend/src/Metamaps/Import.js +++ b/frontend/src/Metamaps/Import.js @@ -411,6 +411,7 @@ const Import = { newKey = newKey.replace(/\s/g, '') // remove whitespace if (newKey === 'url') newKey = 'link' if (newKey === 'title') newKey = 'name' + if (newKey === 'label') newKey = 'desc' if (newKey === 'description') newKey = 'desc' if (newKey === 'direction') newKey = 'category' return newKey diff --git a/frontend/src/Metamaps/Map/index.js b/frontend/src/Metamaps/Map/index.js index 4c2f78bb..7d7322fc 100644 --- a/frontend/src/Metamaps/Map/index.js +++ b/frontend/src/Metamaps/Map/index.js @@ -1,6 +1,8 @@ /* global Metamaps, $ */ import outdent from 'outdent' +import React from 'react' +import ReactDOM from 'react-dom' import Active from '../Active' import AutoLayout from '../AutoLayout' @@ -40,6 +42,12 @@ const Map = { init: function () { var self = Map + // prevent right clicks on the main canvas, so as to not get in the way of our right clicks + $('#wrapper').on('contextmenu', function (e) { + return false + }) + + $('.starMap').click(function () { if ($(this).is('.starred')) self.unstar() else self.star() @@ -52,7 +60,7 @@ const Map = { GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html() self.updateStar() - self.InfoBox.init() + InfoBox.init() CheatSheet.init() $(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper) @@ -102,7 +110,7 @@ const Map = { Selected.reset() // set the proper mapinfobox content - Map.InfoBox.load() + InfoBox.load() // these three update the actual filter box with the right list items Filter.checkMetacodes() @@ -132,7 +140,7 @@ const Map = { Create.newTopic.hide(true) // true means force (and override pinned) Create.newSynapse.hide() Filter.close() - Map.InfoBox.close() + InfoBox.close() Realtime.endActiveMap() } }, diff --git a/frontend/src/Metamaps/PasteInput.js b/frontend/src/Metamaps/PasteInput.js index 6f1cc03f..51d4a933 100644 --- a/frontend/src/Metamaps/PasteInput.js +++ b/frontend/src/Metamaps/PasteInput.js @@ -21,16 +21,7 @@ const PasteInput = { e.preventDefault(); var coords = Util.pixelsToCoords({ x: e.clientX, y: e.clientY }) if (e.dataTransfer.files.length > 0) { - var fileReader = new window.FileReader() - fileReader.readAsText(e.dataTransfer.files[0]) - fileReader.onload = function(e) { - var text = e.currentTarget.result - if (text.substring(0,5) === '(.*)<\/string>[\s\S]*/m, '$1') - } - self.handle(text, coords) - } + self.handleFile(e.dataTransfer.files[0], coords) } // OMG import bookmarks 😍 if (e.dataTransfer.items.length > 0) { @@ -52,7 +43,21 @@ const PasteInput = { }) }, - handle: function(text, coords) { + handleFile: (file, coords = null) => { + var self = PasteInput + var fileReader = new FileReader() + fileReader.readAsText(file) + fileReader.onload = function(e) { + var text = e.currentTarget.result + if (text.substring(0,5) === '(.*)<\/string>[\s\S]*/m, '$1') + } + self.handle(text, coords) + } + }, + + handle: function(text, coords = null) { var self = PasteInput if (text.match(self.URL_REGEX)) { diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js index d179713e..44bbfdb6 100644 --- a/frontend/src/Metamaps/index.js +++ b/frontend/src/Metamaps/index.js @@ -10,7 +10,7 @@ import Create from './Create' import Debug from './Debug' import Filter from './Filter' import GlobalUI, { - Search, CreateMap, Account as GlobalUI_Account + Search, CreateMap, ImportDialog, Account as GlobalUI_Account } from './GlobalUI' import Import from './Import' import JIT from './JIT' @@ -47,6 +47,7 @@ Metamaps.GlobalUI = GlobalUI Metamaps.GlobalUI.Search = Search Metamaps.GlobalUI.CreateMap = CreateMap Metamaps.GlobalUI.Account = GlobalUI_Account +Metamaps.GlobalUI.ImportDialog = ImportDialog Metamaps.Import = Import Metamaps.JIT = JIT Metamaps.Listeners = Listeners diff --git a/frontend/src/components/ImportDialogBox.js b/frontend/src/components/ImportDialogBox.js new file mode 100644 index 00000000..4d113ccc --- /dev/null +++ b/frontend/src/components/ImportDialogBox.js @@ -0,0 +1,75 @@ +import React, { PropTypes, Component } from 'react' +import Dropzone from 'react-dropzone' + +class ImportDialogBox extends Component { + constructor(props) { + super(props) + + this.state = { + showImportInstructions: false + } + } + + handleExport = format => () => { + window.open(`${window.location.pathname}/export.${format}`, '_blank') + } + + handleFile = (files, e) => { + // for some reason it uploads twice, so we need this debouncer + this.debouncer = this.debouncer || window.setTimeout(() => this.debouncer = null, 10) + if (!this.debouncer) { + this.props.onFileAdded(files[0]) + } + } + + toggleShowInstructions = e => { + this.setState({ + showImportInstructions: !this.state.showImportInstructions + }) + } + + render = () => { + return ( +
+

EXPORT

+
+ Export as CSV +
+
+ Export as JSON +
+

IMPORT

+

To upload a file, drop it here:

+ + Drop files here! + +

+ + Show/hide import instructions + +

+ {!this.state.showImportInstructions ? null : (
+

+ You can import topics and synapses by uploading a spreadsheet here. + The file should be in comma-separated format (when you save, change the + filetype from .xls to .csv). +

+ +

You can choose which columns to include in your data. Topics must have a name field. Synapses must have Topic 1 and Topic 2.

+

 

+

* There are many valid import formats. Try exporting a map to see what columns you can include in your import data. You can also copy-paste from Excel to import, or import JSON.

+

* If you are importing a list of links, you can use a Link column in place of the Name column.

+
)} +
+ ) + } +} + +ImportDialogBox.propTypes = { + onFileAdded: PropTypes.func, + exampleImageUrl: PropTypes.string +} + +export default ImportDialogBox diff --git a/package.json b/package.json index c882fdd0..82cd120d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "outdent": "0.2.1", "react": "15.3.2", "react-dom": "15.3.2", + "react-dropzone": "3.6.0", "socket.io": "0.9.12", "webpack": "1.13.2" },