Add menu to attachment cards [SCI-6816]

This commit is contained in:
Anton 2022-05-17 10:39:17 +02:00
parent 711bd77222
commit 9aaf4a136e
12 changed files with 290 additions and 18 deletions

View file

@ -328,10 +328,7 @@
&:hover {
background: $color-concrete;
}
}
.change-preview-type,
.delete-asset {
.fas {
width: 1.5em;
}

View file

@ -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

View file

@ -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

View file

@ -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)
}
}
}

View 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>

View file

@ -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">&times;</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>

View file

@ -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,

View file

@ -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,

View file

@ -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');
}
});
}
}
};

View file

@ -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,

View file

@ -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)

View file

@ -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?