Added support for HD preview images for styles
authorAlexander Ebert <ebert@woltlab.com>
Sun, 29 Oct 2017 17:26:38 +0000 (18:26 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Sun, 29 Oct 2017 17:26:38 +0000 (18:26 +0100)
Closes #2459

13 files changed:
com.woltlab.wcf/defaultStyle.tar
com.woltlab.wcf/templates/styleChooser.tpl
wcfsetup/install/files/acp/templates/styleAdd.tpl
wcfsetup/install/files/acp/templates/styleList.tpl
wcfsetup/install/files/images/stylePreview@2x.png [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Image/Upload.js
wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php
wcfsetup/install/files/lib/data/style/Style.class.php
wcfsetup/install/files/lib/data/style/StyleAction.class.php
wcfsetup/install/files/lib/data/style/StyleEditor.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 7e13802b7afdc6c87886c41a17c92b881da1c75d..a401dd01b6a8e82f2acf7a7a21cf5a461d5cdbb9 100644 (file)
Binary files a/com.woltlab.wcf/defaultStyle.tar and b/com.woltlab.wcf/defaultStyle.tar differ
index 1d415b3ee86bd4b8dcf6691e002fe03ec5314a8f..e9b0fb0eab2b4c0f81292050078ed9e47c382e46 100644 (file)
@@ -3,7 +3,7 @@
                <li data-style-id="{@$style->styleID}">
                        <div class="box64">
                                <span>
-                                       <img src="{@$style->getPreviewImage()}" alt="">
+                                       <img src="{@$style->getPreviewImage()}" srcset="{@$style->getPreviewImage2x()} 2x" height="64" alt="">
                                </span>
                                <div class="details">
                                        <div class="containerHeadline">
index faf10f700598f4620462425b0fc0ceb2beb0d186..53a82c6fe4cf5cda065a615c87e9a743ce699c83 100644 (file)
@@ -13,7 +13,8 @@
                });
                
                {if $action == 'edit'}new AcpUiStyleFaviconUpload({@$style->styleID});{/if}
-               new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}');
+               new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}', false);
+               new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}', true);
                
                new UiToggleInput('input[name="useGoogleFont"]', {
                        show: ['#wcfFontFamilyGoogleContainer']
                                                <small>{lang}wcf.acp.style.image.description{/lang}</small>
                                        </dd>
                                </dl>
+                               <dl{if $errorField == 'image'} class="formError"{/if}>
+                                       <dt><label for="image2x">{lang}wcf.acp.style.image2x{/lang}</label></dt>
+                                       <dd>
+                                               <div class="selectedImagePreview">
+                                                       <img src="{if $action == 'add'}{@$__wcf->getPath()}images/stylePreview@2x.png{else}{@$style->getPreviewImage2x()}{/if}" alt="" id="styleImage2x">
+                                               </div>
+                                               <div id="uploadImage2x"></div>
+                                               <small>{lang}wcf.acp.style.image2x.description{/lang}</small>
+                                       </dd>
+                               </dl>
                                {if $availableTemplateGroups|count}
                                        <dl{if $errorField == 'templateGroupID'} class="formError"{/if}>
                                                <dt><label for="templateGroupID">{lang}wcf.acp.style.templateGroupID{/lang}</label></dt>
index 04d1681b9c3424e37d9b8dbc06005a9c31b6e96a..0f3c6dc8a574813998e49722695baffc9991190a 100644 (file)
@@ -34,7 +34,9 @@
                {foreach from=$objects item=style}
                        <li>
                                <div class="box64">
-                                       <span><img src="{@$style->getPreviewImage()}" alt=""></span>
+                                       <span>
+                                               <img src="{@$style->getPreviewImage()}" srcset="{@$style->getPreviewImage2x()} 2x" height="64" alt="">
+                                       </span>
                                        <div class="details">
                                                <div class="containerHeadline">
                                                        <h3><a href="{link controller='StyleEdit' id=$style->styleID}{/link}">{$style->styleName}</a></h3>
diff --git a/wcfsetup/install/files/images/stylePreview@2x.png b/wcfsetup/install/files/images/stylePreview@2x.png
new file mode 100644 (file)
index 0000000..5ad3fc3
Binary files /dev/null and b/wcfsetup/install/files/images/stylePreview@2x.png differ
index 1347f74b03e15290c86fac0f1efa0bcd3fc51aaa..0170602a512c141fef0b255abb8e964410579834 100644 (file)
@@ -12,11 +12,12 @@ define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], functi
        /**
         * @constructor
         */
-       function AcpUiStyleImageUpload(styleId, tmpHash) {
+       function AcpUiStyleImageUpload(styleId, tmpHash, is2x) {
+               this._is2x = (is2x === true);
                this._styleId = ~~styleId;
                this._tmpHash = tmpHash;
                
-               Upload.call(this, 'uploadImage', 'styleImage', {
+               Upload.call(this, 'uploadImage' + (this._is2x ? '2x' : ''), 'styleImage' + (this._is2x ? '2x' : ''), {
                        className: 'wcf\\data\\style\\StyleAction'
                });
        }
@@ -33,6 +34,7 @@ define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], functi
                 */
                _getParameters: function() {
                        return {
+                               is2x: this._is2x,
                                styleId: this._styleId,
                                tmpHash: this._tmpHash
                        };
index 9e8c98f85eec987424d7857efc2fa7e0b8406547..f479c2b5a4de42cf4da827743fdd9ebb59b7bd4d 100644 (file)
@@ -4,7 +4,6 @@ use wcf\data\style\Style;
 use wcf\data\style\StyleAction;
 use wcf\form\AbstractForm;
 use wcf\system\exception\IllegalLinkException;
-use wcf\system\exception\UserInputException;
 use wcf\system\language\I18nHandler;
 use wcf\system\WCF;
 
@@ -26,7 +25,7 @@ class StyleEditForm extends StyleAddForm {
         * style object
         * @var Style
         */
-       public $style = null;
+       public $style;
        
        /**
         * style id
index 99e8791b7a38d6cc1a6ae85a72a3a0eb8cb30585..ecfd258b1fc45b8b402069bb1f4ba3f95e62cedf 100644 (file)
@@ -22,6 +22,7 @@ use wcf\system\WCF;
  * @property-read      string          $styleVersion           version number of the style
  * @property-read      string          $styleDate              date when the used version of the style has been published
  * @property-read      string          $image                  link or path (relative to `WCF_DIR`) to the preview image of the style
+ * @property-read      string          $image2x                link or path (relative to `WCF_DIR`) to the preview image of the style (2x version)
  * @property-read      string          $copyright              copyright text of the style
  * @property-read      string          $license                name of the style's license 
  * @property-read      string          $authorName             name(s) of the style's author(s)
@@ -136,6 +137,19 @@ class Style extends DatabaseObject {
                return WCF::getPath().'images/stylePreview.png';
        }
        
+       /**
+        * Returns the style preview image path (2x version).
+        * 
+        * @return      string
+        */
+       public function getPreviewImage2x() {
+               if ($this->image2x && file_exists(WCF_DIR.'images/'.$this->image2x)) {
+                       return WCF::getPath().'images/'.$this->image2x;
+               }
+               
+               return WCF::getPath().'images/stylePreview@2x.png';
+       }
+       
        public function getFaviconAppleTouchIcon() {
                return $this->getFaviconPath('apple-touch-icon.png');
        }
index 284f680f3f23260de661c88c26318b1749354b17..513917105f8a996bbd2258678ed0988bd9125ef7 100644 (file)
@@ -220,30 +220,47 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                        return;
                }
                
-               $fileExtension = WCF::getSession()->getVar('stylePreview-'.$this->parameters['tmpHash']);
-               if ($fileExtension !== null) {
-                       $oldFilename = WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$fileExtension;
-                       if (file_exists($oldFilename)) {
-                               $filename = 'stylePreview-'.$style->styleID.'.'.$fileExtension;
-                               if (@rename($oldFilename, WCF_DIR.'images/'.$filename)) {
-                                       // delete old file if it has a different file extension
-                                       if ($style->image != $filename) {
-                                               @unlink(WCF_DIR.'images/'.$style->image);
-                                               
-                                               // update filename in database
-                                               $sql = "UPDATE  wcf".WCF_N."_style
-                                                       SET     image = ?
-                                                       WHERE   styleID = ?";
-                                               $statement = WCF::getDB()->prepareStatement($sql);
-                                               $statement->execute([
-                                                       $filename,
-                                                       $style->styleID
-                                               ]);
+               foreach (['', '@2x'] as $type) {
+                       $fileExtension = WCF::getSession()->getVar('stylePreview-' . $this->parameters['tmpHash'] . $type);
+                       if ($fileExtension !== null) {
+                               $oldFilename = WCF_DIR . 'images/stylePreview-' . $this->parameters['tmpHash'] . $type . '.' . $fileExtension;
+                               if (file_exists($oldFilename)) {
+                                       $filename = 'stylePreview-' . $style->styleID . $type . '.' . $fileExtension;
+                                       if (@rename($oldFilename, WCF_DIR . 'images/' . $filename)) {
+                                               // delete old file if it has a different file extension
+                                               if ($type === '') {
+                                                       if ($style->image != $filename) {
+                                                               @unlink(WCF_DIR . 'images/' . $style->image);
+                                                               
+                                                               // update filename in database
+                                                               $sql = "UPDATE  wcf" . WCF_N . "_style
+                                                                       SET     image = ?
+                                                                       WHERE   styleID = ?";
+                                                               $statement = WCF::getDB()->prepareStatement($sql);
+                                                               $statement->execute([
+                                                                       $filename, $style->styleID
+                                                               ]);
+                                                       }
+                                               }
+                                               else {
+                                                       if ($style->image2x != $filename) {
+                                                               @unlink(WCF_DIR . 'images/' . $style->image2x);
+                                                               
+                                                               // update filename in database
+                                                               $sql = "UPDATE  wcf" . WCF_N . "_style
+                                                                       SET     image2x = ?
+                                                                       WHERE   styleID = ?";
+                                                               $statement = WCF::getDB()->prepareStatement($sql);
+                                                               $statement->execute([
+                                                                       $filename, $style->styleID
+                                                               ]);
+                                                       }
+                                               }
+                                       }
+                                       else {
+                                               // remove temp file
+                                               @unlink($oldFilename);
                                        }
-                               }
-                               else {
-                                       // remove temp file
-                                       @unlink($oldFilename);
                                }
                        }
                }
@@ -340,6 +357,7 @@ BROWSERCONFIG;
                        throw new PermissionDeniedException();
                }
                
+               $this->readBoolean('is2x', true);
                $this->readString('tmpHash');
                $this->readInteger('styleID', true);
                
@@ -373,6 +391,8 @@ BROWSERCONFIG;
                $files = $this->parameters['__files']->getFiles();
                $file = $files[0];
                
+               $multiplier = ($this->parameters['is2x']) ? 2 : 1;
+               
                try {
                        if (!$file->getValidationErrorType()) {
                                // shrink preview image if necessary
@@ -391,11 +411,11 @@ BROWSERCONFIG;
                                                        throw new UserInputException('image');
                                        }
                                        
-                                       if ($imageData[0] > Style::PREVIEW_IMAGE_MAX_WIDTH || $imageData[1] > Style::PREVIEW_IMAGE_MAX_HEIGHT) {
+                                       if ($imageData[0] > (Style::PREVIEW_IMAGE_MAX_WIDTH * $multiplier) || $imageData[1] > (Style::PREVIEW_IMAGE_MAX_HEIGHT * $multiplier)) {
                                                $adapter = ImageHandler::getInstance()->getAdapter();
                                                $adapter->loadFile($fileLocation);
                                                $fileLocation = FileUtil::getTemporaryFilename();
-                                               $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH, Style::PREVIEW_IMAGE_MAX_HEIGHT, false);
+                                               $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH * $multiplier, Style::PREVIEW_IMAGE_MAX_HEIGHT * $multiplier, false);
                                                $adapter->writeImage($thumbnail, $fileLocation);
                                        }
                                }
@@ -404,23 +424,23 @@ BROWSERCONFIG;
                                }
                                
                                // move uploaded file
-                               if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension())) {
+                               if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension())) {
                                        @unlink($fileLocation);
                                        
                                        // store extension within session variables
-                                       WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'], $file->getFileExtension());
+                                       WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : ''), $file->getFileExtension());
                                        
                                        if ($this->parameters['styleID']) {
                                                $this->updateStylePreviewImage($this->style);
                                                
                                                return [
-                                                       'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].'.'.$file->getFileExtension()
+                                                       'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension()
                                                ];
                                        }
                                        
                                        // return result
                                        return [
-                                               'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension()
+                                               'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension()
                                        ];
                                }
                                else {
index a8bd2a2d833f1c5fbc774c70b4816cc897239baa..fd459f0d2c19d99c70712d1f228d06edc1182c83 100644 (file)
@@ -144,7 +144,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                $xpath = $xml->xpath();
                
                $data = [
-                       'name' => '', 'description' => [], 'version' => '', 'image' => '', 'copyright' => '', 'default' => false,
+                       'name' => '', 'description' => [], 'version' => '', 'image' => '', 'image2x' => '', 'copyright' => '', 'default' => false,
                        'license' => '', 'authorName' => '', 'authorURL' => '', 'templates' => '', 'images' => '',
                        'variables' => '', 'date' => '0000-00-00', 'imagesPath' => '', 'packageName' => '', 'apiVersion' => '3.0'
                ];
@@ -217,6 +217,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                                                        
                                                        case 'copyright':
                                                        case 'image':
+                                                       case 'image2x':
                                                        case 'license':
                                                                $data[$element->tagName] = $element->nodeValue;
                                                        break;
@@ -500,33 +501,6 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        $styleData['packageID'] = $packageID;
                        $style = new StyleEditor(self::create($styleData));
                        
-                       // import preview image
-                       if (!empty($data['image'])) {
-                               $fileExtension = mb_substr($data['image'], mb_strrpos($data['image'], '.'));
-                               $index = $tar->getIndexByFilename($data['image']);
-                               if ($index !== false) {
-                                       $filename = WCF_DIR.'images/stylePreview-'.$style->styleID.$fileExtension;
-                                       $tar->extract($index, $filename);
-                                       FileUtil::makeWritable($filename);
-                       
-                                       if (file_exists($filename)) {
-                                               try {
-                                                       if (($imageData = getimagesize($filename)) !== false) {
-                                                               switch ($imageData[2]) {
-                                                                       case IMAGETYPE_PNG:
-                                                                       case IMAGETYPE_JPEG:
-                                                                       case IMAGETYPE_GIF:
-                                                                               $style->update(['image' => 'stylePreview-'.$style->styleID.$fileExtension]);
-                                                               }
-                                                       }
-                                               }
-                                               catch (SystemException $e) {
-                                                       // broken image
-                                               }
-                                       }
-                               }
-                       }
-                       
                        // handle descriptions
                        if (!empty($data['description'])) {
                                self::saveLocalizedDescriptions($style, $data['description']);
@@ -555,6 +529,35 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        $style->update($styleData);
                }
                
+               // import preview image
+               foreach (['image', 'image2x'] as $type) {
+                       if (!empty($data[$type])) {
+                               $fileExtension = mb_substr($data[$type], mb_strrpos($data[$type], '.'));
+                               $index = $tar->getIndexByFilename($data[$type]);
+                               if ($index !== false) {
+                                       $filename = WCF_DIR . 'images/stylePreview-' . $style->styleID . ($type === 'image2x' ? '@2x' : '') . $fileExtension;
+                                       $tar->extract($index, $filename);
+                                       FileUtil::makeWritable($filename);
+                                       
+                                       if (file_exists($filename)) {
+                                               try {
+                                                       if (($imageData = getimagesize($filename)) !== false) {
+                                                               switch ($imageData[2]) {
+                                                                       case IMAGETYPE_PNG:
+                                                                       case IMAGETYPE_JPEG:
+                                                                       case IMAGETYPE_GIF:
+                                                                               $style->update([$type => 'stylePreview-' . $style->styleID . ($type === 'image2x' ? '@2x' : '') . $fileExtension]);
+                                                               }
+                                                       }
+                                               }
+                                               catch (SystemException $e) {
+                                                       // broken image
+                                               }
+                                       }
+                               }
+                       }
+               }
+               
                $tar->close();
                
                return $style;
@@ -651,6 +654,9 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                if ($this->image && @file_exists(WCF_DIR.'images/'.$this->image)) {
                        $styleTar->add(WCF_DIR.'images/'.$this->image, '', FileUtil::addTrailingSlash(dirname(WCF_DIR.'images/'.$this->image)));
                }
+               if ($this->image2x && @file_exists(WCF_DIR.'images/'.$this->image2x)) {
+                       $styleTar->add(WCF_DIR.'images/'.$this->image2x, '', FileUtil::addTrailingSlash(dirname(WCF_DIR.'images/'.$this->image2x)));
+               }
                
                // fetch style description
                $sql = "SELECT          language.languageCode, language_item.languageItemValue
@@ -680,6 +686,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                $xml->writeElement('version', $this->styleVersion);
                $xml->writeElement('apiVersion', $this->apiVersion);
                if ($this->image) $xml->writeElement('image', $this->image);
+               if ($this->image2x) $xml->writeElement('image2x', $this->image2x);
                if ($this->copyright) $xml->writeElement('copyright', $this->copyright);
                if ($this->license) $xml->writeElement('license', $this->license);
                $xml->endElement();
index 471337eb4c1b3422e5de1c77f29078003bc0870c..e68646b6b7fe72bcbf4743ee4f6fcfadf3faece9 100644 (file)
@@ -1945,6 +1945,8 @@ Als Benachrichtigungs-URL in der Konfiguration der sofortigen Zahlungsbestätigu
                <item name="wcf.acp.style.globals.useFluidLayout"><![CDATA[Flexible Breite verwenden]]></item>
                <item name="wcf.acp.style.image"><![CDATA[Vorschaubild]]></item>
                <item name="wcf.acp.style.image.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Lade{else}Laden Sie{/if} hier ein Vorschaubild dieses Stiles hoch, als Bildformate sind JPG und PNG zulässig. Es wird empfohlen Vorschaubilder immer mit der Größe 102px × 64px anzulegen, größere Grafiken werden automatisch skaliert.]]></item>
+               <item name="wcf.acp.style.image2x"><![CDATA[Vorschaubild (HD)]]></item>
+               <item name="wcf.acp.style.image2x.description"><![CDATA[Diese Grafik wird für hochauflösende Bildschirme (z. B. Apple Retina-Display oder 4K/UHD-Monitore) verwendet und muss doppelt so groß sein wie das normale Vorschaubild.]]></item>
                <item name="wcf.acp.style.imagePath"><![CDATA[Bilder-Pfad]]></item>
                <item name="wcf.acp.style.imagePath.description"><![CDATA[Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Stil eigene Grafiken benötigt, {if LANGUAGE_USE_INFORMAL_VARIANT}solltest du{else}sollten Sie{/if} diese in einem Unterordner des Ordners „images“ ablegen. {if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den Pfad zu diesem Ordner an.]]></item>
                <item name="wcf.acp.style.importStyle"><![CDATA[Stil importieren]]></item>
index 4d25ef81484072dabfd205508280d7016129014e..4c04ea1eb2a14a89085647cf306c3c9c6b1eaaf0 100644 (file)
@@ -1888,6 +1888,8 @@ When prompted for the notification URL for the instant payment notifications, pl
                <item name="wcf.acp.style.globals.useFluidLayout"><![CDATA[Use fluid width]]></item>
                <item name="wcf.acp.style.image"><![CDATA[Preview Image]]></item>
                <item name="wcf.acp.style.image.description"><![CDATA[Upload a preview image for this style, acceptable image types are JPG and PNG. Dimensions should be 102px × 64px, exceeding images will be scaled.]]></item>
+               <item name="wcf.acp.style.image2x"><![CDATA[Preview Image (HD)]]></item>
+               <item name="wcf.acp.style.image2x.description"><![CDATA[This image is used on high resolution displays such as the Apple Retina or 4K/UHD-displays. The image must be double times the dimensions of the normal image to work.]]></item>
                <item name="wcf.acp.style.imagePath"><![CDATA[Image Path]]></item>
                <item name="wcf.acp.style.imagePath.description"><![CDATA[The path for optional images for your style, the directory should be located within “images”.]]></item>
                <item name="wcf.acp.style.importStyle"><![CDATA[Import Style]]></item>
index ca08ce48f265e43b0f2e50fffa44dcd6d910e5c3..1c802e8dbda0af1d741dc2d39674854cb5e12381 100644 (file)
@@ -1252,6 +1252,7 @@ CREATE TABLE wcf1_style (
        styleVersion VARCHAR(255) NOT NULL DEFAULT '',
        styleDate CHAR(10) NOT NULL DEFAULT '0000-00-00',
        image VARCHAR(255) NOT NULL DEFAULT '',
+       image2x VARCHAR(255) NOT NULL DEFAULT '',
        copyright VARCHAR(255) NOT NULL DEFAULT '',
        license VARCHAR(255) NOT NULL DEFAULT '',
        authorName VARCHAR(255) NOT NULL DEFAULT '',