Implement file versions modal [SCI-11039]

This commit is contained in:
Martin Artnik 2024-09-17 15:19:44 +02:00
parent 157a23d025
commit 444b561060
7 changed files with 118 additions and 8 deletions

View file

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

View file

@ -34,6 +34,7 @@
@duplicate="duplicate"
@viewMode="changeViewMode"
@move="showMoveModal"
@fileVersionsModal="fileVersionsModal = true"
@menu-toggle="$emit('menu-toggle', $event)"
></MenuDropdown>
<Teleport to="body">
@ -55,6 +56,11 @@
:targets_url="attachment.attributes.urls.move_targets"
@confirm="moveAttachment($event)" @cancel="closeMoveModal"
/>
<FileVersionsModal
v-if="fileVersionsModal"
:url="attachment.attributes.urls.versions"
@close="fileVersionsModal = false"
/>
</Teleport>
</div>
</template>
@ -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;
}
},

View file

@ -0,0 +1,66 @@
<template>
<div ref="modal" @keydown.esc="close" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button @click="close" 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.file_versions_modal.title") }}
</h4>
</div>
<div class="modal-body">
<div v-if="fileVersions" v-for="fileVersion in fileVersions" :key="fileVersion.id">
<div class="flex w-full border border-sn-light-grey rounded mb-1.5 p-1.5 items-center">
<div class="basis-3/4">
<div class="mb-1.5">
<span class="bg-sn-grey-300 me-2 px-2 py-0.5 rounded">v{{ fileVersion.attributes.version }}</span>
<a :href="fileVersion.attributes.url" target="_blank">{{ fileVersion.attributes.filename }}</a>
<small class="inline-block" v-if="fileVersion.attributes.restored_from_version">
({{ i18n.t("assets.file_versions_modal.restored_from_version", { version: fileVersion.attributes.restored_from_version }) }})
</small>
</div>
<div class="flex text-xs text-sn-grey justify-start">
<div class="mr-3">{{ fileVersion.attributes.created_at }}</div>
<div class="mr-3">{{ fileVersion.attributes.created_by.full_name }}</div>
<div>{{ (fileVersion.attributes.byte_size/1024).toFixed(1) }}KB</div>
</div>
</div>
<div class="basis-1/4 flex justify-end">
<a class="btn btn-icon p-0" :href="fileVersion.attributes.url" target="_blank"><i class="sn-icon sn-icon-export"></i></a>
<a class="btn btn-icon p-0 mx-3"><i class="sn-icon sn-icon-restore"></i></a>
</div>
</div>
</div>
<div v-else class="sci-loader"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import modalMixin from './modal_mixin';
import axios from '../../packs/custom_axios';
export default {
name: 'FileVersionsModal',
props: {
url: {
type: String,
required: true
}
},
mixins: [modalMixin],
data() {
return {
fileVersions: null
};
},
created() {
axios.get(this.url).then((response) => {
this.fileVersions = response.data.data;
});
}
};
</script>

View file

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

View file

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

View file

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

View file

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