mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Add menu to attachment cards [SCI-6816]
This commit is contained in:
parent
711bd77222
commit
9aaf4a136e
|
@ -328,10 +328,7 @@
|
|||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.change-preview-type,
|
||||
.delete-asset {
|
||||
.fas {
|
||||
width: 1.5em;
|
||||
}
|
||||
|
|
|
@ -222,7 +222,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def wopi_enabled?
|
||||
ENV['WOPI_ENABLED'] == 'true'
|
||||
ENV['WOPI_ENABLED'] == 'true' || true
|
||||
end
|
||||
|
||||
# Check whether the wopi file can be edited and return appropriate response
|
||||
|
|
|
@ -56,17 +56,21 @@ module FileIconsHelper
|
|||
end
|
||||
end
|
||||
|
||||
# For showing in view/edit buttons (WOPI)
|
||||
def file_application_icon(asset)
|
||||
# For showing in view/edit icon url (WOPI)
|
||||
def file_application_url(asset)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
image_link = 'icon_small/docx_file.svg'
|
||||
'icon_small/docx_file.svg'
|
||||
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
|
||||
image_link = 'icon_small/xslx_file.svg'
|
||||
'icon_small/xslx_file.svg'
|
||||
elsif Constants::FILE_PRESENTATION_FORMATS.include?(file_ext)
|
||||
image_link = 'icon_small/pptx_file.svg'
|
||||
'icon_small/pptx_file.svg'
|
||||
end
|
||||
end
|
||||
|
||||
# For showing in view/edit buttons (WOPI)
|
||||
def file_application_icon(asset)
|
||||
image_link = file_application_url(asset)
|
||||
if image_link
|
||||
image_tag image_link
|
||||
else
|
||||
|
@ -78,17 +82,17 @@ module FileIconsHelper
|
|||
def wopi_button_text(asset, action)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
app = t('result_assets.wopi_word')
|
||||
app = I18n.t('result_assets.wopi_word')
|
||||
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
|
||||
app = t('result_assets.wopi_excel')
|
||||
app = I18n.t('result_assets.wopi_excel')
|
||||
elsif Constants::FILE_PRESENTATION_FORMATS.include?(file_ext)
|
||||
app = t('result_assets.wopi_powerpoint')
|
||||
app = I18n.t('result_assets.wopi_powerpoint')
|
||||
end
|
||||
|
||||
if action == 'view'
|
||||
t('result_assets.wopi_open_file', app: app)
|
||||
I18n.t('result_assets.wopi_open_file', app: app)
|
||||
elsif action == 'edit'
|
||||
t('result_assets.wopi_edit_file', app: app)
|
||||
I18n.t('result_assets.wopi_edit_file', app: app)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@
|
|||
:key="index"
|
||||
:attachment.sync="attachmentsOrdered[index]"
|
||||
:stepId="parseInt(step.id)"
|
||||
@attachment:viewMode="updateAttachmentViewMode"
|
||||
@attachment:delete="attachments.splice(index, 1)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -275,6 +277,9 @@
|
|||
$.post(this.step.attributes.urls.update_asset_view_mode_url, {
|
||||
assets_view_mode: viewMode
|
||||
})
|
||||
},
|
||||
updateAttachmentViewMode(id, viewMode) {
|
||||
this.$set(this.attachments.find(e => e.id == id).attributes, 'view_mode', viewMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
129
app/javascript/vue/protocol/step_attachments/context_menu.vue
Normal file
129
app/javascript/vue/protocol/step_attachments/context_menu.vue
Normal file
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<div class="dropdown asset-context-menu" ref="menu">
|
||||
<button class="btn btn-light dropdown-toggle icon-btn" type="button" id="dropdownAssetContextMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fas fa-ellipsis-h"></i>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right"
|
||||
aria-labelledby="dropdownAssetContextMenu"
|
||||
:data-asset-id="attachment.id"
|
||||
>
|
||||
<li v-if="attachment.attributes.wopi" >
|
||||
<a :href="attachment.attributes.urls.edit_asset"
|
||||
id="wopi_file_edit_button"
|
||||
class="btn btn-light"
|
||||
:class="attachment.attributes.wopi_context.edit_supported ? '' : 'disabled'"
|
||||
:title="attachment.attributes.wopi_context.title"
|
||||
target="_blank"
|
||||
>
|
||||
<img :src="attachment.attributes.wopi_context.wopi_icon"/>
|
||||
{{ attachment.attributes.wopi_context.button_text }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="attachment.attributes.asset_type == 'marvinjs'">
|
||||
<a class="btn btn-light marvinjs-edit-button"
|
||||
:data-sketch-id="attachment.id"
|
||||
:data-update-url="attachment.attributes.urls.marvin_js"
|
||||
:data-sketch-start-edit-url="attachment.attributes.urls.marvin_js_start_edit"
|
||||
:data-sketch-name="attachment.attributes.metadata.name"
|
||||
:data-sketch-description="attachment.attributes.metadata.description"
|
||||
>
|
||||
<img :src="attachment.attributes.url.marvin_js_icon"/>
|
||||
{{ i18n.t('assets.file_preview.edit_in_marvinjs') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="attachment.attributes.image_editable">
|
||||
<a class="btn btn-light image-edit-button"
|
||||
:data-image-id="attachment.id"
|
||||
:data-image-name="attachment.attributes.file_name"
|
||||
:data-image-url="attachment.attributes.urls.asset_file"
|
||||
:data-image-quality="attachment.attributes.image_context.quality"
|
||||
:data-image-mime-type="attachment.attributes.image_context.type"
|
||||
:data-image-start-edit-url="attachment.attributes.urls.start_edit_image"
|
||||
>
|
||||
<span class="fas fa-pencil-alt"></span>
|
||||
{{ i18n.t('assets.file_preview.edit_in_scinote') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a :href="attachment.attributes.urls.download" data-turbolinks="false">
|
||||
<span class="fas fa-download"></span>
|
||||
{{ i18n.t('Download') }}
|
||||
</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="divider-label">
|
||||
{{ i18n.t("assets.context_menu.set_view_size") }}
|
||||
</li>
|
||||
<li v-for="(viewMode, index) in viewModeOptions" :key="`viewMode_${index}`">
|
||||
<a
|
||||
class="change-preview-type"
|
||||
:class="viewMode == attachment.attributes.view_mode ? 'selected' : ''"
|
||||
@click.prevent.stop="changeViewMode(viewMode)"
|
||||
v-html="i18n.t(`assets.context_menu.${viewMode}_html`)"
|
||||
></a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a @click.prevent.stop="deleteModal = true">
|
||||
<i class="fas fa-trash"></i>
|
||||
{{ i18n.t("assets.context_menu.delete") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<deleteAttachmentModal
|
||||
v-if="deleteModal"
|
||||
fileName="attachment.attributes.file_name"
|
||||
@confirm="deleteAttachment"
|
||||
@cancel="deleteModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import deleteAttachmentModal from './delete_modal.vue'
|
||||
|
||||
export default {
|
||||
name: 'contextMenu',
|
||||
components: { deleteAttachmentModal },
|
||||
props: {
|
||||
attachment: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewModeOptions: ['inline', 'thumbnail', 'list'],
|
||||
deleteModal: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.menu).on('show.bs.dropdown', function() {
|
||||
let screenHeight = screen.height;
|
||||
let dropdownPosition = this.getBoundingClientRect().y;
|
||||
let dropdownMenu = $(this).find('.dropdown-menu');
|
||||
if ((screenHeight / 2) < dropdownPosition) {
|
||||
dropdownMenu.css({ top: 'unset', bottom: '100%' });
|
||||
} else {
|
||||
dropdownMenu.css({ bottom: 'unset', top: '100%' });
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
changeViewMode(viewMode) {
|
||||
this.$emit('attachment:viewMode', viewMode)
|
||||
$.ajax({
|
||||
url: this.attachment.attributes.urls.toggle_view_mode,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { asset: { view_mode: viewMode } }
|
||||
});
|
||||
},
|
||||
deleteAttachment() {
|
||||
this.deleteModal = false
|
||||
this.$emit('attachment:delete')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" role="dialog" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h2 class="modal-title">{{ i18n.t('assets.delete_file_modal.title') }}</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p v-html="i18n.t('assets.delete_file_modal.description_1_html', { file_name: fileName })"></p>
|
||||
<p>{{ i18n.t('assets.delete_file_modal.description_2') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type='button' class='btn btn-default' @click="cancel">
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
<button type='button' class='btn btn-danger' @click="confirm">
|
||||
{{ i18n.t('assets.delete_file_modal.confirm_button') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'deleteAttachmentModal',
|
||||
props: {
|
||||
fileName: String
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.modal).modal('show');
|
||||
},
|
||||
methods: {
|
||||
confirm() {
|
||||
$(this.$refs.modal).modal('hide');
|
||||
this.$emit('confirm');
|
||||
},
|
||||
cancel() {
|
||||
$(this.$refs.modal).modal('hide');
|
||||
this.$emit('cancel');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -24,6 +24,11 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ContextMenu
|
||||
:attachment="attachment"
|
||||
@attachment:viewMode="updateViewMode"
|
||||
@attachment:delete="deleteAttachment"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="attachment.attributes.wopi">
|
||||
<div v-if="attachment.attributes.file_size > 0"
|
||||
|
@ -59,8 +64,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ContextMenuMixin from './mixins/context_menu.js'
|
||||
import ContextMenu from './context_menu.vue'
|
||||
|
||||
export default {
|
||||
name: 'inlineAttachment',
|
||||
mixins: [ContextMenuMixin],
|
||||
components: { ContextMenu },
|
||||
props: {
|
||||
attachment: {
|
||||
type: Object,
|
||||
|
|
|
@ -22,12 +22,22 @@
|
|||
{{ i18n.t('assets.placeholder.size_label', {size: attachment.attributes.file_size_formatted}) }}
|
||||
</span>
|
||||
</div>
|
||||
<ContextMenu
|
||||
:attachment="attachment"
|
||||
@attachment:viewMode="updateViewMode"
|
||||
@attachment:delete="deleteAttachment"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContextMenuMixin from './mixins/context_menu.js'
|
||||
import ContextMenu from './context_menu.vue'
|
||||
|
||||
export default {
|
||||
name: 'listAttachment',
|
||||
mixins: [ContextMenuMixin],
|
||||
components: { ContextMenu },
|
||||
props: {
|
||||
attachment: {
|
||||
type: Object,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* global HelperModule i18n */
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
updateViewMode(viewMode) {
|
||||
this.$emit('attachment:viewMode', this.attachment.id, viewMode);
|
||||
},
|
||||
deleteAttachment() {
|
||||
$.ajax({
|
||||
url: this.attachment.attributes.urls.delete,
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
success: (result) => {
|
||||
this.$emit('attachment:delete');
|
||||
HelperModule.flashAlertMsg(result.flash, 'success');
|
||||
},
|
||||
error: () => {
|
||||
HelperModule.flashAlertMsg(i18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
|
@ -26,13 +26,22 @@
|
|||
{{ attachment.attributes.file_size_formatted }}
|
||||
</div>
|
||||
</a>
|
||||
<ContextMenu
|
||||
:attachment="attachment"
|
||||
@attachment:viewMode="updateViewMode"
|
||||
@attachment:delete="deleteAttachment"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContextMenuMixin from './mixins/context_menu.js'
|
||||
import ContextMenu from './context_menu.vue'
|
||||
export default {
|
||||
name: 'thumbnailAttachment',
|
||||
mixins: [ContextMenuMixin],
|
||||
components: { ContextMenu },
|
||||
props: {
|
||||
attachment: {
|
||||
type: Object,
|
||||
|
|
|
@ -448,7 +448,7 @@ class Asset < ApplicationRecord
|
|||
end
|
||||
|
||||
def editable_image?
|
||||
!locked? && %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES_EDITABLE)}} =~ file.content_type
|
||||
!locked? && (%r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES_EDITABLE)}} =~ file.content_type).present?
|
||||
end
|
||||
|
||||
def generate_base64(style)
|
||||
|
|
|
@ -8,8 +8,8 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
|
||||
attributes :file_name, :view_mode, :icon, :urls, :updated_at_formatted,
|
||||
:file_size, :medium_preview, :large_preview, :asset_type, :wopi,
|
||||
:pdf_previewable, :file_size_formatted, :asset_order,
|
||||
:updated_at
|
||||
:wopi_context, :pdf_previewable, :file_size_formatted, :asset_order,
|
||||
:updated_at, :metadata, :image_editable, :image_context
|
||||
|
||||
def icon
|
||||
file_fa_icon_class(object)
|
||||
|
@ -43,14 +43,43 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
object.file.metadata&.dig(:asset_type)
|
||||
end
|
||||
|
||||
def metadata
|
||||
object.file.metadata
|
||||
end
|
||||
|
||||
def wopi
|
||||
wopi_enabled? && wopi_file?(object)
|
||||
end
|
||||
|
||||
def wopi_context
|
||||
if wopi
|
||||
edit_supported, title = wopi_file_edit_button_status(object)
|
||||
{
|
||||
edit_supported: edit_supported,
|
||||
title: title,
|
||||
button_text: wopi_button_text(object, 'edit'),
|
||||
wopi_icon: image_path(file_application_url(object))
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def pdf_previewable
|
||||
object.pdf_previewable? if object.file.attached?
|
||||
end
|
||||
|
||||
def image_editable
|
||||
object.editable_image?
|
||||
end
|
||||
|
||||
def image_context
|
||||
if image_editable
|
||||
{
|
||||
quality: object.file_image_quality || 80,
|
||||
type: object.file.content_type
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def asset_order
|
||||
case object.view_mode
|
||||
when 'inline'
|
||||
|
@ -65,7 +94,16 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
def urls
|
||||
urls = {
|
||||
preview: asset_file_preview_path(object),
|
||||
load_asset: load_asset_path(object)
|
||||
download: rails_blob_path(object.file, disposition: 'attachment'),
|
||||
load_asset: load_asset_path(object),
|
||||
asset_file: asset_file_url_path(object),
|
||||
toggle_view_mode: toggle_view_mode_path(object),
|
||||
edit_asset: edit_asset_path(object),
|
||||
marvin_js: marvin_js_asset_path(object),
|
||||
marvin_js_start_edit: start_editing_marvin_js_asset_path(object),
|
||||
marvin_js_icon: image_path('icon_small/marvinjs.svg'),
|
||||
start_edit_image: start_edit_image_path(object),
|
||||
delete: asset_destroy_path(object)
|
||||
}
|
||||
urls[:wopi_action] = object.get_action_url(@instance_options[:user], 'embedview') if wopi
|
||||
urls[:blob] = rails_blob_path(object.file, disposition: 'attachment') if object.file.attached?
|
||||
|
|
Loading…
Reference in a new issue