diff --git a/BrainPortal/app/controllers/userfiles_controller.rb b/BrainPortal/app/controllers/userfiles_controller.rb index 3aaf0ad38..9c4499831 100644 --- a/BrainPortal/app/controllers/userfiles_controller.rb +++ b/BrainPortal/app/controllers/userfiles_controller.rb @@ -39,7 +39,7 @@ class UserfilesController < ApplicationController around_action :permission_check, :only => [ :download, :update_multiple, :delete_files, - :create_collection, :change_provider, :quality_control, + :create_collection, :create_virtual_collection, :change_provider, :quality_control, :export_file_list ] @@ -1040,6 +1040,84 @@ def create_collection #:nodoc: end + #Create a collection from the selected files. + def create_virtual_collection #:nodoc: + filelist = params[:file_ids].uniq || [] + data_provider_id = params[:data_provider_id_for_collection] + collection_name = params[:collection_name] + file_group = current_assignable_group.id + + if data_provider_id.blank? + flash[:error] = "No data provider selected.\n" + redirect_to :action => :index + return + end + + # Handle collection name + if collection_name.blank? + suffix = Time.now.to_i + while Userfile.where(:user_id => current_user.id, :name => "VirtualCollection-#{suffix}").first.present? + suffix += 1 + end + collection_name = "VirtualCollection-#{suffix}" + end + + if ! Userfile.is_legal_filename?(collection_name) + flash[:error] = "Error: collection name '#{collection_name}' is not acceptable (illegal characters?)." + redirect_to :action => :index, :format => request.format.to_sym + return + end + + # Check if the collection name chosen by the user already exists for this user on the data_provider + if current_user.userfiles.exists?(:name => collection_name, :data_provider_id => data_provider_id) + flash[:error] = "Error: collection with name '#{collection_name}' already exists." + redirect_to :action => :index, :format => request.format.to_sym + return + end + + userfiles = Userfile.find_accessible_by_user(filelist, current_user, :access_requested => :read) + + # todo double check how 0 is possible, bad files should cause exception + if userfiles.count == 0 + flash[:error] = "Error: Inaccessible files selected." + redirect_to :action => :index, :format => request.format.to_sym + return + end + + collection = VirtualFileCollection.new( + :user_id => current_user.id, + :group_id => file_group, + :data_provider_id => data_provider_id, + :name => collection_name + ) + + collection.save! + collection.cache_prepare + coldir = collection.cache_full_path + Dir.mkdir(coldir) + + collection.set_virtual_file_collection(userfiles) + + # Save the content and DB model + + collection.sync_to_provider + collection.save + collection.set_size + + # Find the files + userfiles = Userfile + .find_all_accessible_by_user(current_user, :access_requested => :read) + .where(:id => filelist).all.to_a + + if userfiles.empty? + flash[:error] = "You need to select some files first." + redirect_to(:action => :index) + return + end + redirect_to(:controller => :userfiles, :action => :show, :id => collection.id) + + end + # Copy or move files to a new provider. def change_provider #:nodoc: diff --git a/BrainPortal/app/helpers/userfiles_helper.rb b/BrainPortal/app/helpers/userfiles_helper.rb index ad0eb5089..3432b3a47 100644 --- a/BrainPortal/app/helpers/userfiles_helper.rb +++ b/BrainPortal/app/helpers/userfiles_helper.rb @@ -110,11 +110,11 @@ def data_link(file_name, userfile, replace_div_id="sub_viewer_filecollection_cbr end elsif display_name =~ /\.html$/i # TODO: this will never happen if we ever create a HtmlFile model with at least one viewer link_to "#{display_name}", - stream_userfile_path(@userfile, :file_path => file_name, :disposition => 'inline'), + stream_userfile_path(userfile, :file_path => file_name, :disposition => 'inline'), :target => '_BLANK' else link_to h(display_name), - url_for(:action => :content, :content_loader => :collection_file, :arguments => file_name) + url_for(:action => :content, :id => userfile.id, :content_loader => :collection_file, :arguments => file_name) end end diff --git a/BrainPortal/app/views/userfiles/_dialogs.html.erb b/BrainPortal/app/views/userfiles/_dialogs.html.erb index 4e670c292..08a395e4c 100644 --- a/BrainPortal/app/views/userfiles/_dialogs.html.erb +++ b/BrainPortal/app/views/userfiles/_dialogs.html.erb @@ -349,6 +349,36 @@ +
+
+ + + + ⚠ Invalid! + +
+ + + <%= + data_provider_select('data_provider_id_for_collection', + { :data_providers => writable_dps }, + { + :id => 'co-dp', + :class => 'dlg-fld', + :'data-placeholder' => "A data provider..." + } + ) + %> +

+
+
+ +
+
  • +
    + New virtual collection + +
    +
  • + +
  • . +# +-%> + +<% limit = 500 %> +<% base_dir = base_directory rescue params[:base_directory] %> +<% base_dir = base_dir.presence || "." %> + +<% file_list ||= ( @userfile.list_linked_files(base_dir, [:regular, :directory, :link]) rescue [] ) %> + +<% if file_list.blank? %> + + "> + + (Empty) + + + +<% else %> + + <% for file in file_list[0,limit] %> + <% fname = file.name %> + <% fname = fname.delete_prefix(@userfile.name + '/') unless @userfile.id == file.userfile.id %> + <% if file.symbolic_type == :directory %> + <%= on_click_ajax_replace( { :element => "tr", + :url => url_for( + :id => file.userfile.id, + :action => :display, + :viewer => "directory_contents", + :viewer_userfile_class => "FileCollection", + :base_directory => ".", + :apply_div => "false" + ), + :position => "after", + :before => "Loading..." + }, + { :class => "#{cycle("list-odd", "list-even")}", + :id => file.name.gsub(/\W+/, "_") + } + ) do %> + <%= render :file => @viewer.partial_path(:plain_file_list_row), :locals => {:file => file} %> + <% end %> + <% else %> + "> + <%= render :file => @viewer.partial_path(:plain_file_list_row), :locals => {:file => file} %> + + <% end %> + <% end %> + + <% if file_list.size > limit %> + "> + + <%= (" " * 6 * file_list.first.depth).html_safe %> ... <%= image_tag "/images/lotsa_files_icon.png" %> <%= pluralize(file_list.size-limit, "more entry") %> + + + <% end %> + +<% end %> + + + diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection.html.erb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection.html.erb new file mode 100644 index 000000000..2c7292876 --- /dev/null +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection.html.erb @@ -0,0 +1,36 @@ + +<%- +# +# CBRAIN Project +# +# Copyright (C) 2008-2012 +# The Royal Institution for the Advancement of Learning +# McGill University +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +-%> + +<% + # This partial can be invoked directly as a viewer from the display action, + # or rendered as part of another piece of view code. As such it will + # accept a "base_directory" either as a params[] or as a local variable + base_dir = base_directory rescue params[:base_directory] +%> + +<%= render :file => VirtualFileCollection.view_path(:file_collection_form), + :locals => { :base_directory => base_dir } %> + +
    + diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection.json.erb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection.json.erb new file mode 100644 index 000000000..4cd7bc06f --- /dev/null +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection.json.erb @@ -0,0 +1,2 @@ +<%= @userfile.list_linked_files.to_json %> + diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection_form.html.erb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection_form.html.erb new file mode 100644 index 000000000..b9ad42ce8 --- /dev/null +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection_form.html.erb @@ -0,0 +1,57 @@ + +<%- +# +# CBRAIN Project +# +# Copyright (C) 2008-2012 +# The Royal Institution for the Advancement of Learning +# McGill University +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +-%> + +<% + # This partial requires one local variable: + # + # base_directory : the subdirectory inside the userfile where we start to render the directory content +%> + +<% if @userfile.num_files && @userfile.num_files > 0 %> + + + <%= form_for @userfile, :as => :userfile, + :url => { :controller => :userfiles, + :action => :extract_from_collection + }, + :html => { :method => :post, + :id => "userfile_edit_#{@userfile.id}_#{base_directory}" + } do |f| %> + + <%= ajax_element(display_userfile_path(@userfile, + :viewer => :file_collection_top_table, + :viewer_userfile_class => :VirtualFileCollection, + :base_directory => base_directory, + ), :class => "loading_message") do %> +
    + Loading... +
    + <% end %> + + + + <% end %> + +<% end %> + diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection_top_table.html.erb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection_top_table.html.erb new file mode 100644 index 000000000..a3114b88d --- /dev/null +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_file_collection_top_table.html.erb @@ -0,0 +1,46 @@ + +<%- +# +# CBRAIN Project +# +# Copyright (C) 2008-2012 +# The Royal Institution for the Advancement of Learning +# McGill University +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +-%> + +<% + # This partial requires one params variable: + # params[:base_directory] : a relative path to the subdirectory inside the userfile; we will list from that point. +%> + +<% + base_dir = params[:base_directory].presence || "" +%> + + + + + + + + <%= render :file => VirtualFileCollection.view_path(:directory_contents), + :locals => { :base_directory => base_dir } + %> +
    + + FileDLSize
    + diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_plain_file_list_row.html.erb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_plain_file_list_row.html.erb new file mode 100644 index 000000000..37049256e --- /dev/null +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/views/_plain_file_list_row.html.erb @@ -0,0 +1,87 @@ + +<%- +# +# CBRAIN Project +# +# Copyright (C) 2008-2012 +# The Royal Institution for the Advancement of Learning +# McGill University +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +-%> + +<% + # This partial receives one local variable: + # file : a file structure as returned by the FileCollection listing method + # Note that is contains a full relative path, starting with the @userfile's name itself. +%> + + + <% if @userfile.is_locally_synced? && file.symbolic_type == :regular %> + <%= check_box_tag("file_names[]", file.name, false, :class => "collection_checkbox", :id => nil) %> + <% end %> + + + +   + +
    + <% if file.symbolic_type == :directory %> + <%= image_tag "/images/folder_icon_solid.png" %> + <% else %> + <%= image_tag "/images/file_icon.png" %> + <% end %> + +   + <% bname = Pathname.new(file.name).basename.to_s %> + + <% fname = file.name %> + <% fname = fname.delete_prefix(@userfile.name + '/') if @userfile != file.userfile %> + + <% if file.size > 0 %> + <%= data_link fname, file.userfile %> + <% else %> + <%= bname %> + <% end %> + + <% if file.symbolic_type == :directory %> +   + Expand + + <% end %> +
    + + + + <% if file.symbolic_type == :regular && file.size > 0 && file.size < UserfilesController::MAX_DOWNLOAD_MEGABYTES.megabytes %> + + <% if @userfile.id != file.userfile.id %> + <% download_url = url_for(:action => :content, :content_loader => :collection_file, :id => file.userfile.id, :arguments => fname) %> + <% else %> + <% download_url = url_for(:action => :download, :id => file.userfile.id) %> + <% end %> + + <%= link_to download_url do %> + + <% end %> + + <% end %> + + + + <% if file.symbolic_type != :directory %> + <%= colored_pretty_size(file.size) %> + <% end %> + diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/virtual_file_collection.rb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/virtual_file_collection.rb new file mode 100644 index 000000000..c2e73cdce --- /dev/null +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/virtual_file_collection/virtual_file_collection.rb @@ -0,0 +1,246 @@ + +# +# CBRAIN Project +# +# Copyright (C) 2008-2024 +# The Royal Institution for the Advancement of Learning +# McGill University +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# This file collection is collection of other Userfiles or Collections +# It is implemented using soft links +# two level collections are forbidden to prevent recursion or other issues +class VirtualFileCollection < FileCollection + + Revision_info=CbrainFileRevision[__FILE__] #:nodoc: + + CSV_BASENAME = "_virtual_file_collection.cbcsv" + # todo. add .bidsignore file, otherwise bids validation. Or we can allow CBRAIN filenames starting with dot + + CBRAIN_ARCHIVE_CONTENT_BASENAME = nil + + + reset_viewers # we opted to ignore superclass viewers rather than adjust them + has_viewer :name => 'Virtual File Collection', :partial => :file_collection , :if => :is_locally_synced? + + def self.pretty_type #:nodoc: + "Virtual File Collection" + end + + + def set_size! + self.size, self.num_files = Rails.cache.fetch("VirtualFileCollection_#{self.id || "#{current_user.id}_#{self.data_provider_id}_#{self.name}"}#size", expires_in: 3.minutes) do + userfiles = self.get_userfiles + [userfiles.sum(&:size), userfiles.sum(&:num_files)] + end + self.assign_attributes(size: self.size , num_files: self.num_files) if self.id + true + end + + # Sync the VirtualFileCollection, with the files too + def sync_to_cache(deep=true) #:nodoc: + syncstat = self.local_sync_status(:refresh) + return true if syncstat && syncstat.status == 'InSync' + super() + if deep && ! self.archived? + self.sync_files + self.update_cache_symlinks + end + @cbfl = files= nil # flush internal cache + true + end + + # Invokes the local sync_to_cache with deep=false; this means the + # constitute FileCollection are not synchronized and symlinks not created. + # This method is used by FileCollection when archiving or unarchiving. + def sync_to_cache_for_archiving + result = sync_to_cache(false) + self.erase_cache_symlinks rescue nil + result + end + + # When syncing to the provider, we locally erase + # the symlinks, because they make no sense outside + # of the local Rails app. + # FIXME: this method has a slight race condition, + # after syncing to the provider we recreate the + # symlinks, but if another program tries to access + # them during that time they might not yet be there. + def sync_to_provider #:nodoc: + self.cache_writehandle do # when the block ends, it will trigger the provider upload + self.erase_cache_symlinks unless self.archived? + end + self.make_cache_symlinks unless self.archived? + true + end + + # Sets the set of FileCollections that constitute VirtualFileCollection. + # The CSV file inside the study will be created/updated, + # as well as all the symbolic links. The content + # is NOT synced to the provider side. + def set_virtual_file_collection(userfiles) + cb_error "Multi layer collections are not supported." if userfiles.any? { |f| f.is_a?(VirtualFileCollection) || f.is_a?(CivetVirtualStudy) } + + # Prepare CSV content + content = CbrainFileList.create_csv_file_from_userfiles(userfiles) + + # This optimize so we don't reload the content for making the symlinks + @cbfl = CbrainFileList.new + @cbfl.load_from_content(content) + @files = nil + + # Write CSV content to the interal CSV file + self.cache_prepare + Dir.mkdir(self.cache_full_path) unless Dir.exist?(self.cache_full_path) + File.write(csv_cache_full_path.to_s, content) + self.update_cache_symlinks + self.cache_is_newer + end + + # List linked files or directories, as if present directly + def list_linked_files(dir=:all, allowed_types = :regular) + if allowed_types.is_a? Array + types = allowed_types.dup + else + types = [allowed_types] + end + types.map!(&:to_sym) + types << :file if types.delete(:regular) + + # for combination of :top and :directory file type data are maid up, + # to avoid running file stats command which should not affect file browsing + # alternatively, new option(s) can be added to list_files/cache_collection_index, + # or new dir_info method + if (dir == :top || dir == '.') + cloned_files = self.list_files(:top, :link).cb_deep_clone # no altering the cache of list_files methods + userfiles_by_name = self.get_userfiles.index_by(&:name) + return cloned_files.filter_map do |file| + fname = file.name.split('/')[1] # gets basename + userfile = userfiles_by_name[fname] + if types.include?(:directory) && userfile.is_a?(FileCollection) + file.symbolic_type = :directory + file.userfile = userfile + # binding.pry + file + elsif types.include?(:file) && userfile.is_a?(SingleFile) + file = userfile.list_files.first.clone + file&.name = self.name + '/' + fname + file&.symbolic_type = :regular + file.userfile = userfile + # binding.pry + file + end + end + end + + userfiles = self.get_userfiles + + if dir.is_a? String + name, dir = dir.split '/' + dir |= '.' + userfiles = userfiles.select { |x| x.name == name } + end + + userfiles.map do |userfile| + userfile.list_files(dir, allowed_types).each do |f| + f.name = self.name + '/' + f.name + end + f.userfile = userfile + end.flatten + end + + # todo - remove unless needed for creation? + # def validate_componets + # ufiles = self.get_userfiles + # error "Nested Virtual Collection" if ufile.is_a?(VirtualFileCollection) || ufile.is_a?(CivetVirtualStudy) || ufile.type.lower.include?('virtual') + # end + + # Returns the files IDs + def get_ids + self.get_userfiles.map(&:id) + end + + # Returns the list of files in the internal CbrainFileList + # The list is cached internally and access control is applied + # based on the owner of the VirtualFileCollection. + def get_userfiles #:nodoc: + + if @cbfl.blank? + @cbfl = CbrainFileList.new + file_content = File.read(csv_cache_full_path.to_s) + @cbfl.load_from_content(file_content) + end + + @files ||= @cbfl.userfiles_accessible_by_user!(self.user).compact + file_names = @files.map(&:name) + dup_names = file_names.select { |name| file_names.count(name) >1 }.uniq + cb_error "Virtual file collection contains duplicate filenames #{dup_names.join(',')}" if dup_names.present? + @files.each do |f| + cb_error "Nested virtual file collections are not supported, remove file with id #{f.id}" if ( + f.is_a?(VirtualFileCollection) || + f.is_a?(CivetVirtualStudy) || + f.type.downcase.include?('virtual') + ) + end + end + + + + #==================================================================== + # Support methods, not part of this model's API. + #==================================================================== + + protected + + # Synchronize each file + def sync_files #:nodoc: + self.get_userfiles.each { |uf| uf.sync_to_cache } + end + + # Clean up ALL symbolic links + def erase_cache_symlinks #:nodoc: + Dir.chdir(self.cache_full_path) do + Dir.glob('*').each do |entry| + # FIXME how to only erase symlinks that points to a CBRAIN cache or local DP? + # Parsing the value of the symlink is tricky... + File.unlink(entry) if File.symlink?(entry) + end + end + end + + # This cleans up any old symbolic links, then recreates them. + # Note that this does not sync the files themselves. + def update_cache_symlinks #:nodoc: + self.erase_cache_symlinks + self.make_cache_symlinks + end + + # Create symbolic links in cache for each element of the virtual collection + # Note that this does not sync the files themselves. + def make_cache_symlinks #:nodoc: + self.get_userfiles.each do |uf| + link_value = uf.cache_full_path + link_path = self.cache_full_path + link_value.basename + File.unlink(link_path) if File.symlink?(link_path) && File.readlink(link_path) != link_value + File.symlink(link_value, link_path) unless File.exist?(link_path) + end + end + + def csv_cache_full_path #:nodoc: + self.cache_full_path + CSV_BASENAME + end + +end diff --git a/BrainPortal/config/routes.rb b/BrainPortal/config/routes.rb index d8aa1c02e..92bc2b666 100644 --- a/BrainPortal/config/routes.rb +++ b/BrainPortal/config/routes.rb @@ -149,6 +149,7 @@ post 'create_parent_child' delete 'delete_files' post 'create_collection' + post 'create_virtual_collection' put 'update_multiple' post 'change_provider' post 'compress' diff --git a/BrainPortal/public/javascripts/userfiles.js b/BrainPortal/public/javascripts/userfiles.js index e22b4046f..62b7a32a9 100644 --- a/BrainPortal/public/javascripts/userfiles.js +++ b/BrainPortal/public/javascripts/userfiles.js @@ -124,7 +124,8 @@ $(function() { rename: '/userfiles/:id', update: $('#prop-dialog > form').attr('action'), tags: '/tags/:id', - create_collection: $('#collection-dialog > form').attr('action') + create_collection: $('#collection-dialog > form').attr('action'), + create_virtual_collection: $('#virtual-collection-dialog > form').attr('action') }; /* Userfiles actions/operations */ @@ -386,6 +387,20 @@ $(function() { return defer(function () { uform.submit(); }).promise(); }, + /* + * Create a new VirtualFileCollection containing the currently selected files. + * The new collection's name and target data provider are to be specified + * in the HTML form argument +form. + */ + create_virtual_collection: function (form) { + var uform = userfiles.children('form'); + + setup_form(uform, urls.create_virtual_collection, 'POST', form); + clear_selection(true); + + return defer(function () { uform.submit(); }).promise(); + }, + /* * Tag-related operations; generic CRUD with parameters +id+ (tag ID) and * +data+ (tag attributes as a JS object) @@ -1097,6 +1112,40 @@ $(function() { .toggleClass('ui-state-disabled', !valid); }); + /* New virtual collection dialog */ + $('#virtual-collection-dialog') + .dialog('option', 'buttons', { + 'Cancel': function (event) { + $(this).trigger('close.uf'); + }, + 'Create': function (event) { + var dialog = $(this); + + dialog.trigger('close.uf'); + userfiles.create_virtual_collection(dialog.children('form')[0]); + } + }) + .unbind('open.uf.col-open') + .bind( 'open.uf.col-open', function () { + $(this).dialog('option', 'title', + 'New virtual collection - ' + formatted_selection() + ); + }) + .undelegate('#co-name', 'input.uf.co-name-check') + .delegate( '#co-name', 'input.uf.co-name-check', function () { + var valid = /^\w[\w~!@#%^&*()-+=:[\]{}|<>,.?]*$/.test($(this).val()); + + $('#co-invalid-name').css({ + visibility: valid ? 'hidden' : 'visible' + }); + + $('#virtual-collection-dialog') + .parent() + .find(':button:contains("Create")') + .prop('disabled', !valid) + .toggleClass('ui-state-disabled', !valid); + }); + /* Delete files confirmation dialog */ $('#delete-confirm') .unbind('open.uf.del-cfrm-open')