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 {
|
&:hover {
|
||||||
background: $color-concrete;
|
background: $color-concrete;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.change-preview-type,
|
|
||||||
.delete-asset {
|
|
||||||
.fas {
|
.fas {
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def wopi_enabled?
|
def wopi_enabled?
|
||||||
ENV['WOPI_ENABLED'] == 'true'
|
ENV['WOPI_ENABLED'] == 'true' || true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check whether the wopi file can be edited and return appropriate response
|
# Check whether the wopi file can be edited and return appropriate response
|
||||||
|
|
|
@ -56,17 +56,21 @@ module FileIconsHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# For showing in view/edit buttons (WOPI)
|
# For showing in view/edit icon url (WOPI)
|
||||||
def file_application_icon(asset)
|
def file_application_url(asset)
|
||||||
file_ext = asset.file_name.split('.').last
|
file_ext = asset.file_name.split('.').last
|
||||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
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)
|
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)
|
elsif Constants::FILE_PRESENTATION_FORMATS.include?(file_ext)
|
||||||
image_link = 'icon_small/pptx_file.svg'
|
'icon_small/pptx_file.svg'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# For showing in view/edit buttons (WOPI)
|
||||||
|
def file_application_icon(asset)
|
||||||
|
image_link = file_application_url(asset)
|
||||||
if image_link
|
if image_link
|
||||||
image_tag image_link
|
image_tag image_link
|
||||||
else
|
else
|
||||||
|
@ -78,17 +82,17 @@ module FileIconsHelper
|
||||||
def wopi_button_text(asset, action)
|
def wopi_button_text(asset, action)
|
||||||
file_ext = asset.file_name.split('.').last
|
file_ext = asset.file_name.split('.').last
|
||||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
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)
|
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)
|
elsif Constants::FILE_PRESENTATION_FORMATS.include?(file_ext)
|
||||||
app = t('result_assets.wopi_powerpoint')
|
app = I18n.t('result_assets.wopi_powerpoint')
|
||||||
end
|
end
|
||||||
|
|
||||||
if action == 'view'
|
if action == 'view'
|
||||||
t('result_assets.wopi_open_file', app: app)
|
I18n.t('result_assets.wopi_open_file', app: app)
|
||||||
elsif action == 'edit'
|
elsif action == 'edit'
|
||||||
t('result_assets.wopi_edit_file', app: app)
|
I18n.t('result_assets.wopi_edit_file', app: app)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,8 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
:attachment.sync="attachmentsOrdered[index]"
|
:attachment.sync="attachmentsOrdered[index]"
|
||||||
:stepId="parseInt(step.id)"
|
:stepId="parseInt(step.id)"
|
||||||
|
@attachment:viewMode="updateAttachmentViewMode"
|
||||||
|
@attachment:delete="attachments.splice(index, 1)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -275,6 +277,9 @@
|
||||||
$.post(this.step.attributes.urls.update_asset_view_mode_url, {
|
$.post(this.step.attributes.urls.update_asset_view_mode_url, {
|
||||||
assets_view_mode: viewMode
|
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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ContextMenu
|
||||||
|
:attachment="attachment"
|
||||||
|
@attachment:viewMode="updateViewMode"
|
||||||
|
@attachment:delete="deleteAttachment"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="attachment.attributes.wopi">
|
<template v-if="attachment.attributes.wopi">
|
||||||
<div v-if="attachment.attributes.file_size > 0"
|
<div v-if="attachment.attributes.file_size > 0"
|
||||||
|
@ -59,8 +64,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ContextMenuMixin from './mixins/context_menu.js'
|
||||||
|
import ContextMenu from './context_menu.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'inlineAttachment',
|
name: 'inlineAttachment',
|
||||||
|
mixins: [ContextMenuMixin],
|
||||||
|
components: { ContextMenu },
|
||||||
props: {
|
props: {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -22,12 +22,22 @@
|
||||||
{{ i18n.t('assets.placeholder.size_label', {size: attachment.attributes.file_size_formatted}) }}
|
{{ i18n.t('assets.placeholder.size_label', {size: attachment.attributes.file_size_formatted}) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ContextMenu
|
||||||
|
:attachment="attachment"
|
||||||
|
@attachment:viewMode="updateViewMode"
|
||||||
|
@attachment:delete="deleteAttachment"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ContextMenuMixin from './mixins/context_menu.js'
|
||||||
|
import ContextMenu from './context_menu.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'listAttachment',
|
name: 'listAttachment',
|
||||||
|
mixins: [ContextMenuMixin],
|
||||||
|
components: { ContextMenu },
|
||||||
props: {
|
props: {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: Object,
|
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 }}
|
{{ attachment.attributes.file_size_formatted }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<ContextMenu
|
||||||
|
:attachment="attachment"
|
||||||
|
@attachment:viewMode="updateViewMode"
|
||||||
|
@attachment:delete="deleteAttachment"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ContextMenuMixin from './mixins/context_menu.js'
|
||||||
|
import ContextMenu from './context_menu.vue'
|
||||||
export default {
|
export default {
|
||||||
name: 'thumbnailAttachment',
|
name: 'thumbnailAttachment',
|
||||||
|
mixins: [ContextMenuMixin],
|
||||||
|
components: { ContextMenu },
|
||||||
props: {
|
props: {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -448,7 +448,7 @@ class Asset < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def editable_image?
|
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
|
end
|
||||||
|
|
||||||
def generate_base64(style)
|
def generate_base64(style)
|
||||||
|
|
|
@ -8,8 +8,8 @@ class AssetSerializer < ActiveModel::Serializer
|
||||||
|
|
||||||
attributes :file_name, :view_mode, :icon, :urls, :updated_at_formatted,
|
attributes :file_name, :view_mode, :icon, :urls, :updated_at_formatted,
|
||||||
:file_size, :medium_preview, :large_preview, :asset_type, :wopi,
|
:file_size, :medium_preview, :large_preview, :asset_type, :wopi,
|
||||||
:pdf_previewable, :file_size_formatted, :asset_order,
|
:wopi_context, :pdf_previewable, :file_size_formatted, :asset_order,
|
||||||
:updated_at
|
:updated_at, :metadata, :image_editable, :image_context
|
||||||
|
|
||||||
def icon
|
def icon
|
||||||
file_fa_icon_class(object)
|
file_fa_icon_class(object)
|
||||||
|
@ -43,14 +43,43 @@ class AssetSerializer < ActiveModel::Serializer
|
||||||
object.file.metadata&.dig(:asset_type)
|
object.file.metadata&.dig(:asset_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def metadata
|
||||||
|
object.file.metadata
|
||||||
|
end
|
||||||
|
|
||||||
def wopi
|
def wopi
|
||||||
wopi_enabled? && wopi_file?(object)
|
wopi_enabled? && wopi_file?(object)
|
||||||
end
|
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
|
def pdf_previewable
|
||||||
object.pdf_previewable? if object.file.attached?
|
object.pdf_previewable? if object.file.attached?
|
||||||
end
|
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
|
def asset_order
|
||||||
case object.view_mode
|
case object.view_mode
|
||||||
when 'inline'
|
when 'inline'
|
||||||
|
@ -65,7 +94,16 @@ class AssetSerializer < ActiveModel::Serializer
|
||||||
def urls
|
def urls
|
||||||
urls = {
|
urls = {
|
||||||
preview: asset_file_preview_path(object),
|
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[: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?
|
urls[:blob] = rails_blob_path(object.file, disposition: 'attachment') if object.file.attached?
|
||||||
|
|
Loading…
Reference in a new issue