diff --git a/app/assets/images/attachmentFileTypeIcons/open-iconic-document.svg b/app/assets/images/attachmentFileTypeIcons/open-iconic-document.svg
new file mode 100644
index 00000000..c3e2b061
--- /dev/null
+++ b/app/assets/images/attachmentFileTypeIcons/open-iconic-document.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/app/assets/images/attachmentFileTypeIcons/open-iconic-file.svg b/app/assets/images/attachmentFileTypeIcons/open-iconic-file.svg
new file mode 100644
index 00000000..6a5932db
--- /dev/null
+++ b/app/assets/images/attachmentFileTypeIcons/open-iconic-file.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/app/assets/images/attachmentFileTypeIcons/open-iconic-image.svg b/app/assets/images/attachmentFileTypeIcons/open-iconic-image.svg
new file mode 100644
index 00000000..092665c1
--- /dev/null
+++ b/app/assets/images/attachmentFileTypeIcons/open-iconic-image.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/app/assets/images/attachmentFileTypeIcons/open-iconic-musical-note.svg b/app/assets/images/attachmentFileTypeIcons/open-iconic-musical-note.svg
new file mode 100644
index 00000000..bc66c5c9
--- /dev/null
+++ b/app/assets/images/attachmentFileTypeIcons/open-iconic-musical-note.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/app/assets/images/attachmentFileTypeIcons/open-iconic-question-mark.svg b/app/assets/images/attachmentFileTypeIcons/open-iconic-question-mark.svg
new file mode 100644
index 00000000..4eb6ff36
--- /dev/null
+++ b/app/assets/images/attachmentFileTypeIcons/open-iconic-question-mark.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/app/assets/images/recording-button.png b/app/assets/images/recording-button.png
new file mode 100644
index 00000000..7ea03b22
Binary files /dev/null and b/app/assets/images/recording-button.png differ
diff --git a/app/assets/images/upload_icons/CameraIcons.png b/app/assets/images/upload_icons/CameraIcons.png
new file mode 100644
index 00000000..ee0b77ef
Binary files /dev/null and b/app/assets/images/upload_icons/CameraIcons.png differ
diff --git a/app/assets/images/upload_icons/CloudIcons.png b/app/assets/images/upload_icons/CloudIcons.png
new file mode 100644
index 00000000..eb887a59
Binary files /dev/null and b/app/assets/images/upload_icons/CloudIcons.png differ
diff --git a/app/assets/images/upload_icons/LinkIcons.png b/app/assets/images/upload_icons/LinkIcons.png
new file mode 100644
index 00000000..1f0fbdd4
Binary files /dev/null and b/app/assets/images/upload_icons/LinkIcons.png differ
diff --git a/app/assets/images/upload_icons/MicIcons.png b/app/assets/images/upload_icons/MicIcons.png
new file mode 100644
index 00000000..4cae5e4c
Binary files /dev/null and b/app/assets/images/upload_icons/MicIcons.png differ
diff --git a/app/assets/javascripts/Metamaps.ServerData.js.erb b/app/assets/javascripts/Metamaps.ServerData.js.erb
index bd412a93..0c96df3c 100644
--- a/app/assets/javascripts/Metamaps.ServerData.js.erb
+++ b/app/assets/javascripts/Metamaps.ServerData.js.erb
@@ -13,6 +13,13 @@ Metamaps.ServerData['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.
Metamaps.ServerData['exploremaps_sprite.png'] = '<%= asset_path 'exploremaps_sprite.png' %>'
Metamaps.ServerData['map_control_sprite.png'] = '<%= asset_path 'map_control_sprite.png' %>'
Metamaps.ServerData['user_sprite.png'] = '<%= asset_path 'user_sprite.png' %>'
+Metamaps.ServerData.attachmentFileTypeIcons = {
+ pdf: '<%= asset_path('attachmentFileTypeIcons//open-iconic-document.svg') %>',
+ text: '<%= asset_path('attachmentFileTypeIcons//open-iconic-file.svg') %>',
+ image: '<%= asset_path('attachmentFileTypeIcons//open-iconic-image.svg') %>',
+ audio: '<%= asset_path('attachmentFileTypeIcons//open-iconic-musical-note.svg') %>',
+ unknown: '<%= asset_path('attachmentFileTypeIcons//open-iconic-question-mark.svg') %>'
+}
Metamaps.ServerData.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
Metamaps.ServerData.REALTIME_SERVER = '<%= ENV['REALTIME_SERVER'] %>'
Metamaps.ServerData.RAILS_ENV = '<%= ENV['RAILS_ENV'] %>'
diff --git a/app/assets/stylesheets/base.scss.erb b/app/assets/stylesheets/base.scss.erb
index b4b7bb0c..f7e8f8ee 100644
--- a/app/assets/stylesheets/base.scss.erb
+++ b/app/assets/stylesheets/base.scss.erb
@@ -197,6 +197,7 @@ $mid-gray-opacity: rgba(66, 66, 66, 0.6);
.CardOnGraph .links {
position: relative;
+ background-color: #e0e0e0;
z-index: 2;
.linkItem {
@@ -625,18 +626,117 @@ background-color: #E0E0E0;
z-index:100;
}
+.attachments {
+ border-top: 1px solid #bdbdbd;
+ position: relative;
+ min-height: 3em;
+
+ .file {
+ margin-top: 0.75em;
+ max-width: 85%;
+
+ .filetype-icon {
+ width: 16px;
+ height: 16px;
+ padding: 0.5em;
+ padding-top: 0;
+ float: left;
+ }
+ }
+}
+
+.upload-audio-start,
+.upload-file-dropzone,
+.upload-photo-dropzone,
+.CardOnGraph .attachment-type-chooser > div {
+ text-align: center;
+ color: #cccccc;
+ font-size: 12px;
+ cursor: pointer;
+
+ &:hover {
+ color: #999999;
+ }
+
+ & > div {
+ width: 48px;
+ height: 48px;
+ margin: 0 auto;
+ box-sizing: border-box;
+ padding-top: 75%;
+ }
+ &.photo-upload > div {
+ background-image: url(<%= asset_path('upload_icons/CameraIcons.png') %>);
+ }
+ &.link-upload > div {
+ background-image: url(<%= asset_path('upload_icons/LinkIcons.png') %>);
+ }
+ &.audio-upload > div {
+ background-image: url(<%= asset_path('upload_icons/MicIcons.png') %>);
+ }
+ &.file-upload > div {
+ background-image: url(<%= asset_path('upload_icons/CloudIcons.png') %>);
+ }
+}
+
+.photo-upload,
+.link-upload,
+.audio-upload,
+.file-upload {
+ background-repeat: no-repeat;
+ background-size: 48px 48px;
+ background-position: 0 center;
+ width: 48px;
+ height: 48px;
+
+ &:hover {
+ /*background-position: ;*/
+ }
+}
+
+.upload-audio-start,
+.upload-file-dropzone,
+.upload-photo-dropzone {
+ padding-top: 0.75em;
+}
+
+.upload-audio-recording {
+ font-size: small;
+ color: #aaa;
+ text-align: center;
+}
+
+$recording_button_size: 48px;
+.upload-audio-stop {
+ background-image: url(<%= asset_path('recording-button.png') %>);
+ background-size: $recording_button_size;
+ width: $recording_button_size;
+ height: $recording_button_size;
+ margin: 0 auto;
+ cursor: pointer;
+}
+
+.CardOnGraph .attachment-type-chooser {
+ padding-top: .75em;
+
+ & > div {
+ display: inline-block;
+ width: 25%;
+ }
+}
+
#embedlyLinkLoader {
margin: 0 auto;
width: 28px;
}
-.CardOnGraph .link-adder {
+.CardOnGraph .link-chooser {
width:100%;
height:47px;
position: relative;
}
-.link-adder a {
+.link-chooser a {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -720,7 +820,7 @@ font-family: 'din-regular', helvetica, sans-serif;
z-index: 1;
}
-#addLinkReset {
+.attachment-cancel {
position: absolute;
top: 8px;
right: 15px;
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
index a18e0956..dd6a569c 100644
--- a/app/models/attachment.rb
+++ b/app/models/attachment.rb
@@ -17,7 +17,7 @@ class Attachment < ApplicationRecord
validates :attachable, presence: true
validates_attachment_content_type :file, content_type: Attachable.allowed_types
- validates_attachment_size :file, in: 0.megabytes..5.megabytes
+ validates_attachment_size :file, :in => 0.megabytes..5.megabytes
def image?
Attachable.image_types.include?(file.instance.file_content_type)
diff --git a/frontend/src/Metamaps/GlobalUI/ReactApp.js b/frontend/src/Metamaps/GlobalUI/ReactApp.js
index 6237f08a..63820a53 100644
--- a/frontend/src/Metamaps/GlobalUI/ReactApp.js
+++ b/frontend/src/Metamaps/GlobalUI/ReactApp.js
@@ -37,12 +37,14 @@ const ReactApp = {
mobileTitle: '',
mobileTitleWidth: 0,
metacodeSets: [],
+ attachmentFileTypeIcons: {},
init: function(serverData, openLightbox) {
const self = ReactApp
self.serverData = serverData
self.mobileTitle = serverData.mobileTitle
self.openLightbox = openLightbox
self.metacodeSets = serverData.metacodeSets
+ self.attachmentFileTypeIcons = serverData.attachmentFileTypeIcons
routes = makeRoutes(serverData.ActiveMapper)
self.resize()
window && window.addEventListener('resize', self.resize)
@@ -154,10 +156,13 @@ const ReactApp = {
getTopicCardProps: function() {
const self = ReactApp
return {
- openTopic: TopicCard.openTopic,
metacodeSets: self.metacodeSets,
+ onTopicFollow: Topic.onTopicFollow,
+ openTopic: TopicCard.openTopic,
updateTopic: (topic, obj) => topic.save(obj),
- onTopicFollow: Topic.onTopicFollow
+ uploadAttachment: TopicCard.uploadAttachment,
+ removeAttachment: TopicCard.removeAttachment,
+ fileTypeIcons: self.attachmentFileTypeIcons
}
},
getContextMenuProps: function() {
diff --git a/frontend/src/Metamaps/Views/TopicCard.js b/frontend/src/Metamaps/Views/TopicCard.js
index 943869e9..dadc81b5 100644
--- a/frontend/src/Metamaps/Views/TopicCard.js
+++ b/frontend/src/Metamaps/Views/TopicCard.js
@@ -1,3 +1,5 @@
+/* global $ */
+
import { ReactApp } from '../GlobalUI'
const TopicCard = {
@@ -9,6 +11,56 @@ const TopicCard = {
hideCard: function() {
TopicCard.openTopic = null
ReactApp.render()
+ },
+ uploadAttachment: (topic, file) => {
+ const data = new window.FormData()
+ data.append('attachment[file]', file)
+ data.append('attachment[attachable_type]', 'Topic')
+ data.append('attachment[attachable_id]', topic.id)
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ url: '/attachments',
+ type: 'POST',
+ data,
+ processData: false,
+ contentType: false,
+ success: (data) => {
+ console.log('file upolad success', data)
+ topic.fetch({ success: () => {
+ ReactApp.render()
+ resolve(true)
+ }})
+ },
+ error: (error) => {
+ console.error(error)
+ window.alert('File upload failed')
+ topic.fetch({ success: () => {
+ ReactApp.render()
+ resolve(false)
+ }})
+ }
+ })
+ })
+ },
+ removeAttachment: (topic) => {
+ const attachments = topic.get('attachments')
+ if (!attachments || attachments.length < 1) {
+ return
+ }
+
+ $.ajax({
+ url: `/attachments/${attachments[0].id}`,
+ type: 'DELETE',
+ success: () => {
+ console.log('delete success, syncing topic')
+ topic.fetch({ success: () => ReactApp.render() })
+ },
+ error: error => {
+ console.error(error)
+ window.alert('Failed to remove attachment')
+ topic.fetch({ success: () => ReactApp.render() })
+ }
+ })
}
}
diff --git a/frontend/src/components/TopicCard/Attachments.js b/frontend/src/components/TopicCard/Attachments.js
index fcb5bea8..539d7271 100644
--- a/frontend/src/components/TopicCard/Attachments.js
+++ b/frontend/src/components/TopicCard/Attachments.js
@@ -1,20 +1,101 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import EmbedlyLink from './EmbedlyLink'
+import EmbedlyLinkChooser from './EmbedlyLinkChooser'
+import EmbedlyCard from './EmbedlyCard'
+import FileUploader from './FileUploader'
+import PhotoUploader from './PhotoUploader'
+import AudioUploader from './AudioUploader'
+import FileAttachment from './FileAttachment'
class Attachments extends Component {
+ constructor(props) {
+ super(props)
+
+ this.state = this.defaultState
+ }
+
+ defaultState = {
+ addingPhoto: false,
+ addingLink: false,
+ addingAudio: false,
+ addingFile: false
+ }
+
+ clearState = () => {
+ this.setState(this.defaultState)
+ }
+
+ // onClick handler for the 4 buttons, which triggers showing the proper uploader
+ choose = key => () => {
+ this.setState(Object.assign({}, this.defaultState, { [key]: true }))
+ }
+
render = () => {
const { topic, authorizedToEdit, updateTopic } = this.props
const link = topic.get('link')
+ const attachments = topic.get('attachments')
+ const file = attachments && attachments.length ? attachments[0] : null
+
+ let childComponent
+ if (link) {
+ childComponent = (
+