From 444b561060b8a71387c4221cc60bf305b259015d Mon Sep 17 00:00:00 2001 From: Martin Artnik Date: Tue, 17 Sep 2024 15:19:44 +0200 Subject: [PATCH] Implement file versions modal [SCI-11039] --- app/controllers/assets_controller.rb | 16 +++-- .../content/attachments/context_menu.vue | 17 ++++- .../vue/shared/file_versions_modal.vue | 66 +++++++++++++++++++ app/models/concerns/versioned_attachments.rb | 16 ++++- app/serializers/asset_serializer.rb | 3 +- config/locales/en.yml | 7 ++ config/routes.rb | 1 + 7 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 app/javascript/vue/shared/file_versions_modal.vue diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 383964890..314583de4 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -197,7 +197,7 @@ class AssetsController < ApplicationController return render_403 unless can_read_team?(@asset.team) @asset.last_modified_by = current_user - @asset.file.attach(io: params.require(:image), filename: orig_file_name) + @asset.attach_file_version(io: params.require(:image), filename: orig_file_name, current_user: current_user) @asset.save! create_edit_image_activity(@asset, current_user, :finish_editing) # release previous image space @@ -242,9 +242,10 @@ class AssetsController < ApplicationController # Asset validation asset = Asset.new(created_by: current_user, team: current_team) - asset.file.attach(io: StringIO.new, - filename: "#{params[:file_name]}.#{params[:file_type]}", - content_type: wopi_content_type(params[:file_type])) + asset.attach_file_version(io: StringIO.new, + filename: "#{params[:file_name]}.#{params[:file_type]}", + content_type: wopi_content_type(params[:file_type]), + current_user: current_user) unless asset.valid?(:wopi_file_creation) render json: { @@ -397,6 +398,13 @@ class AssetsController < ApplicationController render json: { checksum: @asset.file.blob.checksum } end + def versions + render( + json: [@asset.file.blob] + + @asset.previous_files.map(&:blob).sort_by { |b| -1 * b.metadata['version'].to_i } + ) + end + private def load_vars diff --git a/app/javascript/vue/shared/content/attachments/context_menu.vue b/app/javascript/vue/shared/content/attachments/context_menu.vue index bb17af66c..5f9a6a7bb 100644 --- a/app/javascript/vue/shared/content/attachments/context_menu.vue +++ b/app/javascript/vue/shared/content/attachments/context_menu.vue @@ -34,6 +34,7 @@ @duplicate="duplicate" @viewMode="changeViewMode" @move="showMoveModal" + @fileVersionsModal="fileVersionsModal = true" @menu-toggle="$emit('menu-toggle', $event)" > @@ -55,6 +56,11 @@ :targets_url="attachment.attributes.urls.move_targets" @confirm="moveAttachment($event)" @cancel="closeMoveModal" /> + @@ -65,6 +71,7 @@ import deleteAttachmentModal from './delete_modal.vue'; import MoveAssetModal from '../modal/move.vue'; import MoveMixin from './mixins/move.js'; import MenuDropdown from '../../menu_dropdown.vue'; +import FileVersionsModal from '../../file_versions_modal.vue'; import axios from '../../../../packs/custom_axios.js'; export default { @@ -73,6 +80,7 @@ export default { RenameAttachmentModal, deleteAttachmentModal, MoveAssetModal, + FileVersionsModal, MenuDropdown }, mixins: [MoveMixin], @@ -91,7 +99,8 @@ export default { return { viewModeOptions: ['inline', 'thumbnail', 'list'], deleteModal: false, - renameModal: false + renameModal: false, + fileVersionsModal: false }; }, computed: { @@ -136,6 +145,12 @@ export default { }); }); } + if (this.attachment.attributes.urls.versions) { + menu.push({ + text: this.i18n.t('assets.context_menu.versions'), + emit: 'fileVersionsModal' + }); + } return menu; } }, diff --git a/app/javascript/vue/shared/file_versions_modal.vue b/app/javascript/vue/shared/file_versions_modal.vue new file mode 100644 index 000000000..0aeb81b43 --- /dev/null +++ b/app/javascript/vue/shared/file_versions_modal.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/models/concerns/versioned_attachments.rb b/app/models/concerns/versioned_attachments.rb index 464ff1aff..6a32e6963 100644 --- a/app/models/concerns/versioned_attachments.rb +++ b/app/models/concerns/versioned_attachments.rb @@ -8,10 +8,22 @@ module VersionedAttachments has_one_attached name, dependent: :detach has_many_attached "previous_#{name.to_s.pluralize}", dependent: :detach - define_method "attach_#{name}_version" do |*args, **options| + define_method :"attach_#{name}_version" do |*args, **options| ActiveRecord::Base.transaction(requires_new: true) do - __send__("previous_#{name.to_s.pluralize}").attach(__send__(name).blob) if __send__(name).attached? + __send__(:"previous_#{name.to_s.pluralize}").attach(__send__(name).blob) if __send__(name).attached? __send__(name).attach(*args, **options) + + new_blob = __send__(name).blob + new_blob.metadata['created_by_id'] = last_modified_by_id + new_blob.save! + + # set version of current latest file if previous versions exist + next unless __send__(:"previous_#{name.to_s.pluralize}").any? + + new_version = + (__send__(:"previous_#{name.to_s.pluralize}").last.blob.metadata['version'] || 1) + 1 + new_blob.metadata['version'] = new_version + new_blob.save! end end end diff --git a/app/serializers/asset_serializer.rb b/app/serializers/asset_serializer.rb index 9817f448a..ba4fa56ef 100644 --- a/app/serializers/asset_serializer.rb +++ b/app/serializers/asset_serializer.rb @@ -138,7 +138,8 @@ class AssetSerializer < ActiveModel::Serializer load_asset: load_asset_path(object), asset_file: asset_file_url_path(object), marvin_js: marvin_js_asset_path(object), - marvin_js_icon: image_path('icon_small/marvinjs.svg') + marvin_js_icon: image_path('icon_small/marvinjs.svg'), + versions: (asset_versions_path(object) if attached) } user = scope[:user] || @instance_options[:user] if can_manage_asset?(user, object) diff --git a/config/locales/en.yml b/config/locales/en.yml index 4b046bb51..78daf6634 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4005,6 +4005,13 @@ en: inline_html: "Large" thumbnail_html: "Thumbnail" list_html: "List" + versions: "Versions" + file_versions_modal: + title: "Version history" + download: "Download" + restore: "Restore" + size: "Size" + restored_from_version: "restored from v%{version}" rename_modal: title: "Rename file" min_length_error: "File name must be at least 1 character long." diff --git a/config/routes.rb b/config/routes.rb index 3c2d9a9ae..3358bd239 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -861,6 +861,7 @@ Rails.application.routes.draw do get 'files/:id/edit', to: 'assets#edit', as: 'edit_asset' get 'files/:id/checksum', to: 'assets#checksum', as: 'asset_checksum' get 'files/:id/show', to: 'assets#show', as: 'asset_show' + get 'files/:id/versions', to: 'assets#versions', as: 'asset_versions' patch 'files/:id/toggle_view_mode', to: 'assets#toggle_view_mode', as: 'toggle_view_mode' get 'files/:id/load_asset', to: 'assets#load_asset', as: 'load_asset' post 'files/:id/update_image', to: 'assets#update_image',