Enable renaming attached files [SCI-10256]

This commit is contained in:
Ivan Kljun 2024-03-13 09:45:37 +01:00 committed by Martin Artnik
parent 5643f8fd66
commit 7ac7ebd32d
17 changed files with 268 additions and 4 deletions

View file

@ -18,7 +18,7 @@ class AssetsController < ApplicationController
before_action :load_vars, except: :create_wopi_file
before_action :check_read_permission, except: %i(edit destroy create_wopi_file toggle_view_mode)
before_action :check_edit_permission, only: %i(edit destroy toggle_view_mode)
before_action :check_edit_permission, only: %i(edit destroy toggle_view_mode rename)
def file_preview
render json: { html: render_to_string(
@ -311,6 +311,28 @@ class AssetsController < ApplicationController
end
end
def rename
new_name = params.require(:asset).permit(:name)[:name]
if new_name.empty?
render json: { error: 'File name must be at least 1 character long.' }, status: :unprocessable_entity
return
elsif new_name.length > Constants::NAME_MAX_LENGTH
render json: { error: 'File name is too long (maximum number is 255 characters).' }, status: :unprocessable_entity
return
end
ActiveRecord::Base.transaction do
@asset.last_modified_by = current_user
@asset.rename_file(new_name)
@asset.save!
end
render json: @asset, serializer: AssetSerializer, user: current_user
rescue ActiveRecord::RecordInvalid
render json: @asset.errors, status: :unprocessable_entity
end
def checksum
render json: { checksum: @asset.file.blob.checksum }
end

View file

@ -64,6 +64,34 @@ class GeneSequenceAssetsController < ApplicationController
head :ok
end
def rename
new_name = params.require(:asset).permit(:name)[:name]
if new_name.empty?
render json: { error: 'File name must be at least 1 character long.' }, status: :unprocessable_entity
return
elsif new_name.length > Constants::NAME_MAX_LENGTH
render json: { error: 'File name is too long (maximum number is 255 characters).' }, status: :unprocessable_entity
return
end
asset = current_team.assets.find_by(id: params[:id])
return render_404 unless asset
ActiveRecord::Base.transaction do
asset.last_modified_by = current_user
asset.rename_file(new_name)
asset.save!
# log_activity(TODO)
end
render json: asset, serializer: AssetSerializer, user: current_user
rescue StandardError => e
Rails.logger.error(e.message)
Rails.logger.error(e.backtrace.join("\n"))
render json: { error: I18n.t('errors.general') }, status: :unprocessable_entity
end
private
def save_asset!

View file

@ -45,6 +45,28 @@ class MarvinJsAssetsController < ApplicationController
end
end
def rename
new_name = params.require(:asset).permit(:name)[:name]
if new_name.empty?
render json: { error: 'File name must be at least 1 character long.' }, status: :unprocessable_entity
return
elsif new_name.length > Constants::NAME_MAX_LENGTH
render json: { error: 'File name is too long (maximum number is 255 characters).' }, status: :unprocessable_entity
return
end
asset = MarvinJsService.update_file_name(new_name, params[:id], current_user, current_team)
# create_rename_marvinjs_activity(asset, current_user, :TODO)
if asset
render json: asset, serializer: AssetSerializer, user: current_user
else
render json: { error: t('marvinjs.no_sketches_found') }, status: :unprocessable_entity
end
end
def start_editing
create_edit_marvinjs_activity(@asset, current_user, :start_editing)
end

View file

@ -129,6 +129,7 @@
:attachmentsReady="attachmentsReady"
@attachments:openFileModal="showFileModal = true"
@attachment:deleted="attachmentDeleted"
@attachment:update="updateAttachment"
@attachment:uploaded="loadAttachments"
@attachment:changed="reloadAttachment"
@attachments:order="changeAttachmentsOrder"
@ -550,6 +551,14 @@
this.attachments = this.attachments.filter((a) => a.id !== id );
this.$emit('stepUpdated');
},
updateAttachment(attachment) {
console.log(this.attachments)
console.log(attachment)
const index = this.attachments.findIndex(a => a.id === attachment.id);
if (index !== -1) {
this.attachments[index] = attachment;
}
},
closeCommentsSidebar() {
this.showCommentsSidebar = false
},

View file

@ -38,6 +38,7 @@
:attachment="attachment"
@attachment:viewMode="updateViewMode"
@attachment:delete="deleteAttachment"
@attachment:update="$emit('attachment:update', $event)"
/>
</div>

View file

@ -124,6 +124,7 @@
:attachmentsReady="attachmentsReady"
@attachments:openFileModal="showFileModal = true"
@attachment:deleted="attachmentDeleted"
@attachment:update="updateAttachment"
@attachment:uploaded="loadAttachments"
@attachment:moved="moveAttachment"
@attachments:order="changeAttachmentsOrder"
@ -429,6 +430,12 @@ export default {
this.attachments = this.attachments.filter((a) => a.id !== id);
this.$emit('resultUpdated');
},
updateAttachment(attachment) {
const index = this.attachments.findIndex((a) => a.id === attachment.id);
if (index !== -1) {
this.attachments[index] = attachment;
}
},
createElement(elementType, tableDimensions = [5, 5], plateTemplate = false) {
$.post(this.urls[`create_${elementType}_url`], { tableDimensions, plateTemplate }, (result) => {
result.data.isNew = true;

View file

@ -34,6 +34,7 @@
@attachment:moved="attachmentMoved"
@attachment:uploaded="$emit('attachment:uploaded')"
@attachment:changed="$emit('attachment:changed', $event)"
@attachment:update="$emit('attachment:update', $event)"
/>
</div>
</div>

View file

@ -35,11 +35,19 @@
@open_scinote_editor="openScinoteEditor"
@open_locally="openLocally"
@delete="deleteModal = true"
@rename="renameModal = true"
@viewMode="changeViewMode"
@move="showMoveModal"
@menu-visibility-changed="$emit('menu-visibility-changed', $event)"
></MenuDropdown>
<Teleport to="body">
<RenameAttachmentModal
v-if="renameModal"
:url_path="attachment.attributes.urls.rename"
:fileName="attachment.attributes.file_name"
@attachment:update="$emit('attachment:update', $event)"
@close="renameModal = false"
/>
<deleteAttachmentModal
v-if="deleteModal"
:fileName="attachment.attributes.file_name"
@ -72,6 +80,7 @@
</template>
<script>
import RenameAttachmentModal from '../modal/rename_modal.vue';
import deleteAttachmentModal from './delete_modal.vue';
import moveAssetModal from '../modal/move.vue';
import MoveMixin from './mixins/move.js';
@ -81,6 +90,7 @@ import MenuDropdown from '../../menu_dropdown.vue';
export default {
name: 'contextMenu',
components: {
RenameAttachmentModal,
deleteAttachmentModal,
moveAssetModal,
MenuDropdown
@ -96,7 +106,8 @@ export default {
data() {
return {
viewModeOptions: ['inline', 'thumbnail', 'list'],
deleteModal: false
deleteModal: false,
renameModal: false
};
},
computed: {
@ -157,6 +168,12 @@ export default {
data_e2e: 'e2e-BT-attachmentOptions-move'
});
}
if (this.attachment.attributes.urls.rename) {
menu.push({
text: this.i18n.t('assets.context_menu.rename'),
emit: 'rename'
});
}
if (this.attachment.attributes.urls.delete) {
menu.push({
text: this.i18n.t('assets.context_menu.delete'),

View file

@ -34,6 +34,7 @@
@attachment:delete="deleteAttachment"
@attachment:moved="attachmentMoved"
@attachment:uploaded="reloadAttachments"
@attachment:update="$emit('attachment:update', $event)"
/>
</div>
<template v-if="attachment.attributes.wopi">

View file

@ -35,6 +35,7 @@
@attachment:delete="deleteAttachment"
@attachment:moved="attachmentMoved"
@attachment:uploaded="reloadAttachments"
@attachment:update="$emit('attachment:update', $event)"
/>
</div>
</template>

View file

@ -126,6 +126,7 @@
@attachment:moved="attachmentMoved"
@attachment:uploaded="reloadAttachments"
@attachment:changed="$emit('attachment:changed', $event)"
@attachment:update="$emit('attachment:update', $event)"
@menu-visibility-changed="handleMenuVisibilityChange"
:withBorder="true"
/>

View file

@ -0,0 +1,90 @@
<template>
<div ref="modal" @keydown.esc="close" class="modal" id="renameAttachmentModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<h4 class="modal-title">
{{ i18n.t('assets.rename_modal.title') }}
</h4>
</div>
<div class="modal-body">
<p>{{ i18n.t('assets.from_clipboard.file_name')}}</p>
<div class="sci-input-container" :class="{ 'error': error }" :data-error-text="error">
<input ref="input" v-model="name" type="text" class="sci-input-field" @keyup.enter="renameAttachment(name)" required="true" />
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="close">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" @click="renameAttachment(name)" :disabled="error">{{ this.i18n.t('assets.context_menu.rename') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
import modalMixin from '../../modal_mixin';
import axios from '../../../../packs/custom_axios.js';
export default {
name: 'RenameAttachmentModal',
mixins: [modalMixin],
props: {
url_path: {
type: String,
required: true
},
fileName: {
type: String,
required: true
}
},
data() {
return {
name: null,
error: null
};
},
created() {
this.name = this.fileName;
},
watch: {
name() {
this.validateName();
}
},
methods: {
validateName() {
if (!this.name || this.name.length === 0) {
this.error = this.i18n.t('assets.rename_modal.min_length_error');
} else if (this.name.length > 255) {
this.error = this.i18n.t('assets.rename_modal.max_length_error');
} else {
this.error = null;
return true;
}
return false;
},
async renameAttachment(newName) {
if (!this.validateName()) {
return;
}
const payload = { asset: { name: newName } };
try {
const response = await axios.patch(this.url_path, payload);
this.$emit('attachment:update', response.data.data);
this.close();
} catch (error) {
if (error.response) {
console.error('Error: ', error.response.data.error || error.response.statusText);
} else {
console.error('Error: ', error.message);
}
}
}
}
};
</script>

View file

@ -352,6 +352,36 @@ class Asset < ApplicationRecord
step || result || repository_cell
end
def rename_file(new_name)
if file.attached?
asset_type = file.metadata['asset_type']
new_filename = case asset_type
when 'marvinjs'
"#{new_name}.jpg"
when 'gene_sequence'
"#{new_name}.json"
else
new_name
end
updated_metadata = file.blob.metadata.merge('name' => new_name)
if %w(marvinjs gene_sequence).include?(asset_type)
file.blob.update!(
filename: new_filename,
metadata: updated_metadata
)
else
file.blob.update!(filename: new_filename)
end
if asset_type == 'gene_sequence' && preview_image.attached?
new_image_filename = "#{new_name}.png"
preview_image.blob.update!(filename: new_image_filename)
end
end
end
private
def tempdir

View file

@ -145,7 +145,17 @@ class AssetSerializer < ActiveModel::Serializer
start_edit_image: start_edit_image_path(object),
delete: asset_destroy_path(object),
move_targets: asset_move_tagets_path(object),
move: asset_move_path(object)
move: asset_move_path(object),
rename: if object.file.attached?
case object.file.metadata['asset_type']
when 'marvinjs'
rename_marvin_js_asset_path(object)
when 'gene_sequence'
rename_gene_sequence_asset_path(object)
else
asset_rename_path(object)
end
end
)
end
urls[:open_vector_editor_edit] = edit_gene_sequence_asset_path(object.id) if can_manage_asset?(user, object)

View file

@ -45,6 +45,19 @@ class MarvinJsService
asset
end
def update_file_name(new_name, asset_id, current_user, current_team)
asset = current_team.assets.find(asset_id)
prepared_name = prepare_name(new_name)
ActiveRecord::Base.transaction do
asset.last_modified_by = current_user
asset.rename_file(prepared_name)
asset.save!
end
asset
end
private
def connect_asset(asset, params, current_user)

View file

@ -3721,11 +3721,16 @@ en:
edit_in_marvinjs: "Open in Marvin JS"
context_menu:
set_view_size: "SET PREVIEW SIZE"
rename: "Rename"
delete: "Delete"
move: "Move"
inline_html: "Large"
thumbnail_html: "Thumbnail"
list_html: "List"
rename_modal:
title: "Rename file"
min_length_error: "File name must be at least 1 character long."
max_length_error: "File name is too long (maximum number is 255 characters)."
delete_file_modal:
title: "Delete file"
description_1_html: "You are about to delete <b>%{file_name}</b></p>"

View file

@ -833,6 +833,7 @@ Rails.application.routes.draw do
get 'files/:id/move_targets', to: 'assets#move_targets', as: 'asset_move_tagets'
post 'files/:id/move', to: 'assets#move', as: 'asset_move'
delete 'files/:id/', to: 'assets#destroy', as: 'asset_destroy'
patch 'files/:id/rename', to: 'assets#rename', as: 'asset_rename'
post 'files/create_wopi_file',
to: 'assets#create_wopi_file',
as: 'create_wopi_file'
@ -1020,6 +1021,7 @@ Rails.application.routes.draw do
end
member do
post :start_editing
patch :rename
end
end
@ -1038,7 +1040,11 @@ Rails.application.routes.draw do
post 'wopi/files/:id', to: 'wopi#post_file_endpoint'
end
resources :gene_sequence_assets, only: %i(new create edit update)
resources :gene_sequence_assets, only: %i(new create edit update) do
member do
patch :rename
end
end
if Rails.env.development? || ENV['ENABLE_DESIGN_ELEMENTS'] == 'true'
resources :design_elements, only: %i(index) do