From ed7ea52372a8065643716ee031a4c3d689e33af0 Mon Sep 17 00:00:00 2001 From: Daniel Rempe Date: Mon, 30 Jan 2017 15:30:38 +0100 Subject: [PATCH] file_field_design_update-2113931-104 --- core/misc/icons/bebebe/dropzone-new.svg | 13 ++ core/misc/icons/ee0000/ex.svg | 1 + core/modules/file/file.module | 27 +++ core/modules/file/src/Element/ManagedFile.php | 6 +- .../src/Plugin/Field/FieldWidget/ImageWidget.php | 4 +- .../Update/UpdatePathRC1TestBaseFilledTest.php | 3 +- .../Tests/Update/UpdatePathTestBaseFilledTest.php | 3 +- core/themes/seven/css/components/dropzone.css | 206 +++++++++++++++++++++ core/themes/seven/js/dropzone.js | 89 +++++++++ core/themes/seven/seven.libraries.yml | 5 + core/themes/seven/seven.theme | 7 + .../templates/form-element--managed-file.html.twig | 105 +++++++++++ 12 files changed, 463 insertions(+), 6 deletions(-) create mode 100644 core/misc/icons/bebebe/dropzone-new.svg create mode 100644 core/misc/icons/ee0000/ex.svg create mode 100644 core/themes/seven/css/components/dropzone.css create mode 100644 core/themes/seven/js/dropzone.js create mode 100644 core/themes/seven/templates/form-element--managed-file.html.twig diff --git a/core/misc/icons/bebebe/dropzone-new.svg b/core/misc/icons/bebebe/dropzone-new.svg new file mode 100644 index 0000000..b3b8e49 --- /dev/null +++ b/core/misc/icons/bebebe/dropzone-new.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/misc/icons/ee0000/ex.svg b/core/misc/icons/ee0000/ex.svg new file mode 100644 index 0000000..6b45a1d --- /dev/null +++ b/core/misc/icons/ee0000/ex.svg @@ -0,0 +1 @@ + diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 687a62c..bc41504 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1263,6 +1263,8 @@ function template_preprocess_file_link(&$variables) { // Use the description as the link text if available. if (empty($variables['description'])) { $link_text = $file_entity->getFilename(); + $link_text = file_shorten_filename($link_text); + $options['attributes']['title'] = $file_entity->getFilename(); } else { $link_text = $variables['description']; @@ -1286,6 +1288,31 @@ function template_preprocess_file_link(&$variables) { } /** + * Shortens a file name. + * + * @param string $filename + * The filename to shorten. + * @param int $limit + * (optional) The maximum length of the filename (without the extension) that + * will not be shortened. + * + * @return string + * The shortened filename, or the original filename if it's less than $limit + * characters long. + */ +function file_shorten_filename($filename, $limit = 10) { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + + $name = pathinfo($filename, PATHINFO_FILENAME); + if (strlen($name) > $limit) { + return substr($name, 0, $limit) . '...' . substr($name, -4) . '.' . $ext; + } + else { + return $filename; + } +} + +/** * Gets a class for the icon for a MIME type. * * @param string $mime_type diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php index f465b18..1f88501 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -182,10 +182,11 @@ public static function uploadAjaxCallback(&$form, FormStateInterface &$form_stat $current_file_count = $form_state->get('file_upload_delta_initial'); if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) { $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content'; + $form[$current_file_count]['#attributes']['class'][] = 'managed-file'; } // Otherwise just add the new content class on a placeholder. else { - $form['#suffix'] .= ''; + $form['#suffix'] .= ''; } $status_messages = ['#type' => 'status_messages']; @@ -239,7 +240,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st '#name' => $parents_prefix . '_upload_button', '#type' => 'submit', '#value' => t('Upload'), - '#attributes' => ['class' => ['js-hide']], + '#attributes' => ['class' => ['upload-button', 'js-hide']], '#validate' => [], '#submit' => ['file_managed_file_submit'], '#limit_validation_errors' => [$element['#parents']], @@ -256,6 +257,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st '#name' => $parents_prefix . '_remove_button', '#type' => 'submit', '#value' => $element['#multiple'] ? t('Remove selected') : t('Remove'), + '#attributes' => ['class' => ['remove-button']], '#validate' => [], '#submit' => ['file_managed_file_submit'], '#limit_validation_errors' => [$element['#parents']], diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php index f881419..03032f3 100644 --- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php +++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php @@ -227,7 +227,7 @@ public static function process($element, FormStateInterface $form_state, $form) '#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'), // @see https://www.drupal.org/node/465106#alt-text '#maxlength' => 512, - '#weight' => -12, + '#weight' => 1, '#access' => (bool) $item['fids'] && $element['#alt_field'], '#required' => $element['#alt_field_required'], '#element_validate' => $element['#alt_field_required'] == 1 ? array(array(get_called_class(), 'validateRequiredFields')) : array(), @@ -238,7 +238,7 @@ public static function process($element, FormStateInterface $form_state, $form) '#default_value' => isset($item['title']) ? $item['title'] : '', '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'), '#maxlength' => 1024, - '#weight' => -11, + '#weight' => 2, '#access' => (bool) $item['fids'] && $element['#title_field'], '#required' => $element['#title_field_required'], '#element_validate' => $element['#title_field_required'] == 1 ? array(array(get_called_class(), 'validateRequiredFields')) : array(), diff --git a/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php b/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php index eb95fce..e65a613 100644 --- a/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php +++ b/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php @@ -112,7 +112,8 @@ public function testUpdatedSite() { $this->assertRaw('+31612345679'); $this->assertText('Test Article - New title'); $this->assertText('test.txt'); - $this->assertText('druplicon.small'); + // The full-length file name is druplicon.small_.png. + $this->assertText('druplicon....all_.png'); $this->assertRaw('General discussion'); $this->assertText('Test Article - New title'); $this->assertText('Test 1'); diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php b/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php index 6edbfe3..a071fae 100644 --- a/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php +++ b/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php @@ -112,7 +112,8 @@ public function testUpdatedSite() { $this->assertRaw('+31612345679'); $this->assertText('Test Article - New title'); $this->assertText('test.txt'); - $this->assertText('druplicon.small'); + // The full-length file name is druplicon.small_.png. + $this->assertText('druplicon....all_.png'); $this->assertRaw('General discussion'); $this->assertText('Test Article - New title'); $this->assertText('Test 1'); diff --git a/core/themes/seven/css/components/dropzone.css b/core/themes/seven/css/components/dropzone.css new file mode 100644 index 0000000..5fed73d --- /dev/null +++ b/core/themes/seven/css/components/dropzone.css @@ -0,0 +1,206 @@ +/** + * Field Widgets. + */ +.field--widget-image-image .remove-button, +.field--widget-file-generic .remove-button { + background: url(../../../../misc/icons/787878/ex.svg) 80px 4px no-repeat; + border: 0; + float: right; + text-align: left; + text-indent: -9999px; + position: absolute; + top: 10px; + right: 0; + width: 95px; + text-shadow: 0; + font-weight: normal; +} + +.field--widget-image-image .remove-button:focus, +.field--widget-file-generic .remove-button:focus { + box-shadow: none; + outline: none; +} + +.field--widget-image-image .remove-button:hover, +.field--widget-file-generic .remove-button:hover { + background: url(../../../../misc/icons/ee0000/ex.svg) 80px 4px no-repeat; + border: 0; + box-shadow: none; + text-align: left; + text-indent: 0; + color: #ee0000; +} + +.field--widget-image-image, +.field--widget-file-generic, +.field--widget-image-image td, +.field--widget-file-generic td { + position: relative; +} + +.field--widget-image-image td .remove-button, +.field--widget-file-generic td .remove-button { + right: 10px; +} + +.field--widget-image-image .dropzone-trigger { + display: none; +} + +.field--widget-image-image .remove-button + .ajax-progress, +.field--widget-file-generic .remove-button + .ajax-progress { + position: absolute; + right: 10px; + top: 35px; +} + +.field--widget-image-image td .remove-button + .ajax-progress, +.field--widget-file-generic td .remove-button + .ajax-progress { + position: absolute; + right: 85px; + top: 13px; +} + +.field--widget-image-image .image-preview { + position: relative; +} + +.field--widget-image-image td .image-widget-data { + float: none; + padding-left: 27px; +} + +.field--widget-image-image .image-preview img { + border: 0; + max-width: 80px; + height: auto; + padding-right: 10px; +} + +.field--widget-image-image td .image-widget-data .form-type-textfield:before, +.field--widget-file-generic td .form-managed-file .form-type-textfield:before{ + content: ''; + clear: both; + display: table; +} + +.field--widget-file-generic td .form-managed-file .file { + margin-bottom: 10px; +} + +.field--widget-file-generic td .form-managed-file .form-item { + padding-left: 27px; +} + + /** + * Dropzone. + */ +.dropzone-wrapper { + width: 100%; + border: 1px solid #bfbfbf; + border-radius: 2px; + background: #fcfcfa; + box-sizing: border-box; +} + +.dropzone { + position: relative; + width: 100%; + height: 100%; + min-height: 100px; + display: table; +} + +.dropzone > div { + display: table-cell; + vertical-align: middle; + box-sizing: border-box; +} + +span.ajax-new-content.managed-file { + display: block; +} + +.dropzone .has-file, +.dropzone .form-managed-file { + padding: 10px; +} + +.dropzone.empty .form-managed-file { + padding: 0; +} + +/* hide error message*/ +.dropzone .messages--error { display: none; } + +/* form file */ +.dropzone .js-form-file { + position: absolute; + left: 0; + top: 0; + cursor: pointer; + min-height: 100px; + width: 100px; + display: inline-block; + opacity: 0; +} + +.dropzone .file { + background: none; + padding: 0; +} + +.dropzone .dropzone-trigger { + position: relative; + text-align: center; + min-height: 100px; + height: 100%; + width: 100px; + border-right: 1px solid #bfbfbf; + background: url('../../../../misc/icons/bebebe/dropzone-new.svg') -200px center no-repeat; +} + +.dropzone.empty .dropzone-trigger { + display: table-cell; + background: url('../../../../misc/icons/bebebe/dropzone-new.svg') 0 center no-repeat; +} + +.dropzone .dropzone-trigger.is-hovering { + background: url('../../../../misc/icons/bebebe/dropzone-new.svg') -100px center no-repeat; +} + +.dropzone .dropzone-trigger.is-complete { + background: url('../../../../misc/icons/bebebe/dropzone-new.svg') -200px center no-repeat; +} + +.field--type-image .dropzone .dropzone-trigger.is-complete, +.field--type-file .dropzone .dropzone-trigger.is-complete { + background: url('../../../../misc/icons/bebebe/dropzone-new.svg') -200px center no-repeat; +} + +.dropzone-description { + padding: 10px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; +} + +.dropzone-preview { + border: 1px solid #ccc; + border-radius: 2px; + background: #fdfdfc; + margin-bottom: 1em; padding: 0.5em 1em; +} + +.dropzone-buttons { + padding: 0; + margin: 0; + list-style: none; +} + +.dropzone-buttons li { + display: inline-block; + margin-right: 10px; +} diff --git a/core/themes/seven/js/dropzone.js b/core/themes/seven/js/dropzone.js new file mode 100644 index 0000000..ad9f5f2 --- /dev/null +++ b/core/themes/seven/js/dropzone.js @@ -0,0 +1,89 @@ +(function ($, Drupal) { + + 'use strict'; + + Drupal.behaviors.dropzone = { + attach: function (context, settings) { + var formFile = $(context).find('.js-form-file'); + + formFile.on('dragover mouseenter', function () { + $(this).closest('.dropzone').find('.dropzone-trigger').addClass('is-hovering'); + }); + formFile.on('dragleave mouseleave', function () { + $(this).closest('.dropzone').find('.dropzone-trigger').removeClass('is-hovering'); + }); + + Drupal.ajax.instances + .filter(function (instance) { + if (instance && instance.element) { + var element = $(instance.element); + } + else { + return; + } + return element.length > 0 && element.hasClass('upload-button') && element.once('magic').length > 0; + }) + .forEach(function (instance) { + var element = $(instance.element); + element.closest('.dropzone').find('.js-upload-image').on('click', function() { + $(this).closest('.js-form-item').find('input[type="file"]').trigger('click'); + }); + + instance.success = function (response, status) { + $(this.element).prop('disabled', false); + + // Save element's ancestors tree so if the element is removed from the dom + // we can try to refocus one of its parents. Using addBack reverse the + // result array, meaning that index 0 is the highest parent in the hierarchy + // in this situation it is usually a
element. + var elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray(); + + // Track if any command is altering the focus so we can avoid changing the + // focus set by the Ajax command. + var focusChanged = false; + for (var i in response) { + if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) { + this.commands[response[i].command](this, response[i], status); + if (response[i].command === 'invoke' && response[i].method === 'focus') { + focusChanged = true; + } + } + } + + // If the focus hasn't be changed by the ajax commands, try to refocus the + // triggering element or one of its parents if that element does not exist + // anymore. + if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) { + var target = false; + + for (var n = elementParents.length - 1; !target && n > 0; n--) { + target = document.querySelector('[data-drupal-selector="' + elementParents[n].getAttribute('data-drupal-selector') + '"]'); + } + + if (target) { + $(target).trigger('focus'); + } + } + + if (target && $(this.element).hasClass('upload-button')) { + $(target).find('.dropzone-trigger').addClass('is-complete').next('.js-form-managed-file').addClass('has-file'); + } + + // Reattach behaviors, if they were detached in beforeSerialize(). The + // attachBehaviors() called on the new content from processing the response + // commands is not sufficient, because behaviors from the entire form need + // to be reattached. + if (this.$form) { + var settings = this.settings || drupalSettings; + Drupal.attachBehaviors(this.$form.get(0), settings); + } + + // Remove any response-specific settings so they don't get used on the next + // call by mistake. + this.settings = null; + }; + }); + } + }; + +})(jQuery, Drupal); diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml index e68b75a..220ec58 100644 --- a/core/themes/seven/seven.libraries.yml +++ b/core/themes/seven/seven.libraries.yml @@ -13,6 +13,7 @@ global-styling: css/components/colors.css: {} css/components/messages.css: {} css/components/dropbutton.component.css: {} + css/components/dropzone.css: {} css/components/entity-meta.css: {} css/components/field-ui.css: {} css/components/form.css: {} @@ -32,7 +33,11 @@ global-styling: css/components/views-ui.css: {} layout: css/layout/layout.css: {} + js: + js/dropzone.js: {} dependencies: + - core/drupal.ajax + - core/drupal.progress - system/admin node-form: diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index 64ee724..3824636 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -186,3 +186,10 @@ function seven_form_node_form_alter(&$form, FormStateInterface $form_state) { $form['revision_information']['#type'] = 'container'; $form['revision_information']['#group'] = 'meta'; } + +/** + * Implements hook_theme_suggestions_form_element_alter(). + */ +function seven_theme_suggestions_form_element_alter(&$suggestions, $variables) { + $suggestions[] = 'form_element__' . $variables['element']['#type']; +} diff --git a/core/themes/seven/templates/form-element--managed-file.html.twig b/core/themes/seven/templates/form-element--managed-file.html.twig new file mode 100644 index 0000000..9ab918e --- /dev/null +++ b/core/themes/seven/templates/form-element--managed-file.html.twig @@ -0,0 +1,105 @@ +{# +/** + * @file + * Theme override for a managed file form element. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - errors: (optional) Any errors for this form element, may not be set. + * - prefix: (optional) The form element prefix, may not be set. + * - suffix: (optional) The form element suffix, may not be set. + * - required: The required marker, or empty if the associated form element is + * not required. + * - type: The type of the element. + * - name: The name of the element. + * - label: A rendered label element. + * - label_display: Label display setting. It can have these values: + * - before: The label is output before the element. This is the default. + * The label includes the #title and the required marker, if #required. + * - after: The label is output after the element. For example, this is used + * for radio and checkbox #type elements. If the #title is empty but the + * field is #required, the label will contain only the required marker. + * - invisible: Labels are critical for screen readers to enable them to + * properly navigate through forms but can be visually distracting. This + * property hides the label for everyone except screen readers. + * - attribute: Set the title attribute on the element to create a tooltip but + * output no label element. This is supported only for checkboxes and radios + * in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement(). + * It is used where a visual label is not needed, such as a table of + * checkboxes where the row and column provide the context. The tooltip will + * include the title and required marker. + * - description: (optional) A list of description properties containing: + * - content: A description of the form element, may not be set. + * - attributes: (optional) A list of HTML attributes to apply to the + * description content wrapper. Will only be set when description is set. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element. This is the default + * value. + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - disabled: True if the element is disabled. + * - title_display: Title display setting. + * + * @see template_preprocess_form_element() + */ +#} +{% +set classes = [ +'js-form-item', +'form-item', +'js-form-type-' ~ type|clean_class, +'form-type-' ~ type|clean_class, +'js-form-item-' ~ name|clean_class, +'form-item-' ~ name|clean_class, +'dropzone-wrapper', +'clearfix', +disabled == 'disabled' ? 'form-disabled', +errors ? 'form-item--error', +] +%} +{% +set description_classes = [ +'dropzone-description', +description_display == 'invisible' ? 'visually-hidden', +] +%} + + + {% if prefix is not empty %} + {{ prefix }} + {% endif %} +
+
+ + {{ children }} + + {% if suffix is not empty %} + {{ suffix }} + {% endif %} + {% if label_display == 'after' %} + {{ label }} + {% endif %} + + {% if description.content %} + +
    +
  • + + {% set count = element['#multiple'] ? 1 : 2 %} + {% trans %} + Add Files + {% plural count %} + Add File + {% endtrans %} + +
  • + {#
  • {{ 'Browse Library'|t }}
  • #} +
+ {{ description.content }} +
+ {% endif %} + + + + -- 2.9.3