pop up a lightbox using React to help you export

This commit is contained in:
Devin Howard 2016-09-25 00:27:04 +08:00
parent 33bcfc1505
commit 518773d6e1
15 changed files with 195 additions and 37 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

View file

@ -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_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>' Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.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.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>'
Metamaps.Erb['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>' 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 %> Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>

View file

@ -1526,9 +1526,8 @@ h3.filterBox {
background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>);
} }
/* map info box */ /* map info box */
/* map info box */
.wrapper div.mapInfoBox { .wrapper .mapInfoBox {
display: none; display: none;
position: absolute; position: absolute;
bottom: 40px; bottom: 40px;
@ -1536,12 +1535,34 @@ h3.filterBox {
background-color: #424242; background-color: #424242;
color: #F5F5F5; color: #F5F5F5;
border-radius: 2px; 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; width: 360px;
min-height: 300px; min-height: 300px;
padding: 0; 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 { .requestTitle {
display: none; display: none;

View file

@ -188,7 +188,7 @@
.upperRightIcon { .upperRightIcon {
width: 32px; width: 32px;
height: 32px; height: 32px;
background-image: url(<%= asset_data_uri('topright_sprite.png') %>); background-image: url(<%= asset_path('topright_sprite.png') %>);
background-repeat: no-repeat; background-repeat: no-repeat;
cursor: pointer; cursor: pointer;
} }
@ -325,7 +325,7 @@
} }
.fullWidthWrapper.withPartners { .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 { .homeWrapper.homePartners {
padding: 64px 0 280px; padding: 64px 0 280px;
@ -364,7 +364,7 @@
cursor: pointer; cursor: pointer;
} }
.openCheatsheet { .openCheatsheet {
background-image: url(<%= asset_data_uri('help_sprite.png') %>); background-image: url(<%= asset_path('help_sprite.png') %>);
background-repeat:no-repeat; background-repeat:no-repeat;
} }
.openCheatsheet:hover { .openCheatsheet:hover {
@ -373,7 +373,7 @@
.mapInfoIcon { .mapInfoIcon {
position: relative; position: relative;
top: 56px; /* puts it just offscreen */ 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; background-repeat:no-repeat;
} }
.mapInfoIcon:hover { .mapInfoIcon:hover {
@ -382,8 +382,14 @@
.mapPage .mapInfoIcon { .mapPage .mapInfoIcon {
top: 0; top: 0;
} }
.importDialog {
background-image: url(<%= asset_path('import.png') %>);
background-position: 0 0;
background-repeat: no-repeat;
width: 32px;
}
.starMap { .starMap {
background-image: url(<%= asset_data_uri('starmap_sprite.png') %>); background-image: url(<%= asset_path('starmap_sprite.png') %>);
background-position: 0 0; background-position: 0 0;
background-repeat: no-repeat; background-repeat: no-repeat;
width: 32px; width: 32px;
@ -437,7 +443,7 @@
.takeScreenshot { .takeScreenshot {
margin-bottom: 5px; margin-bottom: 5px;
border-radius: 2px; border-radius: 2px;
background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>); background-image: url(<%= asset_path 'screenshot_sprite.png' %>);
display: none; display: none;
} }
.takeScreenshot:hover { .takeScreenshot:hover {
@ -450,7 +456,7 @@
.zoomExtents { .zoomExtents {
margin-bottom:5px; margin-bottom:5px;
border-radius: 2px; border-radius: 2px;
background-image: url(<%= asset_data_uri('extents_sprite.png') %>); background-image: url(<%= asset_path('extents_sprite.png') %>);
} }
.zoomExtents:hover { .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, .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; display: block;
} }
@ -623,7 +629,7 @@
} }
.zoomIn { .zoomIn {
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>); background-image: url(<%= asset_path('zoom_sprite.png') %>);
background-position: 0 /…0; background-position: 0 /…0;
border-top-left-radius: 2px; border-top-left-radius: 2px;
border-top-right-radius: 2px; border-top-right-radius: 2px;
@ -632,7 +638,7 @@
background-position: -32px 0; background-position: -32px 0;
} }
.zoomOut { .zoomOut {
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>); background-image: url(<%= asset_path('zoom_sprite.png') %>);
background-position:0 -32px; background-position:0 -32px;
border-bottom-left-radius: 2px; border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px; border-bottom-right-radius: 2px;
@ -740,23 +746,23 @@
left:5px; left:5px;
} }
.exploreMapsCenter .myMaps .exploreMapsIcon { .exploreMapsCenter .myMaps .exploreMapsIcon {
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
background-position: -32px 0; background-position: -32px 0;
} }
.exploreMapsCenter .sharedMaps .exploreMapsIcon { .exploreMapsCenter .sharedMaps .exploreMapsIcon {
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
background-position: -128px 0; background-position: -128px 0;
} }
.exploreMapsCenter .activeMaps .exploreMapsIcon { .exploreMapsCenter .activeMaps .exploreMapsIcon {
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
background-position: 0 0; background-position: 0 0;
} }
.exploreMapsCenter .featuredMaps .exploreMapsIcon { .exploreMapsCenter .featuredMaps .exploreMapsIcon {
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
background-position: -96px 0; background-position: -96px 0;
} }
.exploreMapsCenter .starredMaps .exploreMapsIcon { .exploreMapsCenter .starredMaps .exploreMapsIcon {
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
background-position: -96px 0; background-position: -96px 0;
} }
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon { .myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {

View file

@ -56,7 +56,7 @@
width: 100%; width: 100%;
} }
.wrapper div.mapInfoBox { .wrapper .mapInfoBox {
position: fixed; position: fixed;
top: 50px; top: 50px;
right: 0px; right: 0px;

View file

@ -8,6 +8,7 @@
<div class="infoAndHelp"> <div class="infoAndHelp">
<%= render :partial => 'maps/mapinfobox' %> <%= render :partial => 'maps/mapinfobox' %>
<div class="importDialog infoElement mapElement openLightbox" data-open="import-dialog-lightbox"><div class="tooltipsAbove">Import data</div></div>
<% starred = current_user && @map && current_user.starred_map?(@map) <% starred = current_user && @map && current_user.starred_map?(@map)
starClass = starred ? 'starred' : '' starClass = starred ? 'starred' : ''
tooltip = starred ? 'Star' : 'Unstar' %> tooltip = starred ? 'Star' : 'Unstar' %>

View file

@ -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`
<div class="lightboxContent" id="import-dialog-lightbox">
<div class="importDialogWrapper" />
</div>
`))
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

View file

@ -6,6 +6,7 @@ import Create from '../Create'
import Search from './Search' import Search from './Search'
import CreateMap from './CreateMap' import CreateMap from './CreateMap'
import Account from './Account' import Account from './Account'
import ImportDialog from './ImportDialog'
/* /*
* Metamaps.Backbone * Metamaps.Backbone
@ -21,6 +22,7 @@ const GlobalUI = {
self.Search.init() self.Search.init()
self.CreateMap.init() self.CreateMap.init()
self.Account.init() self.Account.init()
self.ImportDialog.init(Metamaps.Erb, self.openLightbox, self.closeLightbox)
if ($('#toast').html().trim()) self.notifyUser($('#toast').html()) 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 export default GlobalUI

View file

@ -411,6 +411,7 @@ const Import = {
newKey = newKey.replace(/\s/g, '') // remove whitespace newKey = newKey.replace(/\s/g, '') // remove whitespace
if (newKey === 'url') newKey = 'link' if (newKey === 'url') newKey = 'link'
if (newKey === 'title') newKey = 'name' if (newKey === 'title') newKey = 'name'
if (newKey === 'label') newKey = 'desc'
if (newKey === 'description') newKey = 'desc' if (newKey === 'description') newKey = 'desc'
if (newKey === 'direction') newKey = 'category' if (newKey === 'direction') newKey = 'category'
return newKey return newKey

View file

@ -1,6 +1,8 @@
/* global Metamaps, $ */ /* global Metamaps, $ */
import outdent from 'outdent' import outdent from 'outdent'
import React from 'react'
import ReactDOM from 'react-dom'
import Active from '../Active' import Active from '../Active'
import AutoLayout from '../AutoLayout' import AutoLayout from '../AutoLayout'
@ -40,6 +42,12 @@ const Map = {
init: function () { init: function () {
var self = Map 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 () { $('.starMap').click(function () {
if ($(this).is('.starred')) self.unstar() if ($(this).is('.starred')) self.unstar()
else self.star() else self.star()
@ -52,7 +60,7 @@ const Map = {
GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html() GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
self.updateStar() self.updateStar()
self.InfoBox.init() InfoBox.init()
CheatSheet.init() CheatSheet.init()
$(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper) $(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper)
@ -102,7 +110,7 @@ const Map = {
Selected.reset() Selected.reset()
// set the proper mapinfobox content // set the proper mapinfobox content
Map.InfoBox.load() InfoBox.load()
// these three update the actual filter box with the right list items // these three update the actual filter box with the right list items
Filter.checkMetacodes() Filter.checkMetacodes()
@ -132,7 +140,7 @@ const Map = {
Create.newTopic.hide(true) // true means force (and override pinned) Create.newTopic.hide(true) // true means force (and override pinned)
Create.newSynapse.hide() Create.newSynapse.hide()
Filter.close() Filter.close()
Map.InfoBox.close() InfoBox.close()
Realtime.endActiveMap() Realtime.endActiveMap()
} }
}, },

View file

@ -21,16 +21,7 @@ const PasteInput = {
e.preventDefault(); e.preventDefault();
var coords = Util.pixelsToCoords({ x: e.clientX, y: e.clientY }) var coords = Util.pixelsToCoords({ x: e.clientX, y: e.clientY })
if (e.dataTransfer.files.length > 0) { if (e.dataTransfer.files.length > 0) {
var fileReader = new window.FileReader() self.handleFile(e.dataTransfer.files[0], coords)
fileReader.readAsText(e.dataTransfer.files[0])
fileReader.onload = function(e) {
var text = e.currentTarget.result
if (text.substring(0,5) === '<?xml') {
// assume this is a macOS .webloc link
text = text.replace(/[\s\S]*<string>(.*)<\/string>[\s\S]*/m, '$1')
}
self.handle(text, coords)
}
} }
// OMG import bookmarks 😍 // OMG import bookmarks 😍
if (e.dataTransfer.items.length > 0) { 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) === '<?xml') {
// assume this is a macOS .webloc link
text = text.replace(/[\s\S]*<string>(.*)<\/string>[\s\S]*/m, '$1')
}
self.handle(text, coords)
}
},
handle: function(text, coords = null) {
var self = PasteInput var self = PasteInput
if (text.match(self.URL_REGEX)) { if (text.match(self.URL_REGEX)) {

View file

@ -10,7 +10,7 @@ import Create from './Create'
import Debug from './Debug' import Debug from './Debug'
import Filter from './Filter' import Filter from './Filter'
import GlobalUI, { import GlobalUI, {
Search, CreateMap, Account as GlobalUI_Account Search, CreateMap, ImportDialog, Account as GlobalUI_Account
} from './GlobalUI' } from './GlobalUI'
import Import from './Import' import Import from './Import'
import JIT from './JIT' import JIT from './JIT'
@ -47,6 +47,7 @@ Metamaps.GlobalUI = GlobalUI
Metamaps.GlobalUI.Search = Search Metamaps.GlobalUI.Search = Search
Metamaps.GlobalUI.CreateMap = CreateMap Metamaps.GlobalUI.CreateMap = CreateMap
Metamaps.GlobalUI.Account = GlobalUI_Account Metamaps.GlobalUI.Account = GlobalUI_Account
Metamaps.GlobalUI.ImportDialog = ImportDialog
Metamaps.Import = Import Metamaps.Import = Import
Metamaps.JIT = JIT Metamaps.JIT = JIT
Metamaps.Listeners = Listeners Metamaps.Listeners = Listeners

View file

@ -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 (
<div className="import-dialog">
<h3>EXPORT</h3>
<div className="import-blue-button" onClick={this.handleExport('csv')}>
Export as CSV
</div>
<div className="import-blue-button" onClick={this.handleExport('json')}>
Export as JSON
</div>
<h3>IMPORT</h3>
<p>To upload a file, drop it here:</p>
<Dropzone onDropAccepted={this.handleFile}
className="import-blue-button fileupload"
>
Drop files here!
</Dropzone>
<p>
<a onClick={this.toggleShowInstructions} style={{ textDecoration: 'underline', cursor: 'pointer' }}>
Show/hide import instructions
</a>
</p>
{!this.state.showImportInstructions ? null : (<div>
<p>
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).
</p>
<img src={this.props.exampleImageUrl} style={{ maxWidth: '75%', float: 'right', margin: '1em' }}/>
<p style={{ marginTop: '1em' }}>You can choose which columns to include in your data. Topics must have a name field. Synapses must have Topic 1 and Topic 2.</p>
<p>&nbsp;</p>
<p> * 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.</p>
<p> * If you are importing a list of links, you can use a Link column in place of the Name column.</p>
</div>)}
</div>
)
}
}
ImportDialogBox.propTypes = {
onFileAdded: PropTypes.func,
exampleImageUrl: PropTypes.string
}
export default ImportDialogBox

View file

@ -33,6 +33,7 @@
"outdent": "0.2.1", "outdent": "0.2.1",
"react": "15.3.2", "react": "15.3.2",
"react-dom": "15.3.2", "react-dom": "15.3.2",
"react-dropzone": "3.6.0",
"socket.io": "0.9.12", "socket.io": "0.9.12",
"webpack": "1.13.2" "webpack": "1.13.2"
}, },