diff --git a/.eslintrc.js b/.eslintrc.js index aa594fa7..1222f4a1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,11 @@ module.exports = { "sourceType": "module", "parser": "babel-eslint", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } + }, "extends": "standard", "installedESLint": true, "env": { @@ -13,6 +18,8 @@ module.exports = { "react" ], "rules": { + "react/jsx-uses-react": [2], + "react/jsx-uses-vars": [2], "yoda": [2, "never", { "exceptRange": true }] } } diff --git a/app/assets/images/import-example.png b/app/assets/images/import-example.png new file mode 100644 index 00000000..02d59dcd 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..29b5b896 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 75535f82..57f26c98 100644 --- a/app/assets/javascripts/src/Metamaps.Erb.js.erb +++ b/app/assets/javascripts/src/Metamaps.Erb.js.erb @@ -15,6 +15,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 98% rename from app/assets/stylesheets/application.css.erb rename to app/assets/stylesheets/application.scss.erb index 97276b9f..5961c633 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,40 @@ 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 { + box-sizing: border-box; + margin: 0.75em; + padding: 0.75em; + height: 3em; + border: 3px dashed #AAB0FB; + width: 75%; + text-align: center; + cursor: pointer; + } +} +.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; @@ -2028,17 +2055,17 @@ and it won't be important on password protected instances */ left: 0; width: 100%; height: 100%; - position: fixed; + position: absolute; z-index: 1000000; display: none; } #lightbox_main { width: 800px; - height: auto; margin: 0 auto; z-index: 2; position: relative; - top: 50%; + top: 5vh; + height: 90vh; background-color: transparent; color: black; } @@ -2077,8 +2104,11 @@ and it won't be important on password protected instances */ background-position: center center; } #lightbox_content { - width: 552px; - height: 434px; + width: 800px; + height: 500px; + max-height: 90vh; + box-sizing: border-box; + overflow-y: auto; background-color: #e0e0e0; padding: 64px 124px 64px 124px; box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19); 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..82ec71f2 100644 --- a/app/views/layouts/_lowermapelements.html.erb +++ b/app/views/layouts/_lowermapelements.html.erb @@ -8,11 +8,11 @@
<%= render :partial => 'maps/mapinfobox' %> - <% starred = current_user && @map && current_user.starred_map?(@map) - starClass = starred ? 'starred' : '' - tooltip = starred ? 'Star' : 'Unstar' %> -
<%= tooltip %>
-
Map Info
-
Help
-
+ <% starred = current_user && @map && current_user.starred_map?(@map) + starClass = starred ? 'starred' : '' + tooltip = starred ? 'Star' : 'Unstar' %> +
<%= tooltip %>
+
Map Info
+
Help
+
diff --git a/app/views/layouts/_upperelements.html.erb b/app/views/layouts/_upperelements.html.erb index e393f767..c2344643 100644 --- a/app/views/layouts/_upperelements.html.erb +++ b/app/views/layouts/_upperelements.html.erb @@ -19,6 +19,10 @@
+
+
Import data
+
+
Filter
diff --git a/frontend/src/Metamaps/GlobalUI/ImportDialog.js b/frontend/src/Metamaps/GlobalUI/ImportDialog.js new file mode 100644 index 00000000..96f9524f --- /dev/null +++ b/frontend/src/Metamaps/GlobalUI/ImportDialog.js @@ -0,0 +1,38 @@ +/* global $ */ + +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 () { + ImportDialog.openLightbox('import-dialog') + }, + hide: function () { + ImportDialog.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 f3e872ec..868843bf 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..43f04a30 100644 --- a/frontend/src/Metamaps/Map/index.js +++ b/frontend/src/Metamaps/Map/index.js @@ -40,6 +40,11 @@ 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 +57,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 +107,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 +137,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..f0425032 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 window.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..bfb60235 --- /dev/null +++ b/frontend/src/components/ImportDialogBox.js @@ -0,0 +1,76 @@ +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 + // eslint-disable-next-line no-return-assign + 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/frontend/src/components/Header.js b/frontend/src/components/Maps/Header.js similarity index 76% rename from frontend/src/components/Header.js rename to frontend/src/components/Maps/Header.js index ee4184d5..d323c0d5 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Maps/Header.js @@ -21,15 +21,15 @@ class Header extends Component { const { signedIn, section } = this.props const activeClass = (title) => { - let forClass = "exploreMapsButton" - forClass += " " + title + "Maps" - if (title == "my" && section == "mine" || - title == section) forClass += " active" + let forClass = 'exploreMapsButton' + forClass += ' ' + title + 'Maps' + if (title === 'my' && section === 'mine' || + title === section) forClass += ' active' return forClass } - const explore = section == "mine" || section == "active" || section == "starred" || section == "shared" || section == "featured" - const mapper = section == "mapper" + const explore = section === 'mine' || section === 'active' || section === 'starred' || section === 'shared' || section === 'featured' + const mapper = section === 'mapper' return (
@@ -38,31 +38,31 @@ class Header extends Component {
diff --git a/frontend/src/components/MapCard.js b/frontend/src/components/Maps/MapCard.js similarity index 93% rename from frontend/src/components/MapCard.js rename to frontend/src/components/Maps/MapCard.js index bf1416fc..e31ede18 100644 --- a/frontend/src/components/MapCard.js +++ b/frontend/src/components/Maps/MapCard.js @@ -3,7 +3,7 @@ import React, { Component, PropTypes } from 'react' class MapCard extends Component { render = () => { const { map, currentUser } = this.props - + function capitalize (string) { return string.charAt(0).toUpperCase() + string.slice(1) } @@ -16,12 +16,12 @@ class MapCard extends Component { const truncatedName = n ? (n.length > maxNameLength ? n.substring(0, maxNameLength) + '...' : n) : '' const truncatedDesc = d ? (d.length > maxDescLength ? d.substring(0, maxDescLength) + '...' : d) : '' const editPermission = map.authorizeToEdit(currentUser) ? 'canEdit' : 'cannotEdit' - + return ( +
diff --git a/frontend/src/components/MapListItem.js b/frontend/src/components/Maps/MapListItem.js similarity index 100% rename from frontend/src/components/MapListItem.js rename to frontend/src/components/Maps/MapListItem.js diff --git a/frontend/src/components/MapperCard.js b/frontend/src/components/Maps/MapperCard.js similarity index 68% rename from frontend/src/components/MapperCard.js rename to frontend/src/components/Maps/MapperCard.js index e2f4cb33..dbb06cbc 100644 --- a/frontend/src/components/MapperCard.js +++ b/frontend/src/components/Maps/MapperCard.js @@ -3,14 +3,14 @@ import React, { Component, PropTypes } from 'react' class MapperCard extends Component { render = () => { const { user } = this.props - + return (
-
+
-
-
+
+
{ user.name }
@@ -19,10 +19,10 @@ class MapperCard extends Component {
{ user.numMaps }
maps
-
{ user.numTopics }
topics
-
{ user.numSynapses }
synapses
-
-
+
{ user.numTopics }
topics
+
{ user.numSynapses }
synapses
+
+
) diff --git a/frontend/src/components/Maps.js b/frontend/src/components/Maps/index.js similarity index 76% rename from frontend/src/components/Maps.js rename to frontend/src/components/Maps/index.js index 7931da5d..22a41d3e 100644 --- a/frontend/src/components/Maps.js +++ b/frontend/src/components/Maps/index.js @@ -1,20 +1,19 @@ import React, { Component, PropTypes } from 'react' -import Header from './Header.js' -import MapperCard from './MapperCard.js' -import MapCard from './MapCard.js' -import MapListItem from './MapListItem.js' +import Header from './Header' +import MapperCard from './MapperCard' +import MapCard from './MapCard' +import MapListItem from './MapListItem' class Maps extends Component { render = () => { const { maps, currentUser, section, displayStyle, user, moreToLoad, loadMore } = this.props let mapElements - if (displayStyle == 'grid') { + if (displayStyle === 'grid') { mapElements = maps.models.map(function (map) { return }) - } - else if (displayStyle == 'list') { + } else if (displayStyle === 'list') { mapElements = maps.models.map(function (map) { return }) @@ -28,9 +27,10 @@ class Maps extends Component { { currentUser && !user ? : null } { mapElements }
- { moreToLoad ? - [,
] - : null } + {!moreToLoad ? null : [ + , +
+ ]}