mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-11 07:34:41 +08:00
Enable renaming attached files [SCI-10256]
This commit is contained in:
parent
5643f8fd66
commit
7ac7ebd32d
17 changed files with 268 additions and 4 deletions
|
@ -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
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
:attachment="attachment"
|
||||
@attachment:viewMode="updateViewMode"
|
||||
@attachment:delete="deleteAttachment"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
@attachment:delete="deleteAttachment"
|
||||
@attachment:moved="attachmentMoved"
|
||||
@attachment:uploaded="reloadAttachments"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
90
app/javascript/vue/shared/content/modal/rename_modal.vue
Normal file
90
app/javascript/vue/shared/content/modal/rename_modal.vue
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue